/* The graphics, part three: the connection diagrams, the framed
   illustrations that sit inside a feature card, and the callout panels that
   say what is kept and what cannot be. */

/* -------------------------------------------------------------- mini bits --- */

/* A crop of the phone's screen for a feature: most are its terminal, so the
   frame paints the terminal's own Campbell dark in both themes. The height is
   shared, so a row of features lines up its names; the inspector draws them
   larger (home.css). chords.css, files.css, screens.css and shells.css fill
   them in. */
.mini {
  --mini-height: 188px;
  position: relative;
  display: grid;
  gap: 10px;
  align-content: center;
  min-height: var(--mini-height);
  padding: 14px;
  border-radius: 12px;
  background: var(--term-bg);
  overflow: hidden;
}

/* What holds, what to check, what we keep and what we never receive: each a
   titled inset grouped list, its rows split by hairlines. */
.sealed {
  display: grid;
  align-content: start;
  border-radius: var(--r-group);
  background: var(--surface);
  box-shadow: var(--highlight);
  overflow: hidden;
}

.sealed ul {
  display: grid;
  padding: 0;
  margin: 0;
  list-style: none;
}

.sealed li {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 16px 20px;
  color: var(--label-body);
  font-size: var(--callout);
  line-height: 1.5;
}

.sealed li + li {
  box-shadow: inset 48px 0 0 var(--surface), inset 0 0.5px 0 var(--hairline);
}

.sealed li svg {
  width: 16px;
  height: 16px;
  flex: none;
  margin-top: 3px;
  color: var(--tint);
}

.sealed.is-held li svg {
  color: var(--label-2);
}

.sealed.is-cant li svg {
  color: var(--green);
}

.sealed-nothing {
  display: flex;
  gap: 10px;
  align-items: center;
  padding: 16px 20px;
  background: var(--green-fill);
  color: var(--green-ink);
}

.sealed-nothing b {
  font-size: var(--callout);
  font-weight: 600;
}

.sealed-nothing svg {
  width: 18px;
  height: 18px;
}

/* The two lanes are separate cards; without a gap they read as one diagram
   with a stray divider through it. The gap clamps for the same reason the
   artwork does: a fixed 22px is a third of the whole diagram's height on a
   320px screen, so the cards would drift apart as they shrink. */
.tunnel {
  display: grid;
  gap: clamp(11px, 2.4vw, 22px);
}

/* One row at every width. Below 900px the row scales down with the viewport
   rather than turning into a column: every size here is a clamp whose ceiling
   is the desktop value and is reached by 900px, and a mobile device gets the same
   picture smaller.

   Each icon has a track of its own, exactly as wide as the icon, and the
   column gap is the space between an icon and the end of a wire. A wire fills
   the track between two icons, so its line stops one `--wire-gap` short of each
   icon at every width, whatever the labels say. Sizing a peer's track by its
   labels instead puts the icon somewhere inside a box of unknown width, and the
   line ends drift away from the icons by a different amount at each end.

   `--peer-w` is the room a peer's labels get. The edge tracks hold the half of
   it that hangs outside the icon, so the outer labels stay inside the card,
   and a wire is never so short that two neighbouring labels meet. */
.lane {
  --art: clamp(27px, 6vw, 54px);
  --wire-gap: clamp(7px, 1.556vw, 14px);
  --peer-w: clamp(72px, 18.667vw, 168px);
  --edge: max(0px, calc((var(--peer-w) - var(--art)) / 2 - var(--wire-gap)));
  display: grid;
  grid-template-columns:
    var(--edge) var(--art)
    minmax(calc(var(--peer-w) - var(--art) - 2 * var(--wire-gap)), 1fr)
    var(--art) var(--edge);
  gap: clamp(3px, 2vw, 18px) var(--wire-gap);
  align-items: start;
  padding: clamp(20px, 2.9vw, 28px) clamp(6px, 2.25vw, 20px) clamp(13px, 2.9vw, 24px);
  border-radius: var(--r-panel);
  background: var(--surface);
  box-shadow: var(--highlight)
}

.lane > :nth-child(1) { grid-column: 2; }
.lane > :nth-child(2) { grid-column: 3; }
.lane > :nth-child(3) { grid-column: 4; }

