/* ===== Line Icons — case study =====
   Step 1: the skeleton. Sections, headings, vertical rhythm. The contents of
   02–05 land in later steps (see ICONOGRAPHY_CASE.md section 8).

   LAYOUT IS TRANSFERRED FROM FIGMA — file WcR5jHxWvkB0Cvvtmdet7B, frame
   `Iconography — Dark - final`, node 2038:3563, 1440 × 4817.

   Every colour and every measurement comes from a --ico-* token in
   css/base/tokens.css. That is rule 5 of the spec, and it is why this sheet
   has almost no literal numbers in it: the ones that remain are structural
   (0, 1fr, 50%), not design decisions.

   Dark only, by design — no [data-theme] hooks anywhere. */

/* Inter, self-hosted rather than linked from Google Fonts.
   Rule 4 of the spec is "no external dependencies", and a webfont request is
   one: it would make first paint depend on a third party the rest of this
   site never calls. The file is Google's *variable* Inter — the 400/500/600
   URLs all resolve to the same woff2 — so one file per subset covers the
   three weights the design uses, declared as a range. SIL OFL 1.1. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../../assets/fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../../assets/fonts/inter-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ---------------------------------------------------------------- case -- */
/* ONE dark block inside a themed page. Before the integration this class sat
   on <body> and owned the whole document; now the shell around it (sticky
   topbar, nav dock) is themed like the rest of the site and .ico is
   just the article between them. That is what lets typography.css back in:
   it paints the BODY from the site's roles, which is correct for the shell,
   and everything the case needs differently is set here for its own subtree.

   z-index is declared rather than left to chance — the site keeps one
   documented stacking scale, and the fixed nav dock (--z-nav 100) has to win
   over everything in here, ribbons included. */
.ico {
  position: relative;
  z-index: var(--z-content);
  font-family: var(--ico-font);
  font-weight: var(--ico-fw-regular);
  color: var(--ico-text);
  /* The site's body line-height is 1.6 (prose). Every text box in the source
     frame is 1.2 — 11px sets a 13 line, 16px a 19, 10px a 12 — and the
     layout is built on those numbers: the count pill in section 03 is 18
     tall precisely because 12 + 2×3 = 18. Inheriting 1.6 silently inflates
     every one of them, so the case resets it at the root rather than
     patching each rule. */
  line-height: var(--ico-lh-tight);
  /* Long, dense page: keep the icons crisp rather than letting the browser
     hint them into uneven weights. */
  -webkit-font-smoothing: antialiased;
  /* Run-out space at the end of the page. The closing footer used to hold it,
     along with the clearance for the fixed nav dock; with the footer gone the
     page holds it itself, or section 05 ends flush under the dock. Named off
     --dock-space rather than typed as 80px, because that is what the number
     is for — the same run-out .case carries on the Yumoji page. */
  padding-bottom: var(--dock-space);
}

/* .ico__col is gone. Sections put their content in the site's own
   `.container` (72rem cap, 24 gutter → 1104 of content at the cap), so the
   case measures the same as every other page instead of carrying a 1200
   column of its own. Everything below that used to be sized against 1200 has
   been re-fitted to 1104 — see the notes on section 02, 03 and 05.

   The SECTION stays full-bleed: its dark ground runs edge to edge and only
   the content is held to the container. That is what makes the case read as
   one block rather than as a dark card floating on the page. */

/* The shared sticky topbar (css/sections/case.css) hard-codes case.css's own
   120px page padding, which would not line up with .container — and outside
   `.case` that variable is not even defined, which invalidates the whole
   shorthand and leaves the bar with no padding at all. Both problems go away
   by handing the horizontal inset to a .container inside it. */
.ico-topbar { padding: var(--space-4) 0; }
.ico-topbar__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}

/* ------------------------------------------------------------ sections -- */
/* Each section carries its measured height from the Figma frame. They are
   min-heights, not heights: when a later step puts real content in, content
   that overflows should push the page rather than silently clip. The step-1
   check is that the five still sum to 4817. */
