/* ==========================================================================
   agent.css — the agent-platforms figure: a brief becomes agents.

   The picture: a user outside the builder, four signals filling the builder's
   four blocks, one big arrow into a factory of meshed gears, and four agents
   walking four roads to the four jobs they were built for.

   Division of labour, the same one the other figures use:

     this file          the frame and the look — what a box is, what a road is
                        made of, what a bot is painted in
     agent-timeline.css where everything stands and when everything happens,
                        both generated from the one schedule in tools/agent.py

   The look is the ADTech diagram's, because that is where this figure's
   vocabulary comes from: a hairline connector at low opacity, cargo that grows
   out of nothing and shrinks back, a glow on anything that is meant to read as
   live, and one colour per road so four roads are four roads.

   Two things here are not in that figure:

     · the stage is not stretched. `.agt__art` carries a viewBox with the
       stage's own ratio and the default preserveAspectRatio, so a unit is the
       same length in both axes. That is what lets this figure carry bots: the
       ADTech fan stretches its box, which is why its cargo can only ever be a
       round dot.
     · the rays. A signal is a dash running along a curve that is otherwise
       not drawn, with a hairline left behind it once it has passed — so at
       rest the picture shows what is connected, and in motion it shows what
       is being sent.

   Under 1000px it stops being a picture: the drawing goes, the parts become
   one column in reading order with a rail down the left, and the same
   information is a list. That state and prefers-reduced-motion share one
   block at the foot of this file — both want the finished frame, held.
   ========================================================================== */

.agt {
  --agt-cycle: 14.4s;        /* agent-timeline.css sets the real ones */
  --agt-ratio: 1200 / 520;
  margin: var(--s-5) 0 var(--s-6);
}
.agt, .agt figure { margin-inline: 0; }

/* --- the caption ---------------------------------------------------------- */
/* A figcaption, not a heading: the page's outline is h1 → h2, and a picture's
   own title has no business in it. */
.agt__cap {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-2);
  margin-bottom: var(--s-4);
}
.agt__h {
  font-weight: 640; font-size: var(--fs-md); letter-spacing: -.015em;
  color: var(--ink);
}
.agt__note {
  flex: 1 1 22ch; min-width: 0;
  font-size: var(--fs-3xs); line-height: 1.45; color: var(--ink-3);
}

/* --- the stage ------------------------------------------------------------ */
.agt__body { position: relative; aspect-ratio: var(--agt-ratio); }

/* The drawing sits under the labels and over nothing: every box on the
   picture is HTML, so the SVG carries only line work and the bots. */
.agt__art {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; pointer-events: none; overflow: visible;
}

/* Every label that is not inside a box. */
.agt__tag {
  position: absolute;
  font-family: var(--font-mono); font-size: var(--fs-3xs);
  letter-spacing: .04em; text-transform: lowercase;
  color: var(--ink-3); white-space: nowrap;
}

/* --- the user ------------------------------------------------------------- */
/* Outside the builder, because they are not part of it — they are the one
   filling it in. The traditional avatar: a head and a pair of shoulders, no
   frame and no cleverness, so it is read as "a person" at a glance. */
.agt-user {
  fill: var(--c, var(--c-blue));
  animation-name: agt-user;
}
.agt-user__head, .agt-user__body { fill: inherit; }
.agt__tag--user {
  left: var(--x); top: var(--y); transform: translateX(-50%);
  text-align: center;
  animation-name: agt-user;
}

/* --- the signals --------------------------------------------------------- */
/* The curve a signal runs. Not drawn until one has: what is left afterwards
   is a hairline, at the same weight and opacity as an ADTech trace, so the
   two figures read as the same hand. */
.agt-trace {
  fill: none; stroke: var(--bc, var(--c-blue)); stroke-width: 1.25;
  stroke-linecap: round; opacity: 0;
  vector-effect: non-scaling-stroke;
}