/* The gap inside a peer - icon to title to subtitle - shrinks with everything
   else, or the labels drift apart as the boxes they sit in get smaller and the
   peer stops reading as one unit.

   A peer sits in its icon's track and is wider than it: centred, it overhangs
   both sides equally, so the icon stays on the track and the labels centre
   under it. The cap makes a long label wrap rather than reach a neighbour. */
.peer {
  display: grid;
  justify-items: center;
  justify-self: center;
  width: max-content;
  max-width: var(--peer-w);
  gap: clamp(2.5px, 0.9vw, 8px);
  text-align: center;
}

/* Each floor here is half the ceiling. The `vw` term still reaches the full
   desktop size by 900px, so nothing above that width moves; below it the
   diagram can now reach half size instead of stopping at two thirds. */
.peer-art {
  display: grid;
  place-items: center;
  width: var(--art);
  height: var(--art);
  border-radius: clamp(8px, 1.8vw, 14px);
  background: var(--fill-2);
  color: var(--label)
}

.peer-art svg {
  width: clamp(12.5px, 2.8vw, 25px);
  height: clamp(12.5px, 2.8vw, 25px);
}

.peer-art.is-relay {
  background: var(--tint-fill);
  color: var(--tint)
}

.peer b {
  color: var(--label);
  font-size: clamp(11px, 1.45vw, 13px);
  font-weight: 600
}

.peer small {
  color: var(--label-2);
  font-size: clamp(9px, 1.12vw, 11px)
}

/* The Pro lane gives both captions the same `cap` grid area, so the cross-fade
   between them never changes the card's height. */
.peer-caption {
  grid-column: 1 / -1;
  /* 4px on a desktop. A narrow wire wraps its label onto a second line, which
     hangs below the peers, so the caption steps down to clear it. The step-down
     range is halved with everything else, so the gap shrinks on a mobile device rather
     than holding a desktop-sized gap under a half-sized diagram - but the 4px
     floor is the desktop value itself, so it stays: on a wide screen the `max`
     is what applies, and halving it would move the desktop caption. */
  padding-top: max(4px, calc(14px - 1.35vw));
  color: var(--label-2);
  font-size: var(--footnote);
  text-align: center;
}

/* The wire: a hairline that a packet walks along, carrying a padlock. It is
   dropped by half the difference in height so its line runs through the middle
   of the icons it joins, not through the labels under them. */
.wire {
  position: relative;
  height: var(--wire-h);
  margin-top: calc((var(--art) - var(--wire-h)) / 2);
}

/* `width` is restated rather than left to the horizontal rule, so the
   left/right=0 stretch cannot pin it open: a wire that spans mobile device to desktop
   is as wide as its grid area, and its hairline has to be exactly that wide
   whatever the box is. */
.wire-line {
  position: absolute;
  top: 50%;
  right: 0;
  left: 0;
  width: auto;
  height: 1px;
  background: repeating-linear-gradient(90deg, var(--hairline-strong) 0 6px, transparent 6px 14px);
  transform: translateY(-50%);
}

.wire-lock {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 2;
  display: grid;
  place-items: center;
  width: clamp(15px, 3.4vw, 30px);
  height: clamp(15px, 3.4vw, 30px);
  border-radius: 50%;
  background: var(--surface);
  box-shadow: 0 0 0 1px var(--tint-line);
  color: var(--tint);
  transform: translate(-50%, -50%)
}

.wire-lock svg {
  width: clamp(7px, 1.6vw, 14px);
  height: clamp(7px, 1.6vw, 14px);
}

/* One dot that goes out and comes back, rather than a pair crossing at once.
   Two dots read as two sessions; the wire is one conversation, and a single
   dot that leaves and returns is the shape of a request and its reply without
   asking the eye to follow two things at the same time.

   `--wire-run` is one full crossing-and-return, and `--lane-cycle` is the
   security lane's whole loop. They are related on purpose: the lane has two
   states, and each state is exactly one run, so a dot is finishing its trip
   outward at the moment the diagram hands over to the next state. The values
   are declared together so the relationship cannot drift. */
.lane {
  --wire-run: 3.5s;
  --lane-cycle: 7s;
  --wire-h: clamp(17px, 3.8vw, 34px);
}