/* No background. The frame painted every section #131312 and the page
   #0E0E0D behind them, and both now resolve to --surface-page — the same
   colour the body already has. Painting it again would be a no-op except for
   one thing: it is opaque, and it hid .dot-grid and the shade the visitor
   picked under an unbroken slab for the whole length of the case. Removing it
   is what makes the page finally sit ON the site rather than on top of it.

   The ribbon fade is unaffected and was the reason to be careful here: it is
   a mask, so it takes the content's alpha down to whatever is behind — the
   grid included — rather than blending toward a colour it has to be told. */
.ico__section {
  padding-top: var(--ico-sp-section);
}

.ico__section--hero       { min-height: var(--ico-h-hero); }
.ico__section--system     { min-height: var(--ico-h-system); }
.ico__section--categories { min-height: var(--ico-h-categories); }
.ico__section--grid       { min-height: var(--ico-h-grid); }
.ico__section--inuse      { min-height: var(--ico-h-inuse); }

/* --------------------------------------------------------------- type -- */
/* The case follows the system's rule of two roles (DESIGN_SYSTEM.md, «Правило
   ролей»): --font-display for headings, --font-body for everything else, with
   --font-ui as the third family for dense interface and numeric text.

   The site's own rule is the element selector `h1, h2, h3` in typography.css,
   and it is too broad for this subtree. Two of those three are headings here
   and one is not: .ico__h1 and .ico__h2 are the case's section titles, while
   every h3 in here is a LABEL — the uppercase category names in section 03
   and "Optical compensation" in 02, both 12.8px and tracked. Putting a serif
   on those would be reading the rule off the tag instead of off the role.

   So the element rule is cancelled across the subtree, and the display face
   is named back on the two classes that are actually headings. Everything
   else keeps --font-ui: the page has no prose at all — its non-heading text
   is category counts, dimension callouts, spec values and captions, which is
   precisely the role that token exists for. */
.ico :is(h1, h2, h3, h4) { font-family: inherit; }

/* `.ico` is carried on both selectors purely for weight. The reset above is
   (0,1,1) — one class and one element — and a bare `.ico__h1` is (0,1,0), so
   the reset wins and the display face silently does nothing. Same trap
   `.case__grid .case__card .case__subtitle` documents in case.css. */
.ico .ico__h1,
.ico .ico__h2 {
  font-family: var(--font-display);
  /* 600 on Georgia, the same weight the Yumoji case sets on its own section
     titles (.case__section-title) — so both case pages carry one heading. */
  font-weight: var(--ico-fw-semibold);
  line-height: var(--ico-lh-tight);
  letter-spacing: normal;
  color: var(--ico-text);
}
.ico__h1 { font-size: var(--ico-fs-h1); }
.ico__h2 { font-size: var(--ico-fs-h2); }

/* Heading → content, everywhere except the Hero, which has its own larger
   gap (114) because the three big icons need air the H2 sections don't. */
.ico__body { margin-top: var(--ico-sp-title); }

/* The one section that does not use the 40: its frame places the cluster
   block 63 below the H2. See --ico-cat-title-gap. */
.ico__section--categories .ico__body { margin-top: var(--ico-cat-title-gap); }

/* --------------------------------------------------------------- hero -- */
.ico__section--hero .ico__body { margin-top: var(--ico-hero-gap-title); }

/* The row of three 160px icons, centres at 208 / 600 / 992. None of the bare
   flex distributions lands there — space-between alone gives 80 / 600 / 1120
   — but an inset of 128 plus space-between does, exactly. */
.ico__hero-row {
  height: var(--ico-hero-icon);
  display: flex;
  justify-content: space-between;
  padding-inline: var(--ico-hero-inset);
}

/* One morph slot. Both layers are stacked and both stay in the DOM for the
   life of the page: step 7 cross-fades between them by swapping which one is
   current, never by rebuilding nodes. Statically, layer 0 is the visible one
   and layer 1 is the standby, already holding the next icon. */
.ico__morph {
  position: relative;
  width: var(--ico-hero-icon);
  height: var(--ico-hero-icon);
}
.ico__morph-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  color: var(--ico-icon);
  opacity: 0;
}
.ico__morph-layer[data-current] { opacity: 1; }

/* The cross-fade, 420ms (spec 9.1), using the "spin" transform from the
   icon-transition prototype rather than the plain scale the spec sketched.

   These are animations rather than transitions on purpose. A transition
   would leave the outgoing layer parked at scale 1.06 and it would then have
   to be reset to .94 for its next turn — a reset that is itself animatable,
   so it needs transitions disabled and a forced reflow to hide. An animation
   just ends and hands the element back to its base rule: the incoming layer
   lands on opacity 1 because it now carries [data-current], the outgoing on
   opacity 0 because it no longer does. Nothing snaps, and there is no
   reflow trick to explain later. */