/* And the signal itself: 18 units of beam in a gap longer than the path, so
   exactly one dash exists and the offset carries it from end to end. The dash
   is in pathLength units — every ray is normalised to 100 — which is what
   lets one rule serve four curves of four different lengths. */
.agt-ray {
  fill: none; stroke: var(--bc, var(--c-blue));
  stroke-width: 3; stroke-linecap: round;
  stroke-dasharray: 26 100;
  opacity: 0;
  filter: drop-shadow(0 0 7px var(--bc, var(--c-blue)));
  vector-effect: non-scaling-stroke;
}

/* --- the builder --------------------------------------------------------- */
.agt__panel {
  position: absolute; left: var(--x); top: var(--y);
  width: var(--w); height: var(--h);
  z-index: 2;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-lg);
}
.agt__tag--panel { top: calc(-1 * var(--s-4) - 3px); left: 2px; }

/* The blocks are a layer of their own over the stage, not children of the
   panel. Inside it they were positioned against its padding box — a pixel in
   from its border — which put them a pixel off the task rows they are meant
   to line up with, and left the frame with 14px of air above and 16 below.
   The panel is now purely the frame drawn behind them. */
.agt__blocks { position: absolute; inset: 0; z-index: 2; }

/* A block is placed rather than laid out, so it lines up with the task on its
   own row across the whole picture. */
.agt-blk {
  position: absolute; left: var(--x); top: var(--y);
  width: var(--w); height: var(--h);
  display: grid; place-content: center; place-items: center;
  padding: 0 var(--s-3);
  overflow: hidden;
  background: var(--bg-2);
  border: 1px solid var(--line); border-radius: var(--r-sm);
}
/* What the signal fills it with, in that block's own colour. Behind the
   label, not instead of it. */
.agt-blk__fill {
  position: absolute; inset: 0; z-index: -1; opacity: 0;
  background: color-mix(in oklab, var(--bc, var(--c-blue)) 26%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--bc, var(--c-blue)) 48%, transparent);
}
/* Mono, and centred. These are not headings — they are the four fields of a
   brief, and a field's name reads as a field's name in the typeface the rest
   of this site keeps for machine-facing text. */
.agt-blk__n {
  font-family: var(--font-mono);
  font-weight: 500; font-size: var(--fs-md); letter-spacing: -.005em;
  line-height: 1.3; color: var(--ink); text-align: center;
}

/* --- the big arrow ------------------------------------------------------- */
/* One filled shape, not a stroke and two ticks: it is the only connector here
   that carries the whole brief at once, and it has to look like more than a
   wire. */
.agt-arrow {
  /* Stroked in its own colour with round joins: that is what takes the
     corners and the spiked head off a block arrow, and it costs one
     declaration instead of a dozen arc segments in the path. Both paints are
     solid — the element's own opacity does the dimming, or the semi-opaque
     stroke would draw a darker rim over the semi-opaque fill. */
  fill: var(--c, var(--c-blue));
  stroke: var(--c, var(--c-blue)); stroke-width: 5;
  stroke-linejoin: round; stroke-linecap: round;
  opacity: .16;
  animation-name: agt-arrow;
}
.agt-arrow__beam {
  fill: none; stroke: var(--c, var(--c-blue));
  stroke-width: 7; stroke-linecap: round;
  stroke-dasharray: 26 100;
  opacity: 0;
  filter: drop-shadow(0 0 6px var(--c, var(--c-blue)));
  animation-name: agt-beam;
}