.wire-packet {
  position: absolute;
  top: 50%;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--tint);
  box-shadow: 0 0 8px rgba(46, 155, 255, 0.8);
  transform: translate(-50%, -50%);
  animation: packet var(--wire-run) var(--ease-in-out) infinite
}

/* `left` is the one animated property on this site that is not transform or
   opacity, and it is a 5px dot. The alternative (offset-path) needs geometry in
   absolute units, which the wire does not have.

   The midpoint is the far end of the wire, so the two halves are one crossing
   each way. ease-in-out over the whole run does the work of decelerating into
   the far end and setting off back again, which is what makes it read as a
   single dot turning around rather than as one animation swapping for another. */
@keyframes packet {
  0% {
    left: 0;
    opacity: 0;
  }
  10% {
    opacity: 1;
  }
  50% {
    left: 100%;
    opacity: 1;
  }
  90% {
    opacity: 1;
  }
  100% {
    left: 0;
    opacity: 0;
  }
}

/* The same run started from the far end. The wire between the relay and the
   desktop carries a dot that begins at the desktop, so on the remote lane both
   ends of the path are sending at the same time and the two dots converge on
   the relay. `packet` animates `left`, so the reversed leg is stated by hand
   rather than by reversing the animation. */
@keyframes packet-from-far-end {
  0% {
    left: 100%;
    opacity: 0;
  }
  10% {
    opacity: 1;
  }
  50% {
    left: 0;
    opacity: 1;
  }
  90% {
    opacity: 1;
  }
  100% {
    left: 100%;
    opacity: 0;
  }
}

/* Each half of the remote path starts at the device it belongs to: the mobile device's
   dot begins at the mobile device and walks right, the desktop's begins at the desktop
   and walks left. Nothing is staggered, because there is no hand-off to stagger
   around any more - both devices are on the wire for the whole of the first
   state, and the dots meeting at the relay is the picture of that. */
.wire.is-relay .wire-packet {
  animation-name: packet;
  animation-delay: 0s;
}

.wire.is-relay.is-second .wire-packet {
  animation-name: packet-from-far-end;
  animation-delay: 0s;
}

/* The direct link is only on screen for the second state, so its dot waits out
   the first one. It then has a full run to itself: the dot is crossing as the
   direct state comes up and has turned around by the time it goes. */
.lane.is-pro .wire-direct .wire-packet {
  animation-delay: var(--wire-run);
}

/* The label under the wire. The peers' labels overhang their icons by up to an
   edge track's width, into the wire's span, so the label is narrowed by that
   much at each end and wraps rather than running into them. */
.wire-label {
  position: absolute;
  top: 100%;
  left: 50%;
  width: calc(100% - 2 * var(--edge));
  /* It is nudged clear of the hairline by half the wire's height, so it has to
     shrink with it: a fixed 4px would be a whole line's gap once the wire is
     half size, and the label would float below the diagram. */
  margin-top: clamp(2px, 0.45vw, 4px);
  color: var(--label-2);
  font-size: clamp(8px, 1.06vw, 11px);
  font-weight: 510;
  text-align: center;
  transform: translateX(-50%);
}

/* The Pro lane has five stops: mobile device, wire, relay, wire, desktop. The relay is
   the only one drawn in the accent, because it is the only one we operate. */
.lane.is-pro {
  --peer-w: clamp(66px, 16.444vw, 148px);
  --mid-w: clamp(66px, 18vw, 162px);
  --wire-min: calc((var(--peer-w) + var(--mid-w)) / 2 - var(--art) - 2 * var(--wire-gap));
  grid-template-columns:
    var(--edge) var(--art) minmax(var(--wire-min), 1fr)
    var(--art) minmax(var(--wire-min), 1fr)
    var(--art) var(--edge);
}

.lane.is-pro > .is-relay-peer {
  max-width: var(--mid-w);
}

/* --------------------------------------------------------- relay handover ---
   The Pro lane plays the sequence a remote connection actually makes: the relay
   introduces the two devices, and then it is not in the path any more. It is
   the one claim on this page a static diagram cannot make, because the whole
   point is which of the wires are live at which moment.

   One --lane-cycle clock holds every part of it, so the relay's fade, the
   direct link's arrival and the captions cannot drift apart: they are all
   percentages of the same clock. The clock is two --wire-runs, one per state,
   so a state's dot completes its crossing and return before the next state
   begins. The relay is up first, the handover lands around the middle, the
   direct session holds, and the reset is a fade rather than a cut so the loop
   has no visible seam.

   Every animated property here is opacity or transform, so the cycle runs on the
   compositor. The one exception is the packet's `left`, which is the same
   5px-dot exception `packet` already documents.

   Nothing here is width-gated: a mobile device gets the same row, scaled, and the same
   handover at the same tempo. */