@keyframes ico-morph-in {
  from { opacity: 0; transform: var(--ico-morph-enter-from); }
  to   { opacity: 1; transform: none; }
}
@keyframes ico-morph-out {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: var(--ico-morph-leave-to); }
}
.ico__morph-layer.is-entering {
  animation: ico-morph-in var(--ico-morph-dur) var(--ico-morph-ease);
}
.ico__morph-layer.is-leaving {
  animation: ico-morph-out var(--ico-morph-dur) var(--ico-morph-ease);
}

/* The ribbon panel closes the section: it is flush with the bottom edge,
   which is why .ico__section--hero has no bottom padding to cancel.

   No background. The frame's panel carries a #1C1C1A fill but it is switched
   OFF, so the ribbons sit straight on the section colour — which is also what
   makes the edge fade work, since it resolves to whatever is behind rather
   than to a panel of its own. The radius and the clip stay: the frame clips
   its rows to rounded corners even with the fill hidden. */
.ico__hero-panel {
  margin-top: var(--ico-hero-gap-row);
  height: var(--ico-hero-panel-h);
  border-radius: var(--ico-radius);
  overflow: hidden;
}

/* Three rows, 40 tall, 32 apart, centred → 88 / 160 / 232 in the 360 panel. */
.ico__ribbons {
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--ico-ribbon-gap);
  /* A mask, not an overlay: it works over any ground and survives a change of
     one — including the themed page and the dot grid now showing through it,
     which an opaque gradient sitting on top could not do at all.

     The #000 in the stops is not a colour and not a hardcode: a mask reads
     only alpha, so the stop means "opaque here", and `transparent` means
     "gone". Any solid colour would render identically. */
  -webkit-mask-image: linear-gradient(90deg, transparent 0,
                      #000 var(--ico-mask-edge),
                      #000 calc(100% - var(--ico-mask-edge)), transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0,
                      #000 var(--ico-mask-edge),
                      #000 calc(100% - var(--ico-mask-edge)), transparent 100%);
}

.ico__ribbon {
  height: var(--ico-ribbon-icon);
  overflow: hidden;    /* the track is far wider than the panel */
}

/* The track holds its icon list TWICE, so one period is exactly half its
   width and the loop runs to -50% with nothing to see at the seam. That only
   holds because each cell is a whole 72 INCLUDING its trailing gap — with a
   flex `gap` the last icon would have no gap after it and the loop would
   jump by 32 every cycle.

   Transform only, linear, infinite (spec 9.2). Nothing here touches `left`,
   `margin` or `background-position`, and the container's opacity is never
   animated — the edges are the mask's job, not a fading overlay's. */
@keyframes ico-ribbon-left {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}
@keyframes ico-ribbon-right {
  from { transform: translate3d(-50%, 0, 0); }
  to   { transform: translate3d(0, 0, 0); }
}

.ico__ribbon-track {
  display: flex;
  width: max-content;
  animation-duration: var(--ico-ribbon-dur);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  /* A negative delay starts the loop already in progress. It is how the three
     rows are phase-shifted apart without three sets of keyframes, and it is
     what puts the first painted frame where the mockup has it. */
  animation-delay: var(--ico-ribbon-delay, 0s);
  /* Spec 10. The track is 4032 wide and moves for the life of the page, so
     it is worth its own layer; without this the browser re-rasterises a very
     wide box on every frame. It is set once here rather than toggled from JS
     — toggling it is the documented way to make it useless. */
  will-change: transform;
}

/* Off-screen, both directions of travel stop where they are. `paused` keeps
   the animation's own clock, so scrolling back does not restart the loop or
   jump it — the fourth pre-flight check in spec 10. */
.ico__ribbons.is-paused .ico__ribbon-track { animation-play-state: paused; }
.ico__ribbon-track[data-direction="left"]  { animation-name: ico-ribbon-left; }
.ico__ribbon-track[data-direction="right"] { animation-name: ico-ribbon-right; }