/* --- the factory --------------------------------------------------------- */
.agt__factory {
  position: absolute; left: var(--x); top: var(--y);
  width: var(--w); height: var(--h);
  /* Under the drawing, not over it: the gears are this box's contents and
     they are drawn in the SVG. A z-index on them cannot lift them out of
     that SVG's stacking context, so the box goes below instead — and its own
     background stops painting over its own machinery the moment it lights. */
  z-index: 0;
  border: 1px solid var(--line-strong); border-radius: var(--r-lg);
  background: color-mix(in oklab, var(--c, var(--c-blue)) 6%, var(--surface));
  opacity: .3;
  animation-name: agt-factory;
}
.agt__tag--factory {
  top: calc(-1 * var(--s-4) - 3px); left: 2px;
  color: var(--ink-2);
}
/* The engine gets its credit as a caption on the factory rather than a box of
   its own: it is what this runs on, not another place on the map. Under the
   box and not inside it — inside, it stood in the air around the gears, and
   that air is now one measure on all four sides. */
.agt__tag--factnote { top: calc(100% + 7px); left: 2px; opacity: .8; }

/* The gears live in the drawing, over the box they turn inside — and they
   warm with it, because a lit factory full of grey gears reads as broken.

   The opacity is on the group, never on a gear. Per gear, every place two of
   them overlap composites twice and comes out lighter than the rest, which is
   what made the train look like it was sitting on a semi-transparent panel. */
.agt-gears { opacity: .3; animation-name: agt-factory; }
.agt-gear__g {
  /* Pinned to the group's own bounding box, whose centre is the hub. Not the
     view box: a percentage origin resolved against *that* is measured from
     the box's corner, and a turn of anything but a whole revolution then
     carries the gear clean across the picture. */
  transform-box: fill-box; transform-origin: center;
  animation-timing-function: linear;
}
.agt-gear rect, .agt-gear circle { stroke: none; }
.agt-gear rect, .agt-gear__rim { fill: color-mix(in oklab, var(--ink-3) 70%, var(--bg)); }
.agt-gear__hub { fill: color-mix(in oklab, var(--c, var(--c-blue)) 60%, var(--bg)); }
.agt-gear__hole { fill: var(--bg); }

/* --- the roads and what walks them --------------------------------------- */
/* Drawn, unlike the rays: an agent walks one, so it has to be there before
   the agent is. Each keeps its own colour. */
.agt-road {
  fill: none; stroke: var(--rc, var(--c-blue)); stroke-width: 1.6;
  stroke-linecap: round; opacity: .16;
  vector-effect: non-scaling-stroke;
}

/* A traveller. The stylesheet moves it now — it used to be <animateMotion>,
   and one animation engine cannot fall out of step with itself the way two
   could. Linear, because a courier does not accelerate. */
.agt-carry {
  color: var(--rc, var(--c-blue));
  animation-duration: var(--agt-cycle);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-fill-mode: both;
}
/* The bot grows out of the factory and shrinks into the job. The scale is on
   the bot and the motion on the carrier around it: one element cannot hold
   both a motion transform and a CSS one. `both` is not optional — before its
   turn an element with no fill mode shows its own base style at full size,
   while animateMotion has not started either, which puts a bright bot in the
   corner of the picture. */
.agt-bot {
  transform-box: fill-box; transform-origin: 50% 50%;
  opacity: 0;
  animation-duration: var(--agt-cycle);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-fill-mode: both;
}
.agt-bot__head {
  fill: color-mix(in oklab, currentColor 78%, var(--bg));
  stroke: currentColor; stroke-width: 1.2;
}
.agt-bot__eye { fill: var(--bg); }
.agt-bot__ant, .agt-bot__dish, .agt-bot__neck {
  fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round;
}
.agt-bot__tip { fill: currentColor; }
.agt-bot__wheel { fill: color-mix(in oklab, currentColor 55%, var(--bg)); }

/* The job an agent is carrying, on its body — and the same glyph is the icon
   on the task it walks to, which is what says which bot goes where. */