/* The row layout sizes this by stretching it across the columns it spans, which
   is what a mobile-device-to-desktop wire has to do. Nothing else can set a width here:
   a width would cap the stretch and the wire would stop reaching the desktop. */
.wire-direct {
  display: grid;
  place-items: center;
}

.lane.is-pro {
  grid-template-areas:
    ".   phone  wire-a relay   wire-b desktop ."
    "cap cap    cap    cap     cap    cap     cap";
}

.lane.is-pro > :nth-child(1) { grid-area: phone; }
.lane.is-pro > :nth-child(2) { grid-area: wire-a; }
.lane.is-pro > .is-relay-peer { grid-area: relay; }
.lane.is-pro > :nth-child(4) { grid-area: wire-b; }
.lane.is-pro > :nth-child(5) { grid-area: desktop; }
.lane.is-pro > .peer-caption { grid-area: cap; }

/* The direct link takes the place of the two relay wires and the relay itself
   rather than sitting below them, so the handover is a swap inside one row and
   the card is exactly as tall as it was before the animation existed. It spans
   the three middle columns - the two wires and the relay between them - so it
   runs from the mobile device to the desktop and crosses nothing. */
.lane.is-pro > .wire-direct {
  grid-area: wire-a / wire-a / wire-b / wire-b;
  z-index: 1;
}

.wire-direct {
  position: relative;
  height: var(--wire-h);
  width: auto;
  margin-top: calc((var(--art) - var(--wire-h)) / 2);
}

/* The relay and its two wires stand down together: the relay dims, and the
   wires it fed stop carrying a dot. The cycle is one run per state, so the
   changeover lands on the dot turning around at the far end of its wire. */
.lane.is-pro .is-relay-peer,
.lane.is-pro .wire.is-relay {
  animation: relay-stand-down var(--lane-cycle) var(--ease-in-out) infinite;
}

.lane.is-pro .wire-direct {
  animation: direct-arrives var(--lane-cycle) var(--ease-in-out) infinite;
}

/* Both captions share the one caption cell, so the cell is the same height
   whichever one is showing and the card cannot change height mid-loop. The
   switch is a cross-fade at the same instant the diagram changes. */
.lane.is-pro .peer-caption:not(.is-direct) {
  animation: cap-relay var(--lane-cycle) var(--ease-in-out) infinite;
}

.lane.is-pro .peer-caption.is-direct {
  display: block;
  padding-top: 0;
  animation: cap-direct var(--lane-cycle) var(--ease-in-out) infinite;
}

/* The relay leaves the diagram entirely rather than lingering as a ghost. A dim
   relay still reads as part of the path, which is the one thing the handover is
   meant to disprove, so it goes to zero opacity and takes its two wires with it.
   `visibility` follows at the end of the fade so the two dots stop being drawn
   and stop taking a hit-test while they are invisible. */
@keyframes relay-stand-down {
  0%,
  34% {
    opacity: 1;
    visibility: visible;
  }
  46%,
  86% {
    opacity: 0;
    visibility: hidden;
  }
  100% {
    opacity: 1;
    visibility: visible;
  }
}

@keyframes direct-arrives {
  0%,
  36% {
    opacity: 0;
    transform: scaleX(0.4);
  }
  48%,
  86% {
    opacity: 1;
    transform: scaleX(1);
  }
  100% {
    opacity: 0;
    transform: scaleX(0.4);
  }
}

@keyframes cap-relay {
  0%,
  38% {
    opacity: 1;
  }
  50%,
  88% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

@keyframes cap-direct {
  0%,
  38% {
    opacity: 0;
  }
  50%,
  88% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}

.sealed h3 {
  padding: 16px 20px 12px;
  font-size: var(--headline);
  letter-spacing: var(--track-body);
  box-shadow: inset 0 -0.5px 0 var(--hairline);
}