.ico__ribbon-icon {
  width: var(--ico-ribbon-icon);
  height: var(--ico-ribbon-icon);
  margin-right: var(--ico-ribbon-gap);
  flex: none;
  color: var(--ico-icon);
}

/* -------------------------------------------------------------- panels -- */
/* The shared surface for the ribbon panel, the category clusters and the
   full-set grid. Only the padding differs, and it differs through the token
   rather than through an override, so a cluster is `.ico__panel` with one
   custom property changed rather than a second copy of the surface. */
.ico__panel {
  background: var(--ico-bg-panel);
  border-radius: var(--ico-radius);
  padding: var(--ico-panel-pad);
}

/* ------------------------------------------------------- 04 full set --- */
/* 15 equal columns across the panel's 1120 of content, each icon centred in
   its cell. The column gap is implicit: `1fr` columns already distribute the
   slack, so adding one would double-count it and pull the last column in.

   No surface of its own — the frame has this panel's fill switched off, the
   same as the Hero's ribbon panel, so the grid sits on the section colour.
   It keeps the padding, which is what insets the icons from the column. */
.ico__grid {
  padding: var(--ico-panel-pad);
  display: grid;
  grid-template-columns: repeat(var(--ico-grid-cols), 1fr);
  row-gap: var(--ico-grid-row-gap);
  justify-items: center;
}

.ico__grid-icon {
  width: var(--ico-grid-icon);
  height: var(--ico-grid-icon);
  color: var(--ico-icon);   /* the sprite paints with currentColor */
}

/* ----------------------------------------------------- 03 categories --- */
/* Two rows of plaques. The plaques keep the frame's own widths — those are
   what make the icons wrap 12 / 7 / 5 / 5 / 8 across — and the row justifies
   them over the 1200 column, which is the one place this section departs from
   the frame: the gutter lands on 12 / 13.5 rather than 16, because the
   frame's block is 4-5px wider than the column it sits in. See tokens.css.

   The row height is fixed rather than hugged. That is what feeds the icon
   area below: it gets the leftover and spreads its rows into it, which is
   where the frame's 16 and 12 row gaps come from without either being typed. */
.ico__clusters {
  display: flex;
  flex-direction: column;
  gap: var(--ico-cat-rows-gap);
}

.ico__cluster-row {
  display: flex;
  gap: var(--ico-cat-rows-gap);
  align-items: stretch;
}
/* A plaque is `.ico__panel` with one token changed (32 here, 40 on the
   full-set grid), not a second copy of the surface.

   The frame's plaque widths were 725 + 463 and 332 + 332 + 509 — a total of
   1188, which needed the old 1200 column. In the site container there is
   1104, so the widths are now RATIOS rather than pixels: `flex-grow` set to
   the frame's numbers keeps the proportions exactly and lets the row fill
   whatever it is given.

   That degrades cleanly, which is the reason it is safe to do: at 1104 the
   icons wrap 10 / 6 in the first row and 4 / 4 / 7 in the second, so every
   plaque in a row still ends on the same number of rows and the row still
   comes out level. */
.ico__cluster {
  --ico-panel-pad: var(--ico-cat-pad);
  flex: var(--ico-cat-w, 1) 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--ico-cat-body-gap);
}

.ico__cluster--navigation { --ico-cat-w: var(--ico-cat-w-navigation); }
.ico__cluster--finance    { --ico-cat-w: var(--ico-cat-w-finance); }
.ico__cluster--wellbeing  { --ico-cat-w: var(--ico-cat-w-wellbeing); }
.ico__cluster--status     { --ico-cat-w: var(--ico-cat-w-status); }
.ico__cluster--product    { --ico-cat-w: var(--ico-cat-w-product); }

/* index → name → count, all on one 16-tall row. The height is set rather
   than left to the content because the three items have different line
   boxes (12 / 13 / 16) and the pill is what defines the row. */
.ico__cluster-head {
  display: flex;
  align-items: center;
  gap: var(--ico-cat-head-gap);
  height: var(--ico-cat-head-h);
}

.ico__cluster-index {
  font-size: var(--ico-fs-micro);
  font-weight: var(--ico-fw-medium);
  letter-spacing: var(--ico-ls-micro);
  color: var(--ico-text-muted);
}