.agt-pl__box {
  fill: color-mix(in oklab, var(--bg) 55%, currentColor);
  stroke: currentColor; stroke-width: 1.1;
}
.agt-pl__clip { fill: currentColor; }
.agt-pl__ink {
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.agt-pl__dot { fill: currentColor; }

/* --- what the agents make ------------------------------------------------ */
/* Slides, task plates, letters and charts. One set of rules for all four,
   because they are the same object in the picture's grammar: a small plate in
   its agent's colour that appears, does one thing, and goes. What differs is
   the glyph on it and the path it takes — both of which are in the drawing,
   not here. */
.agt-out {
  transform-box: fill-box; transform-origin: 50% 50%;
  color: var(--rc, var(--c-blue));
  opacity: 0;
  animation-duration: var(--agt-cycle);
  animation-timing-function: var(--ease-out);
  animation-iteration-count: infinite;
  animation-fill-mode: both;
}
.agt-out__box {
  fill: color-mix(in oklab, var(--bg) 62%, currentColor);
  stroke: currentColor; stroke-width: 1.2;
}
.agt-out__ink, .agt-out__bar {
  fill: none; stroke: currentColor;
  stroke-linecap: round; stroke-linejoin: round;
}
.agt-out__ink { stroke-width: 1.9; }
.agt-out__bar { stroke-width: 2.6; opacity: .55; }
.agt-out__fill, .agt-out__dot { fill: currentColor; }
/* The only text in the drawing, and the only text allowed in it: a token of
   six characters that is the same in both languages. */
.agt-out__t {
  fill: var(--ink);
  font-family: var(--font-mono); font-size: 15px; letter-spacing: .01em;
}

/* --- the four jobs ------------------------------------------------------- */
/* A station is a workshop, not a caption for one: its name runs along the top
   and the rest of it is the floor its agent works on.

   Under the drawing, not over it. The road runs *into* the station and the
   agent stands inside it — a box painted over the picture would cut every
   road off at its own doorstep, which is the same reason the factory box sits
   below its own gears. */
.agt__tasks { position: absolute; inset: 0; z-index: 0; }
.agt-task {
  position: absolute; left: var(--x); top: var(--y);
  width: var(--w); height: var(--h);
  color: var(--rc, var(--c-blue));
  background: color-mix(in oklab, var(--rc, var(--c-blue)) 4%, var(--surface));
  border: 1px solid var(--line); border-radius: var(--r-md);
  border-left: 3px solid var(--rc, var(--c-blue));
  opacity: .34;
}
.agt-task__n {
  position: absolute; left: var(--s-4); top: 9px;
  font-weight: 640; font-size: var(--fs-xs); letter-spacing: -.01em;
  line-height: 1.25; color: var(--ink);
}

/* --- the analytics lines ------------------------------------------------- */
/* Not a card. A chart that has arrived is a result; this agent's job is the
   reading of them, so what its floor shows is the line still being drawn —
   a dash in pathLength units run from full offset to nought, with the reading
   riding its head. */
.agt-graph {
  fill: none; stroke: var(--rc, var(--c-blue));
  stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 100 100;
  opacity: 0;
  filter: drop-shadow(0 0 5px color-mix(in oklab, var(--rc, var(--c-blue)) 55%, transparent));
  vector-effect: non-scaling-stroke;
}
.agt-graph__head {
  fill: var(--rc, var(--c-blue)); opacity: 0;
  animation-duration: var(--agt-cycle);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-fill-mode: both;
}

/* --- one clock ----------------------------------------------------------- */
/* Every animation on the figure runs the same pass, so nothing can drift out
   of step with the story — including the <animateMotion> in the drawing,
   whose dur is the same number, generated from the same schedule. */
.agt-user, .agt__tag--user, .agt-trace, .agt-ray, .agt-blk, .agt-blk__fill,
.agt-arrow, .agt-arrow__beam, .agt__factory, .agt-gears, .agt-gear__g,
.agt-road, .agt-task, .agt-graph {
  animation-duration: var(--agt-cycle);
  animation-iteration-count: infinite;
  animation-fill-mode: both;
  animation-timing-function: var(--ease-out);
}
.agt-ray, .agt-arrow__beam, .agt-graph { animation-timing-function: linear; }

/* --------------------------------------------------------------------------
   Below 1000px the picture is a list.

   The parts are already in reading order in the markup — the user, the
   builder's four blocks, the factory, the four jobs — so the fallback is to
   stop placing them and let them stack. The rail keeps the sequence, which is
   the whole argument once the roads are gone.
   -------------------------------------------------------------------------- */
@media (max-width: 1000px) {
  .agt__body {
    aspect-ratio: auto;
    display: grid; gap: var(--s-3);
    grid-template-columns: minmax(0, 1fr);
  }
  .agt__art { display: none; }

  .agt__panel, .agt__factory, .agt__blocks, .agt__tasks, .agt__tag--user {
    position: static; width: auto; height: auto;
    /* the centring transform goes with the placement, or the user's label is
       carried half its own width off the left of the page */
    transform: none;
  }
  .agt__panel { padding: var(--s-4) var(--s-3) var(--s-2); }
  .agt__factory { padding: var(--s-5) var(--s-3) var(--s-4); }
  .agt__tag--panel, .agt__tag--factory { position: static; }
  .agt__tag--factnote { position: static; }
  .agt__tag--user { text-align: left; }

  .agt__blocks, .agt__tasks {
    display: grid; gap: var(--s-2);
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  /* `relative`, and the name back into the flow. A station's name is placed
     against it in the picture; with the box gone static that placement is
     resolved against the stage instead, and all four names pile up in one
     corner. Same trap as the block fill above, same fix. */
  .agt-task { position: relative; left: auto; top: auto; }
  .agt-task__n { position: static; }
  /* `relative`, not `static`: the fill behind a block's label is absolute
     against it, and a block with no positioning of its own hands that fill
     to the stage — where it covers the entire figure. The offsets have to go
     with the placement, though: on a relatively positioned box `left` shifts
     it rather than placing it, and the block slides out of the panel. */
  .agt-blk { position: relative; left: auto; top: auto; }
  .agt-blk, .agt-task {
    width: auto; height: auto;
    min-height: 44px; padding: var(--s-2) var(--s-3);
  }
  .agt-blk {
    border-left: 2px solid var(--c, var(--c-blue));
    border-radius: 0 var(--r-sm) var(--r-sm) 0;
  }
}

@media (max-width: 620px) {
  .agt__blocks, .agt__tasks { grid-template-columns: minmax(0, 1fr); }
}

/* --------------------------------------------------------------------------
   The finished state, held — for the two contexts that want a still picture.

   Reduced motion is the obvious one. The list is the other, and it matters
   just as much: with the pass still running over a static list, a reader
   meets an empty builder and four dim jobs for most of a fourteen-second
   loop, because an animation's own values win over anything the layout says.

   And it is the finished frame, not the first: an empty builder with no
   agents is the one moment of this story that says nothing.
   -------------------------------------------------------------------------- */
@media (max-width: 1000px), (prefers-reduced-motion: reduce) {
  /* What travels goes; what was *made* stays. The three slides are the one
     piece of output that is a result rather than a delivery, and in a picture
     that has stopped they are the only thing saying an agent did anything. */
  .agt-ray, .agt-arrow__beam, .agt-carry, .agt-graph { display: none; }
  .agt-park .agt-out { animation: none; opacity: 1; }
  .agt-user, .agt__tag--user, .agt-trace, .agt-blk, .agt-blk__fill,
  .agt-arrow, .agt__factory, .agt-gears, .agt-gear__g, .agt-road, .agt-task {
    animation: none;
  }
  .agt-user, .agt__tag--user, .agt-blk, .agt-blk__fill, .agt__factory,
  .agt-gears, .agt-task { opacity: 1; }
  .agt-trace, .agt-road { opacity: .3; }
  .agt-arrow { opacity: .4; }
}
