/* ==========================================================================
   SYS.02 module diagrams.
   Four hand-built SVGs that replaced four stock photographs. Each one draws
   what the module actually is, which a photograph of a rack or a laptop never
   did — and it removes an unanswerable licensing question about images whose
   provenance nobody in this repo can vouch for.

   EVERY colour comes from a token and is set HERE, never as an attribute on
   the SVG: var() does not resolve in an SVG presentation attribute (the
   gradient in SYS.06 already caught us out that way). Setting it in CSS also
   means the diagrams follow the theme for free.
   ========================================================================== */

.dg{
  position:absolute; inset:0; width:100%; height:100%;
  display:block; overflow:hidden;
  /* Matches what object-fit:cover did for the photographs it replaced. */
  color:var(--ink);
}

/* Blueprint field behind the drawing — the same language as .gridlay, but on
   the PANEL rather than inside the SVG. The media panel is about 1.07:1 on a
   desktop and 16/9 on a phone while the drawings are 1.6:1, so the SVG is
   letterboxed with `meet`; a grid drawn inside it would stop at the drawing's
   edge and leave bare bands above and below. */
.mod .m-media{
  background-color:var(--media-bg);
  background-image:
    linear-gradient(to right, var(--grid-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px);
  background-size:58px 58px;
}
/* The scrim was built to darken a photograph so nothing competed with it.
   There is no photograph now, the module copy sits in the other column, and
   at .85 it was simply greying out the line work. A whisper is enough to
   settle the panel into the card. */
.mod .m-media .duo{
  background:linear-gradient(100deg, rgba(var(--duo-rgb),.34), rgba(var(--duo-rgb),.06) 60%, transparent);
}
/* Breathing room so the drawing never touches the panel edge. */
/* LABEL SIZE IS A LADDER, not a constant -- the same device .wyg uses below,
   and for the same reason, which this drawing had never accounted for.

   The viewBox is 800x500 and preserveAspectRatio is "meet", so the whole
   drawing is scaled by (rendered width / 800) and THE TEXT IS SCALED WITH IT.
   A constant 15px label therefore never rendered at 15px anywhere: measured
   across seventeen widths it came out at 10.7px on a 1440 desktop, 7.6px at
   1024, and 5.0px at 390 -- where the port numbers were an illegible smudge
   rather than "01".

   --t is the label size in USER UNITS. Each step below is chosen so that
   --t x scale lands near 13px of actual screen text, and the three other
   label rules are multiples of it so their relative hierarchy is fixed.
   The jump at 860px is the card going from two columns to one, which makes
   the drawing WIDER again -- hence the ladder restarts there rather than
   continuing to climb. */
.dg{ padding:clamp(14px,2vw,30px); --t:19; }

/* two-column card: the media panel shrinks as the viewport narrows */
@media (max-width:1360px){ .dg{ --t:21; } }
@media (max-width:1200px){ .dg{ --t:24; } }
@media (max-width:1024px){ .dg{ --t:27; } }
/* one column from here: the panel goes full width, so the scale jumps back up */
@media (max-width:860px) { .dg{ --t:17; } }
@media (max-width:768px) { .dg{ --t:20; } }
@media (max-width:640px) { .dg{ --t:23; } }
@media (max-width:560px) { .dg{ --t:27; } }
@media (max-width:480px) { .dg{ --t:31; } }
@media (max-width:430px) { .dg{ --t:35; } }
@media (max-width:390px) { .dg{ --t:40; } }
@media (max-width:360px) { .dg{ --t:44; } }

.dg-frame{ fill:none; stroke:var(--line); stroke-width:1.5; }
.dg-line{ stroke:var(--line); stroke-width:1; fill:none; }
.dg-tag{
  fill:var(--amber); font-family:var(--mono); font-size:calc(var(--t) * .87px);
  letter-spacing:.2em; text-transform:uppercase;
}
.dg-tag-c{ fill:var(--accent); text-anchor:middle; }
.dg-lbl{ fill:var(--muted); font-family:var(--mono); font-size:calc(var(--t) * 1px); letter-spacing:.06em; }
.dg-nums text{
  fill:var(--faint); font-family:var(--mono); font-size:calc(var(--t) * .8px);
  letter-spacing:.14em; text-anchor:middle;
}
/* Both forms. .dg-feed and .dg-reflow carry .dg-runs on the path ITSELF, so
   the descendant selector alone never matched them — they fell back to the
   SVG default fill:black and painted a solid wedge across the diagram. */
.dg-runs path, path.dg-runs{ stroke:var(--accent); stroke-width:1.6; fill:none; opacity:.5; stroke-linecap:round; }
.dg-nodes circle{ fill:var(--accent); opacity:.75; }

/* ---------- MOD.01 patch panel ---------- */
.dg-ports rect{ fill:var(--media-bg); stroke:var(--line); stroke-width:1; }
.dg-leds circle{ fill:var(--accent); opacity:.25; animation:dg-blink 2.4s var(--ease-out) infinite; animation-delay:var(--d,0s); }
@keyframes dg-blink{ 0%,72%{ opacity:.22 } 78%{ opacity:1 } 92%,100%{ opacity:.22 } }

/* ---------- MOD.02 tenant ---------- */
.dg-people g circle{ fill:none; stroke:var(--muted); stroke-width:1.6; }
.dg-people g path{ fill:none; stroke:var(--muted); stroke-width:1.6; }
.dg-hex{ fill:color-mix(in oklab, var(--accent) 10%, transparent); stroke:var(--accent); stroke-width:1.6; }
.dg-lock{ fill:none; stroke:var(--accent); stroke-width:2; }
.dg-lockbody{ fill:var(--accent); opacity:.85; }
.dg-svc rect{ fill:var(--media-bg); stroke:var(--line); stroke-width:1; }
.dg-fan path{ opacity:.28; }
.dg-token{ fill:var(--accent); }

/* ---------- MOD.03 support loop ---------- */
.dg-loop{ fill:none; stroke:var(--line); stroke-width:1.5; }
/* The travelling arc. 2πr for r=150 is ~942. */
.dg-loop-run{
  fill:none; stroke:var(--accent); stroke-width:2.5; stroke-linecap:round;
  stroke-dasharray:150 792; stroke-dashoffset:0;
  transform-origin:470px 250px;
  animation:dg-orbit 7s linear infinite;
}
@keyframes dg-orbit{ to{ stroke-dashoffset:-942; } }
.dg-stage circle{ fill:var(--panel); stroke:var(--line); stroke-width:1.5; }
.dg-stage text{ text-anchor:middle; fill:var(--ink); font-family:var(--mono); font-size:calc(var(--t) * 1px); }
.dg-stage-lbl text{ text-anchor:middle; }
.dg-spark{ fill:none; stroke:var(--accent); stroke-width:2; stroke-linejoin:round; }
.dg-alert{ fill:var(--amber); animation:dg-pulse 2.8s var(--ease-out) infinite; }
@keyframes dg-pulse{ 0%,60%{ opacity:.35 } 70%{ opacity:1 } 85%,100%{ opacity:.35 } }
.dg-feed{ stroke-dasharray:5 7; animation:dg-flow 1.6s linear infinite; }
@keyframes dg-flow{ to{ stroke-dashoffset:-24; } }

/* ---------- MOD.04 layout ---------- */
.dg-dots circle{ fill:var(--faint); }
.dg-blocks rect{
  fill:var(--media-bg); stroke:var(--line); stroke-width:1;
  opacity:0; transform:translateY(8px);
  animation:dg-in .6s var(--ease-out) forwards;
  animation-delay:calc(var(--i,0) * 130ms + 200ms);
}
.dg-blocks rect.dg-fill{ fill:var(--accent); stroke:var(--accent); }
@keyframes dg-in{ to{ opacity:1; transform:none; } }
.dg-reflow{ stroke-dasharray:5 7; animation:dg-flow 1.6s linear infinite; }

/* ---------- motion off ----------
   The MOD.04 blocks rest at opacity:0 and are brought in by the animation, so
   this must set the end state rather than simply cancelling it — otherwise
   reduced motion means an empty frame. */
@media (prefers-reduced-motion:reduce){
  .dg-leds circle,.dg-loop-run,.dg-alert,.dg-feed,.dg-reflow{ animation:none; }
  .dg-leds circle{ opacity:.55; }
  .dg-alert{ opacity:1; }
  .dg-blocks rect{ animation:none; opacity:1; transform:none; }
  .dg-token{ display:none; }        /* SMIL animateMotion is not CSS-cancellable */
}

/* ==========================================================================
   SYS.04 "Why C2C Infra" — four schematics, one per argument.

   Same rules as the SYS.02 drawings above: every colour is set HERE and never
   as an SVG presentation attribute, because var() does not resolve in one.

   TWO THINGS THAT ARE EASY TO BREAK
   1. --t is the label size in USER UNITS, and the SVG is scaled to its
      container, so a 12-unit label renders at 12 x (panel width / 520). At
      520px wide that is 12px; in a 306px-wide phone panel it is 7px. The
      media-query ladder at the foot of this block keeps the RENDERED size
      near 11px everywhere. Every text rule below is a multiple of --t, so
      retuning a breakpoint is one number, not fifteen.
   2. The draw-in reads --len off each [data-draw] element, written by
      why.js from getTotalLength(). The fallback in var(--len,640) only has
      to be LONGER than the longest path here (the 532-unit step profile);
      if JS never runs, the stroke is fully hidden until .is-in lands, which
      is exactly what a too-short fallback would break.
   ========================================================================== */

/* The cap is what makes the ladder below short enough to reason about: past
   620px the drawing stops growing, so every panel wider than that renders at
   exactly one scale (1.19) and needs exactly one label size. */
.wyg{ --t:12; max-width:620px; margin-inline:auto; font-family:var(--mono); }
.wyg text{ letter-spacing:.08em; }

/* ---------- entrances ----------
   Strokes trace themselves; everything else fades up. Both are keyed off
   .is-in on the grid (why.js), and both carry the panel's own --i so the
   four diagrams arrive in sequence rather than all at once. */
.wyg [data-draw]{
  stroke-dasharray:var(--len,640); stroke-dashoffset:var(--len,640);
  transition:stroke-dashoffset .95s var(--ease-out);
  transition-delay:calc(var(--d,0ms) + var(--i,0) * 150ms);
}
.why-grid.is-in .wyg [data-draw]{ stroke-dashoffset:0; }

.wyg text,
.wy-src circle, .wy-who, .wy-halo,
.wy-chips rect, .wy-chips circle,
.wy-slabs rect,
.wy-term, .wy-band, .wy-now, .wy-ax, .wy-bus-run{
  opacity:0;
  transition:opacity .7s var(--ease-out);
  transition-delay:calc(var(--d,0ms) + var(--i,0) * 150ms);
}
.why-grid.is-in .wyg text,
.why-grid.is-in .wy-src circle, .why-grid.is-in .wy-who, .why-grid.is-in .wy-halo,
.why-grid.is-in .wy-chips rect, .why-grid.is-in .wy-chips circle,
.why-grid.is-in .wy-slabs rect,
.why-grid.is-in .wy-term, .why-grid.is-in .wy-band,
.why-grid.is-in .wy-now, .why-grid.is-in .wy-ax, .why-grid.is-in .wy-bus-run{ opacity:1; }

/* Per-element offsets, so each drawing assembles in the order its argument
   is read rather than all at once. Resting opacity is carried by
   fill-opacity / stroke-opacity, never by opacity — that property is spoken
   for by the entrance above. */
.wy-src circle:nth-of-type(2){ --d:60ms }  .wy-src circle:nth-of-type(3){ --d:120ms }
.wy-src circle:nth-of-type(4){ --d:180ms } .wy-src circle:nth-of-type(5){ --d:240ms }
.wy-stems line:nth-child(2){ --d:60ms }  .wy-stems line:nth-child(3){ --d:120ms }
.wy-stems line:nth-child(4){ --d:180ms } .wy-stems line:nth-child(5){ --d:240ms }
.wy-bus{ --d:340ms } .wy-bus-run{ --d:1300ms } .wy-drop{ --d:900ms }
.wy-term,.wy-term-t{ --d:1100ms }
.wy-spokes path:nth-child(1){ --d:380ms } .wy-spokes path:nth-child(2){ --d:470ms }
.wy-spokes path:nth-child(3){ --d:560ms } .wy-spokes path:nth-child(4){ --d:650ms }
.wy-chips g,.wy-chips rect,.wy-chips circle,.wy-chips text{ --d:700ms }
.wy-slabs g{ --d:calc(180ms + var(--s,0) * 90ms) }
.wy-steps{ --d:260ms } .wy-ceil{ --d:900ms }
.wy-band,.wy-now{ --d:1150ms }

/* ---------- WHY.01 one backbone ---------- */
.wy-src circle{ fill:var(--accent); }
.wy-src text{ fill:var(--muted); font-size:calc(var(--t) * .86px); text-anchor:middle; }
.wy-stems line{ stroke:var(--line); stroke-width:1; }
.wy-bus{ stroke:var(--accent); stroke-width:2.5; stroke-linecap:round; stroke-opacity:.45; }
/* The one ambient motion in this section: a short pulse running the length of
   the backbone. "The line is live" is the argument, so it moves at rest. */
.wy-bus-run{
  stroke:var(--accent); stroke-width:2.5; stroke-linecap:round;
  stroke-dasharray:26 390; stroke-dashoffset:416;
  animation:wy-run 3.4s linear infinite 1.4s;
}
@keyframes wy-run{ to{ stroke-dashoffset:0; } }
.wy-drop{ stroke:var(--accent); stroke-width:1.5; stroke-opacity:.6; }
.wy-term{ fill:var(--panel); stroke:var(--accent); stroke-width:1.5; }
.wy-term-t{ fill:var(--ink); font-size:calc(var(--t) * .88px); text-anchor:middle; }
.wy-note{ fill:var(--faint); font-size:calc(var(--t) * .86px); letter-spacing:.1em; }
.wy-note-e{ text-anchor:end; }

/* ---------- WHY.02 one identity ---------- */
.wy-halo{ fill:none; stroke:var(--accent); stroke-opacity:.16; stroke-width:1; stroke-dasharray:3 6; }
.wy-ring{ fill:none; stroke:var(--accent); stroke-width:1.5; }
.wy-who circle{ fill:var(--accent); }
.wy-who path{ fill:none; stroke:var(--accent); stroke-width:2.5; stroke-linecap:round; }
.wy-core-t{ fill:var(--ink); font-size:calc(var(--t) * .92px); text-anchor:middle; letter-spacing:.16em; }
/* --line is the right weight for a border and far too faint for a line that
   has to be followed across 190 units of empty ground. */
.wy-spokes path{ fill:none; stroke:var(--accent); stroke-opacity:.3; stroke-width:1.5; }
.wy-chips rect{ fill:var(--panel); stroke:var(--line); stroke-width:1; }
.wy-chips circle{ fill:var(--accent); }
.wy-chips text{ fill:var(--muted); font-size:calc(var(--t) * .95px); letter-spacing:.14em; }

/* ---------- WHY.03 what ships with it ---------- */
.wy-col-h{ fill:var(--amber); font-size:calc(var(--t) * .86px); letter-spacing:.14em; }
.wy-split{ stroke:var(--line); stroke-width:1; stroke-dasharray:2 5; }
.wy-slabs rect{ fill:var(--panel); stroke:var(--line); stroke-width:1; }
.wy-slabs .wy-edge{ fill:var(--accent); stroke:none; }
.wy-slabs text{ fill:var(--ink); font-size:calc(var(--t) * .82px); letter-spacing:.06em; }

/* ---------- WHY.04 headroom ---------- */
.wy-band{ fill:var(--accent); fill-opacity:.14; stroke:none; transition:fill-opacity .45s var(--ease-out); }
/* The axes are the only thing that says "this is a plot", so they have to be
   readable — at .5 on top of --faint's own .5 alpha they rendered at a
   quarter strength and the schematic lost its frame. */
.wy-ax{ stroke:var(--faint); stroke-opacity:.85; stroke-width:1; }
.wy-ax-t{ fill:var(--faint); font-size:calc(var(--t) * .86px); letter-spacing:.16em; text-anchor:middle; }
.wy-ax-e{ text-anchor:end; }
.wy-steps{ fill:none; stroke:var(--accent); stroke-width:2.5; stroke-linejoin:round; stroke-linecap:round; }
.wy-ceil{ stroke:var(--accent); stroke-width:1.5; stroke-opacity:.55; }
.wy-now{ fill:var(--accent); stroke:var(--media-bg); stroke-width:2; }

/* ---------- hover ----------
   Motion is spent on hover rather than left running: four animated panels at
   rest would make this the noisiest section on the page, and none of it would
   be telling the reader anything they had asked for.

   The dashed-flow trick relies on a whole number of dash periods: with
   dasharray 6 10 the period is 16, so animating the offset by exactly -16
   lands where it started and the stroke returns to solid the moment the
   pointer leaves. Change one number and you must change the other. */
@media (hover:hover){
  .wy:hover .wy-bus-run{ animation-duration:1.5s; animation-delay:0s; }
  .wy:hover .wy-spokes path,
  .wy:hover .wy-steps{ stroke-dasharray:6 10; animation:wy-flow 1s linear infinite; }
  .wy:hover .wy-spokes path{ stroke:var(--accent); stroke-opacity:.8; }
  /* The stack fans, deepest first — the same gesture as squaring off a pile
     of paper, which is exactly what the right-hand column is. */
  .wy:hover .wy-slabs g{
    transform:translateX(5px);
    transition:transform .5s var(--ease-out) calc(var(--s,0) * 45ms);
  }
  .wy:hover .wy-band{ fill-opacity:.24; }
  .wy:hover .wy-now{ animation:wy-ping 1.6s var(--ease-out) infinite; }
}
@keyframes wy-flow{ to{ stroke-dashoffset:-16; } }
@keyframes wy-ping{ 0%,70%{ r:4.5 } 82%{ r:7 } 100%{ r:4.5 } }
.wy-slabs g{ transition:transform .5s var(--ease-out); }

/* ---------- label size ladder ----------
   The numbers are rendered pixels measured in the browser, not guesses: the
   SVG is 520 user units wide whatever the panel is, so the label size has to
   climb as the panel narrows or the diagrams turn into grey texture. */
@media (max-width:1360px){ .wyg{ --t:13; } }
@media (max-width:1220px){ .wyg{ --t:15; } }
@media (max-width:1080px){ .wyg{ --t:17; } }
@media (max-width:940px) { .wyg{ --t:19; } }
@media (max-width:860px) { .wyg{ --t:12; } }   /* one column: the panels are
                                                  wide again, so the ladder
                                                  restarts from the top here */
@media (max-width:640px) { .wyg{ --t:15; } }
@media (max-width:500px) { .wyg{ --t:19; } }
@media (max-width:400px) { .wyg{ --t:21; } }
/* One more step for the narrowest phones. .wy-slabs carries the smallest
   multiplier in this set (.82), so it is the first label to fall under the
   11px floor as the drawing shrinks -- measured at 10px on a 320 viewport
   while every other label was still clear. 24 puts it back to 11.4px. */
@media (max-width:340px) { .wyg{ --t:24; } }

/* ---------- motion off ----------
   Every element here rests hidden and is brought in by a transition, so this
   has to assert the END state rather than simply cancel the motion —
   cancelling alone would leave four empty wells. */
@media (prefers-reduced-motion:reduce){
  .wyg [data-draw]{ transition:none; stroke-dasharray:none; stroke-dashoffset:0; }
  .wyg text,
  .wy-src circle, .wy-who, .wy-halo,
  .wy-chips rect, .wy-chips circle,
  .wy-slabs rect,
  .wy-term, .wy-band, .wy-now, .wy-ax, .wy-bus-run{ opacity:1; transition:none; }
  .wy-bus-run{ animation:none; stroke-dashoffset:0; stroke-dasharray:none; stroke-opacity:0; }
  .wy-slabs g{ transition:none; }
}