.ico__cluster-name {
  font-size: var(--ico-fs-label);
  font-weight: var(--ico-fw-medium);
  letter-spacing: var(--ico-ls-caps);
  color: var(--ico-text);
  text-transform: uppercase;
}

/* The pill takes the header row's own height rather than a vertical padding:
   the frame's 2px is on no scale, and pinning the height to the row is what
   the 2 was expressing anyway. */
.ico__cluster-count {
  display: inline-flex;
  align-items: center;
  height: var(--ico-cat-head-h);
  padding-inline: var(--ico-pill-pad-x);
  font-size: var(--ico-fs-micro);
  font-weight: var(--ico-fw-medium);
  letter-spacing: var(--ico-ls-micro);
  color: var(--ico-text-muted);
  background: var(--ico-bg-section);
  border-radius: var(--ico-radius-pill);
}

/* Icons flow and wrap at a fixed gap, centred — so a short last row sits
   under the middle of the plaque instead of hanging off its left edge, which
   is what the frame does in all five. Vertically they take the leftover
   height and spread into it. */
.ico__cluster-icons {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  /* The frame distributed the row gap inside a fixed plaque height; without
     that height there is no slack to distribute, so the gap is stated. */
  align-content: flex-start;
  align-items: center;
  column-gap: var(--ico-cat-col-gap);
  row-gap: var(--ico-cat-row-gap);
}

.ico__cluster-icon {
  flex: 0 0 auto;
  width: var(--ico-cat-icon);
  height: var(--ico-cat-icon);
  color: var(--ico-icon);
}

/* ------------------------------------------------------- 02 anatomy ---- */
/* Artboard and spec column side by side, both vertically centred. The
   centring is load-bearing rather than cosmetic: it is what puts the
   artboard at y=60 and the spec column at y=98 inside the 520 block, which
   is where the source frame has them. The left inset leaves room for the
   vertical callout and its label, which live outside the artboard box. */
.ico__anatomy {
  height: var(--ico-anatomy-h);
  padding-left: var(--ico-anatomy-inset);
  display: flex;
  align-items: center;
  gap: var(--ico-anatomy-gap);
}

/* --- the artboard ------------------------------------------------------ */
/* No radius, on purpose: it is a drawing surface, not a card. The callouts
   are positioned against it and deliberately overflow it. */
.ico__artboard {
  position: relative;
  flex: none;
  width: var(--ico-artboard);
  height: var(--ico-artboard);
  background: var(--ico-bg-artboard);
}

.ico__artboard-icon {
  width: 100%;
  height: 100%;
  color: var(--ico-icon);
}

/* The grid and the optical box are one SVG laid over the icon — the grid
   reads on top of the drawing in the source, which is the whole point of
   the illustration. */
.ico__artboard-grid {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
/* Stroke lives here rather than on the elements: SVG presentation attributes
   do not resolve var(), so tokens have to reach the shapes through CSS. */
.ico__artboard-lines line {
  stroke: var(--ico-line-grid);
  stroke-width: var(--ico-grid-line);
}
.ico__optical-box {
  fill: none;
  stroke: var(--ico-line-dim);
  stroke-width: var(--ico-grid-line);
  stroke-dasharray: var(--ico-dash);
}

/* --- dimension callouts ------------------------------------------------ */
.ico__dim {
  position: absolute;
  background: var(--ico-line-dim);
}
.ico__dim-label {
  position: absolute;
  font-size: var(--ico-fs-label);
  font-weight: var(--ico-fw-medium);
  color: var(--ico-text);      /* the labels read at full strength; only the
                                  rules and ticks are dimmed */
  white-space: nowrap;
}

/* vertical: full height, 40 to the left, label a further 10 out */
.ico__dim--v      { left: calc(var(--ico-dim-left) * -1); top: 0;
                    width: var(--ico-grid-line); height: 100%; }
.ico__dim--v-tick { left: calc((var(--ico-dim-left) + (var(--ico-dim-tick) - var(--ico-grid-line)) / 2) * -1);
                    width: var(--ico-dim-tick); height: var(--ico-grid-line); }
.ico__dim--v-top    { top: 0; }
.ico__dim--v-bottom { bottom: 0; }
.ico__dim-label--v  { top: 50%; transform: translateY(-50%);
                      right: calc(100% + var(--ico-dim-left) + var(--ico-dim-label-gap)); }

/* horizontal: full width, 24 below, label centred a further 10 underneath */
.ico__dim--h      { left: 0; top: calc(100% + var(--ico-dim-below));
                    width: 100%; height: var(--ico-grid-line); }
.ico__dim--h-tick { top: calc(100% + var(--ico-dim-below) - (var(--ico-dim-tick) - var(--ico-grid-line)) / 2);
                    width: var(--ico-grid-line); height: var(--ico-dim-tick); }
.ico__dim--h-left  { left: 0; }
.ico__dim--h-right { right: 0; }
.ico__dim-label--h { left: 50%;
                     top: calc(100% + var(--ico-dim-below) + var(--ico-dim-label-gap-h));
                     transform: translateX(-50%); }

/* --- the spec column --------------------------------------------------- */
/* Placed rather than centred — see the note on --ico-spec-top.
   `flex: 1 1 <w>` rather than a fixed width: the frame's 120 + 400 + 96 + 528
   comes to 1144 and the container gives 1104, so this column takes whatever
   is left of the row instead of overflowing it by 40. */
.ico__spec {
  flex: 1 1 var(--ico-spec-w);
  min-width: 0;
  align-self: flex-start;
  margin-top: var(--ico-spec-top);
}

.ico__params {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  column-gap: var(--ico-param-col-gap);
  row-gap: var(--ico-param-row-gap);
}
.ico__param      { display: flex; flex-direction: column; gap: var(--ico-param-gap); }
.ico__param-label { font-size: var(--ico-fs-label); font-weight: var(--ico-fw-regular);
                    color: var(--ico-text-muted); }
.ico__param-value { font-size: var(--ico-fs-value); font-weight: var(--ico-fw-semibold);
                    color: var(--ico-text); }

.ico__divider {
  height: var(--ico-grid-line);
  background: var(--ico-line-divider);
  margin-block: var(--ico-divider-gap);
}

.ico__optical-title { font-size: var(--ico-fs-label); font-weight: var(--ico-fw-medium);
                      color: var(--ico-text); }
.ico__optical-note  { font-size: var(--ico-fs-label); font-weight: var(--ico-fw-regular);
                      color: var(--ico-text-muted); margin-top: var(--ico-param-gap); }

.ico__cards {
  margin-top: var(--ico-card-gap);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--ico-card-gap);
}
/* The hairline is an inset shadow rather than a border on purpose: the frame
   draws its stroke inside the 113-tall box, and a real border would add 2 to
   the card and push the whole spec column down. */
.ico__card {
  background: var(--ico-bg-card);
  box-shadow: inset 0 0 0 var(--ico-grid-line) var(--ico-line-divider);
  border-radius: var(--ico-radius-sm);
  padding: var(--ico-card-pad);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--ico-card-inner-gap);
}
.ico__card-icon  { width: var(--ico-card-icon); height: var(--ico-card-icon);
                   color: var(--ico-icon); }
.ico__card-value { font-size: var(--ico-fs-label); font-weight: var(--ico-fw-medium);
                   color: var(--ico-text); }
.ico__card-label { font-size: var(--ico-fs-micro); font-weight: var(--ico-fw-regular);
                   color: var(--ico-text-muted); }

/* --------------------------------------------------------- 05 in use --- */
/* Left-aligned in the column, not centred: the section's auto-layout aligns
   to the start, and the row is 1181.5 of the 1200. Tops aligned, so the two
   captions sit on one line even though the shots are different widths. */
.ico__shots {
  display: flex;
  align-items: flex-start;
  gap: var(--ico-shot-gap);
}

/* No card around the shot — no panel fill, no 16 padding. The frame carries
   the radius and the hairline directly on the image, and the caption hangs
   below it. Width comes from the image's own proportion at a fixed height,
   so a card is exactly as wide as its screenshot. */
.ico__shot { margin: 0; }

.ico__shot-frame {
  position: relative;
  border-radius: var(--ico-radius-img);
  overflow: hidden;
  width: fit-content;
}

/* The hairline is drawn over the image rather than around it: the frame
   strokes INSIDE, so a real border would push the shot 2px narrower and the
   two cards would stop matching their measured widths. */
.ico__shot-frame::after {
  content: "";
  position: absolute;
  inset: 0;
  border: var(--ico-grid-line) solid var(--ico-line-divider);
  border-radius: inherit;
  pointer-events: none;
}

.ico__shot-img {
  display: block;
  height: var(--ico-shot-h);
  width: auto;
}

.ico__shot-caption {
  margin-top: var(--ico-shot-caption);
  text-align: center;
  font-size: var(--ico-fs-label);
  font-weight: var(--ico-fw-medium);
  color: var(--ico-text);
}

/* ===== reduced motion (spec 10) ==========================================
   reset.css already clamps every animation to 0.01ms, which stops the
   ribbons — but it stops them wherever the clamp lands, and it does nothing
   at all about the morph, whose motion is a JS timer swapping two layers.
   So both halves are turned off explicitly: the tracks here, the timers in
   iconography.js. What stays on screen is the first icon of each list and
   three still ribbons, which is what the spec asks to be left. */
@media (prefers-reduced-motion: reduce) {
  /* The attribute selector is not decoration: the direction rules below set
     `animation-name` at [class][attr], so a plain `.ico__ribbon-track` here
     loses to them and the ribbon still runs — for the 0.01ms reset.css
     clamps it to, but it runs. Matching their specificity is what actually
     takes the animation off. */
  .ico__ribbon-track[data-direction] {
    animation: none;
    transform: none;
    will-change: auto;
  }
  .ico__morph-layer { animation: none; }
}

/* ===== breakpoints (spec 10) =============================================
   Sizes are not here — they are token overrides at the foot of tokens.css.
   What is here is the handful of things a token cannot say: which way a flex
   box runs, what wraps, and what is dropped entirely.

   THE UPPER BREAKPOINT IS 72em, NOT A PIXEL WIDTH, and that is deliberate.
   It is the site container's own cap (`--container: 72rem`): above it the
   content box is a constant 1104 whatever the window does, below it the
   container shrinks with the viewport. Writing it in `em` is what keeps the
   two tied — a visitor browsing at a 20px base font moves BOTH the container
   (rem) and this query (em) to the same place, which a px query would not.
   The 0.01 is only so the query stops just short of the cap itself. */

/* --- narrower than the container's cap ----------------------------------- */
@media (max-width: 71.99em) {
  /* Section 02. The block stops being a fixed 520 with two centred halves
     and becomes two columns that start at the top: the spec column's placed
     98 offset was a fact about the frame's height, and there is no frame
     height here any more. */
  .ico__anatomy {
    height: auto;
    align-items: flex-start;
  }
  .ico__spec { margin-top: 0; }

  /* Section 03 stops being a proportional row and becomes an even grid.
     Wrapping a flex row was the obvious move and it is wrong: a plaque that
     wraps onto a line of its own GROWS to fill it, so Product & People — the
     widest ratio — ended up 752 wide with fifteen icons across while its two
     siblings shared the line above. `auto-fit` gives even tracks and, because
     it only collapses tracks that are empty everywhere, a lone wrapped plaque
     stays one track wide while a row of two still fills the width. */
  .ico__cluster-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(var(--ico-cat-basis), 1fr));
  }

  /* Section 05. Height-driven sizing is what keeps the two captions on one
     line; below the container's cap there is not enough room for it, so the
     shots share the column instead and each keeps its own proportion. The
     captions then sit a few pixels apart — the two shots are 3036x3116 and
     3117x3117, so the taller one hangs 2.6% lower. */
  .ico__shots { align-items: flex-start; }
  .ico__shot  { flex: 1 1 0; min-width: 0; }
  .ico__shot-frame { width: 100%; }
  .ico__shot-img   { width: 100%; height: auto; }
}

/* --- below 768 -----------------------------------------------------------
   The site's own main breakpoint, in px like every other sheet here: this one
   is about the device, not about the container. */
@media (max-width: 767px) {
  /* Hero: two big slots, not three (spec 10). The third is hidden here and
     its timer is stopped in iconography.js — hiding it alone would leave a
     timer redrawing an invisible element for the life of the page. */
  .ico__morph:nth-child(3) { display: none; }

  /* Section 02 stacks. The artboard keeps its left inset because the
     dimension callouts live in it. */
  .ico__anatomy { flex-direction: column; }

  /* Section 05 stacks (spec 10). Each shot now has the full column, so it
     goes back to being width-driven with nothing to share. */
  .ico__shots { flex-direction: column; }
}
