@import url('assets/fonts.css');
@import url('assets/tokens.css');

/* ============================================================================
   The kit reference site: layout only, brand-neutral by construction.

   Every value here resolves to a --site-* variable, and brand.css is the one
   file that maps those onto the kit's own tokens. Keep this file free of brand
   names, hex values and font names, so the next pass starts from it unchanged.

   The page is read as a set of blocks on a grid. Content sits on a block; the
   block sits on the grid. Where the order needs to show, a hairline shows it.
   Nothing carries a radius, a shadow or a gradient unless the kit does: the
   geometry does the work and decoration on top reads as noise against it.
   ============================================================================ */

:root {
  /* The four heading sizes. Where the kit leaves them to the surface, the
     fallbacks below are the surface's own; where the kit sets them, brand.css
     supplies --site-text-h1..h3 and --site-text-eyebrow and those win. */
  --text-h1: var(--site-text-h1, clamp(36px, 4.4vw, 64px));
  --text-h2: var(--site-text-h2, clamp(24px, 2.4vw, 36px));
  --text-h3: var(--site-text-h3, 24px);
  --text-eyebrow: var(--site-text-eyebrow, 12px);

  /* The grid the page is read on. Six, so that halves, thirds and sixths are
     all lines on it: a row of two columns and a row of three both register
     against the same grid. Loose paragraphs take a third of it, so a paragraph
     above a row of columns lines up with the columns below it. */
  --gcols: 6;
  --gspan-loose: 2;
  /* The two measures the layout offers a run of prose. A lead introduces a
     section and takes the column; a note is an aside and takes the narrow one.
     Three sixths and two sixths of the measure land on the widths the layout
     sets for them. */
  --gspan-column: 3;

  /* The surface's own side margin. It is never narrower than the clear space of
     the largest logo on the page, and every block edge registers against it. */
  /* The page's side margin is the kit's block gap, not the page's halved
     vertical step: the reduction above is vertical, and narrowing the margins
     with it would crop the page rather than tighten it. */
  --margin: clamp(var(--site-space-x), 5vw, var(--gap-blocks));

  /* The rail holds fifteen section names, so it is sized to the longest of
     them rather than to the brand's own marcom rail. That rail marks progress
     through a handful of stops; this one is an index and has to be read. */
  --rail-w: var(--site-rail-index-w, 216px);

  /* The cover is a header, not a screen to be held on: it is short enough that
     the first section is already showing under it. The render is pinned to the
     foot of the band, so the height it loses comes off the top of the image. */
  --cover-h: 72svh;
  --tile: var(--site-tile, 80px);
  --rule: var(--site-rule-weight) solid var(--site-rule-color);

  /* The one interval between a label and the thing it names, wherever that
     pairing appears: a section head, a group, a cell, a nav group. The kit offers two gaps for elements that belong to one
     another, and this pairing takes the larger of them: on the smaller one the
     label crowds its own heading. Every rule below reads this rather than
     setting its own, so the relationship stays one decision. */
  --label-gap: var(--site-label-gap, var(--site-space-x));

  /* Motion. Read from brand.css, which reads them from the kit's own tokens;
     never a value typed here. */
  --ease-neutral:  var(--site-motion-ease-neutral);
  --ease-enter:    var(--site-motion-ease-enter);
  --ease-standard: var(--site-motion-ease-standard);
  --ease-exit:     var(--site-motion-ease-exit);

  --dur-react-in:  var(--site-motion-duration-react-in);
  --dur-react-out: var(--site-motion-duration-react-out);
  --dur-reveal:    var(--site-motion-duration-reveal);
  --dur-press:     var(--site-motion-duration-press);

  --lift-micro:  var(--site-motion-lift-micro);
  --lift-macro:  var(--site-motion-lift-macro);
  --lift-hero:   var(--site-motion-lift-hero);

  --stagger-simultaneous: var(--site-motion-stagger-simultaneous);
  --stagger-default:      var(--site-motion-stagger-default);
  --stagger-feature:      var(--site-motion-stagger-feature);
  --stagger-narrative:    var(--site-motion-stagger-narrative);
}

/* The variables below are brand decisions rather than layout ones, so this sheet
   reads them with a fallback instead of declaring them. brand.css is linked
   first, so a :root default here would beat it. Never add a :root default for
   a --site-* variable.

     --site-case-display   the display face's case. Some systems set caps.
     --site-feature-*      the surfaces that invert. Some systems reverse out
                           of their ink; some forbid a dark field and feature
                           on an approved tint instead.
     --site-text-h*        the heading sizes, where the kit sets them.
     --site-foot-bg/ink    the footer's field. Some systems close on the accent;
                           some forbid the accent as a field.
     --site-rail-index-w   the rail's width. This rail is an index and is sized
                           to the longest section name, not to the kit's own rail.
     --site-cta-*          the button's padding, size, tracking and radius.
     --site-grain          a texture laid over the feature surfaces, for a
                           system whose graphic language is material rather
                           than flat. Off unless the brand sets it. */

* { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: var(--site-space-2x);
}

/* The browser's own scrollbars are the one piece of chrome on the page the
   system does not draw, and on a page this long they cut a gray channel down
   the right edge of every band. They are hidden everywhere and the surfaces
   they served stay scrollable by wheel, trackpad, keyboard and touch. */
html, body, .rail__nav, .scroll {
  scrollbar-width: none;
  -ms-overflow-style: none;
}
html::-webkit-scrollbar,
body::-webkit-scrollbar,
.rail__nav::-webkit-scrollbar,
.scroll::-webkit-scrollbar { width: 0; height: 0; display: none; }

body {
  margin: 0;
  background: var(--site-bg);
  color: var(--site-ink);
  font-family: var(--site-font-body);
  font-weight: var(--site-weight-body);
  font-size: var(--site-text-p3);
  line-height: var(--site-leading-body);
  letter-spacing: var(--site-tracking-body);
  -webkit-font-smoothing: antialiased;
}

/* ── the five type styles ───────────────────────────────────────────────── */

.h1, .h2 {
  font-family: var(--site-font-display);
  font-weight: var(--site-weight-display);
  line-height: var(--site-leading-display);
  text-transform: var(--site-case-display, uppercase);
  margin: 0;
}
.h1 { font-size: var(--text-h1); letter-spacing: var(--site-tracking-h1); }
.h2 { font-size: var(--text-h2); letter-spacing: var(--site-tracking-h2); }

.h3 {
  font-family: var(--site-font-body);
  font-weight: var(--site-weight-body-bold);
  font-size: var(--text-h3);
  line-height: var(--site-leading-display);
  letter-spacing: var(--site-tracking-h3);
  margin: 0;
}

.eyebrow {
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  line-height: var(--site-leading-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  /* The label style is the same family and nearly the same size as the prose
     beside it, so case and tracking alone are not enough to separate them.
     The accent carries the distinction, as it already does on a dark band. */
  color: var(--site-accent);
  margin: 0;
}

p { margin: 0; }
.lead { font-size: var(--site-text-p2); }
.note { font-size: var(--site-text-p3); color: var(--site-neutral-70); }
.statement { font-size: var(--site-text-p1); }
.statement--caps { text-transform: uppercase; }
.muted { color: var(--site-neutral-70); }

code {
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: 0.85em;
  letter-spacing: var(--site-tracking-eyebrow);
}

a { color: inherit; text-decoration: none; }
em, i { font-style: italic; }

/* ── the rail ───────────────────────────────────────────────────────────── */

/* The page's own chrome. It is the quietest surface here: no fill, no shadow,
   and a single hairline holding it off the work. The filled tile at its head is
   the one place the mark sits in a solid square, and it runs flush to the top
   and left of the page. */
.shell { display: flex; align-items: flex-start; }

.rail {
  position: sticky;
  top: 0;
  z-index: 40;
  width: var(--rail-w);
  flex: 0 0 var(--rail-w);
  height: 100vh;
  display: flex;
  flex-direction: column;
  padding-top: var(--site-rail-pad-block, var(--site-space-2x));
  background: var(--site-rail-bg, var(--site-bg));
  border-right: var(--rule);
  /* Square, deliberately. A rounded corner only reads as a rounded corner when
     the page's own ground sits behind it. This rail's neighbour is the cover's
     full-bleed render at the top of the scroll and a light band everywhere
     after it, so the round cut a wedge of the artwork out of the rail's corner
     instead. The kit's rail radius belongs to the marcom rail, which is laid on
     paper; this one is a column against the page's content. */
}

.rail__nav {
  display: flex;
  flex-direction: column;
  gap: var(--site-space-x);
  padding: var(--site-space-x) var(--site-rail-pad-inline, var(--site-space-x));
  overflow-y: auto;
}
.rail__group { display: flex; flex-direction: column; }
.rail__group .eyebrow { color: var(--site-neutral-70); margin-bottom: var(--label-gap); }

.rail__link {
  position: relative;
  padding-left: var(--site-space-x);
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  line-height: 2;
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--site-neutral-70);
}
/* The mark of position survives, attached to the name it marks rather than
   standing on its own. */
.rail__link::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: var(--site-tick-short, 12px);
  height: var(--site-rule-weight);
  background: currentColor;
  opacity: 0;
  transition: opacity var(--dur-react-in) var(--ease-standard);
}
.rail__link {
  transition: color var(--dur-react-out) var(--ease-standard);
}
.rail__link:hover { color: var(--site-ink); transition-duration: var(--dur-react-in); }
.rail__link.is-current { color: var(--site-accent); }
.rail__link.is-current::before { opacity: 1; }

/* The mark at the head of the rail. The rail is sticky and full height, so the
   mark is the one thing on screen at every point of the scroll, and it is the
   way back to the top from anywhere. It takes the small step of the logomark:
   the drawing is 40px here, which is the mark's own minimum height. */
.rail__mark {
  display: block;
  padding: 0 var(--site-rail-pad-inline, var(--site-space-x))
           var(--site-space-2x);
  transition: opacity var(--dur-react-out) var(--ease-standard),
              transform var(--dur-press) var(--ease-standard);
}
.rail__mark img { display: block; height: 40px; width: auto; }
.rail__mark:hover { opacity: 0.7; transition-duration: var(--dur-react-in); }
.rail__mark:active { transform: scale(0.98); transition-duration: var(--dur-press); }
.rail__mark:focus-visible { outline: 2px solid var(--site-accent); outline-offset: 2px; }

/* Below the rail's breakpoint this is the only control in the bar, and set as
   bare uppercase text it read as a stray word floating over the cover rather
   than as the bar's own control. It takes the chrome every other control on
   the page takes: a hairline, a pill, and the ground the page uses under a
   control. */
.rail__toggle {
  display: none;
  appearance: none;
  border: var(--rule);
  border-radius: var(--radius-pill);
  background: var(--site-bg-2);
  color: inherit;
  padding: var(--site-space-half-x) var(--site-space-x);
  margin-right: var(--margin);
  transition: background-color var(--dur-react-out) var(--ease-standard),
              color var(--dur-react-out) var(--ease-standard),
              transform var(--dur-press) var(--ease-standard);
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  cursor: pointer;
}

main { flex: 1; min-width: 0; }

/* ── bands ──────────────────────────────────────────────────────────────── */

/* A section is a band. Bands are divided by a hairline, never by a fill, and
   they sit apart on the large interval. */
.band { border-top: var(--rule); }

/* A section jumped to lands a little below the top of the window rather than
   flush against it, and below the rail's breakpoint it clears the bar the rail
   becomes. Without this the band's own top rule sits in the window's edge and
   the section reads as having no top. */
section[id] { scroll-margin-top: var(--site-space-x); }
@media (max-width: 1200px) {
  section[id] { scroll-margin-top: calc(var(--tile) + var(--site-space-x)); }
}

/* A band, a group and a section head are all read on the page's own column
   count. Everything in them spans the full measure; a loose paragraph takes a
   single column, so it starts and ends where the columns beneath it do. */
.band__inner {
  padding: var(--site-space-12x) var(--margin);
  display: grid;
  grid-template-columns: repeat(var(--gcols), minmax(0, 1fr));
  column-gap: 0;
  row-gap: var(--site-space-6x);
}
.band__inner > * { grid-column: 1 / -1; min-width: 0; }
.band__inner > .note {
  grid-column: 1 / span var(--gspan-loose);
  padding-right: var(--site-space-2x);
}
.band__inner > .lead, .band__inner > .statement {
  grid-column: 1 / span var(--gspan-column);
  padding-right: var(--site-space-2x);
}

.band--dark { background: var(--site-feature-bg, var(--site-ink)); color: var(--site-feature-ink, var(--site-bg)); }
.band--dark .note { color: var(--site-feature-note, var(--site-neutral-30)); }
.band--dark .eyebrow { color: var(--site-accent); }

.head {
  display: grid;
  grid-template-columns: repeat(var(--gcols), minmax(0, 1fr));
  column-gap: 0;
  row-gap: var(--site-space-2x);
}
.head > * { grid-column: 1 / -1; }
.head > .lead { grid-column: 1 / span var(--gspan-column); padding-right: var(--site-space-2x); }
.head .eyebrow { margin-bottom: calc(var(--label-gap) - var(--site-space-2x)); }

.group {
  min-width: 0;
  display: grid;
  grid-template-columns: repeat(var(--gcols), minmax(0, 1fr));
  column-gap: 0;
  row-gap: var(--site-space-2x);
}
.group > * { grid-column: 1 / -1; min-width: 0; }
.group > .note {
  grid-column: 1 / span var(--gspan-loose);
  padding-right: var(--site-space-2x);
}
.group > .statement {
  grid-column: 1 / span var(--gspan-column);
  padding-right: var(--site-space-2x);
}
/* A group's label sits on the group's own row gap, which is the interval
   between parts and far too wide to read as attached to what it names. */
.group > .eyebrow:not(:last-child) {
  margin-bottom: calc(var(--label-gap) - var(--site-space-2x));
}
.stack { display: grid; row-gap: var(--site-space-6x); }

.cols { display: grid; gap: var(--site-space-6x); align-items: start; }
.cols--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.cols--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.cols--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.cols--5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.cols--tight { gap: var(--site-space-2x); }
.cols--icons { align-items: end; }

/* A row of text columns is ruled, not spaced: one hairline runs across the
   whole row, and a hairline stands between the columns. The columns sit flush
   so neither line is ever broken by a gutter. */
.cols--ruled { column-gap: 0; row-gap: 0; align-items: stretch; }
.cols--ruled > * {
  border-top: var(--rule);
  border-left: var(--rule);
  /* The foot of a cell closes the parts of one block, not the interval between
     blocks: on the larger gap a short cell trails its rule far past its last
     line. */
  padding: var(--site-space-2x) var(--site-space-2x);
}
.cols--ruled.cols--2 > *:nth-child(2n + 1),
.cols--ruled.cols--3 > *:nth-child(3n + 1),
.cols--ruled.cols--4 > *:nth-child(4n + 1) { border-left: 0; padding-left: 0; }
.band--dark .cols--ruled > * { border-color: var(--site-feature-rule, var(--site-neutral-80)); }

/* A grid nested inside another grid holds a fraction of the content column, so
   the viewport breakpoints below never describe the width it really has. A
   two-up inside a two-up is under 250px a cell on a 1440 screen, which is
   narrower than the headings in it. It collapses on its own width, not on the
   window's. */
@media (max-width: 1600px) {
  .cols .cols--2, .cols .cols--3 { grid-template-columns: minmax(0, 1fr); }
  .cols .cols--ruled > * { border-left: 0; padding-left: 0; }
}

/* Images butt up against one another, so a set reads as one field. */
.cols--flush { column-gap: 0; }
.cols--flush .shot { margin-left: calc(var(--site-rule-weight) * -1); }
.cols--flush.cols--2 > *:nth-child(2n + 1) .shot,
.cols--flush.cols--4 > *:nth-child(4n + 1) .shot { margin-left: 0; }
.cols--flush .eyebrow, .cols--flush .note { padding-right: var(--site-space-2x); }

/* One stack, used wherever a block holds a label, a heading and prose. The
   eyebrow belongs to what follows it, so it sits on the small interval; every
   other step in the stack sits on the larger one. */
.piece, .layer, .face, .rule-cell, .numbered, .stat, .icon-cell {
  display: flex;
  flex-direction: column;
  gap: var(--site-space-2x);
}
.piece > .eyebrow:not(:last-child), .layer > .eyebrow:not(:last-child),
.face > .eyebrow:not(:last-child), .rule-cell > .eyebrow:not(:last-child),
.stat > .eyebrow:not(:last-child),
.icon-cell > .eyebrow:not(:last-child) {
  margin-bottom: calc(var(--label-gap) - var(--site-space-2x));
}

/* ── cover ─────────────────────────────────────────────────────── */

/* The page opens the way the brand's own guidelines open: one screen, the
   cover's pigment render run full bleed, and the lockup reversed out of it in
   white. The kit permits full-bleed pigment imagery as a ground and puts white
   type and a white lockup on it, and it is the only field in the system that
   runs to all four edges. */
.cover {
  position: relative;
  margin-left: calc(var(--rail-w) * -1);
  width: calc(100% + var(--rail-w));
  /* The render bleeds under the rail; the type on it does not. */
  padding-left: var(--rail-w);
  isolation: isolate;
  overflow: hidden;
  min-height: var(--cover-h);
  display: grid;
  place-items: center;
  background: var(--site-cover-bg, var(--site-ink));
  color: var(--site-cover-ink, var(--site-bg));
}
.cover > * { position: relative; z-index: 1; }

/* The render itself. It is cropped to the frame rather than fitted, and it is
   pinned to the foot of the band, so the crop comes off the top and the lit limb
   is never the part that goes. */
.cover__field {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    image-set(
      url("imagery/cover-field.webp") 1x,
      url("imagery/cover-field.webp") 2x
    ) center bottom / cover no-repeat,
    var(--site-cover-bg, var(--site-ink));
}

/* The mark and the wordmark ship in ink, so the lockup is masked and filled
   white rather than recolored in the artwork. */
.cover__lockup {
  display: block;
  width: min(calc(var(--layout-module) * 9), 62vw);
  aspect-ratio: 1180 / 220;
  background: var(--site-cover-ink, var(--site-bg));
  -webkit-mask: url("assets/logos/permanence-bio-lockup-horizontal.svg") center / contain no-repeat;
          mask: url("assets/logos/permanence-bio-lockup-horizontal.svg") center / contain no-repeat;
}

/* The running head the guidelines carry on every page: who it belongs to, and
   what the piece is, in the label style. */
.cover__meta {
  position: absolute;
  top: var(--site-space-2x);
  left: calc(var(--rail-w) + var(--margin));
  right: var(--margin);
  display: flex;
  flex-wrap: wrap;
  gap: var(--site-space-x) var(--site-space-2x);
}
.cover__label {
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--site-cover-ink, var(--site-bg));
}

@media (max-width: 1200px) {
  .cover__field {
    background-image: image-set(
      url("imagery/cover-field-small.webp") 1x,
      url("imagery/cover-field.webp") 2x
    );
  }
  /* At a phone's width the lockup runs to the page's own margins. */
  .cover__lockup { width: calc(100% - var(--margin) * 2); }
}

/* ── call to action ─────────────────────────────────────────────────────── */

/* One button, three treatments. Square corners, a mono label in all caps on
   even padding, and no border, shadow or gradient.

   The reaction is asymmetric: it engages in --dur-react-in and releases over
   --dur-react-out, both on ease.standard. The button starts with the slower
   out-timing as its base transition, and :hover swaps in the faster in-timing,
   so entering is quick and leaving settles. Press adds scale(0.98) on its own
   --dur-press (80ms, immediate tactile feedback) rather than the react pair,
   so it reads as depressed rather than merely recolored. */
.cta {
  display: inline-flex;
  align-items: center;
  gap: var(--site-space-x);
  justify-content: center;
  padding: var(--site-cta-pad-y, var(--site-space-x)) var(--site-cta-pad-x, var(--site-space-2x));
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--site-cta-size, var(--text-eyebrow));
  letter-spacing: var(--site-cta-tracking, var(--site-tracking-eyebrow));
  text-transform: uppercase;
  border: 0;
  border-radius: var(--site-cta-radius, 0);
  cursor: pointer;
  transition: background var(--dur-react-out) var(--ease-standard),
              color var(--dur-react-out) var(--ease-standard),
              transform var(--dur-press) var(--ease-standard);
}
.cta:hover {
  transition-duration: var(--dur-react-in);
}
.cta:active { transform: scale(0.98); transition-duration: var(--dur-press); }
.cta--accent { background: var(--site-accent); color: var(--site-ink); }
.cta--solid { background: var(--site-ink); color: var(--site-bg); }
.cta--quiet { background: var(--site-cta-quiet-bg, var(--site-bg)); color: var(--site-cta-quiet-ink, var(--site-ink)); }
.cta--accent:hover { background: var(--site-ink); color: var(--site-bg); }
/* The quiet button carries the accent as its label, so it inverts on hover
   rather than filling with the color the label is already set in. */
.cta--quiet:hover { background: var(--site-accent); color: var(--site-cta-quiet-bg, var(--site-bg)); }
.cta--solid:hover { background: var(--site-neutral-90); }

.link {
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  display: inline-flex;
  align-items: center;
  gap: var(--site-space-half-x);
  transition: color var(--dur-react-out) var(--ease-standard);
}
.link:hover { transition-duration: var(--dur-react-in); }
/* The small chevron the system uses beside a text link, drawn on the same 45
   degree diagonals as everything else. */
.chev {
  width: 6px;
  height: 6px;
  border-top: 2px solid currentColor;
  border-right: 2px solid currentColor;
  transform: rotate(45deg);
}
.link:hover { color: var(--site-accent); }

/* ── copy affordance ────────────────────────────────────────────────────── */

/* Anything carrying a value can be lifted off the page. The affordance stays
   invisible until the pointer is on the specimen, so the resting page is just
   the work. */
.copy {
  appearance: none;
  border: 0;
  background: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  display: block;
  width: 100%;
}
.copy:focus-visible { outline: 2px solid var(--site-accent); outline-offset: 4px; }

.copy__hint {
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--site-neutral-70);
}

/* One toast, bottom left, out of the way of the work. */
.toast {
  position: fixed;
  left: var(--margin);
  bottom: var(--site-space-2x);
  z-index: 60;
  padding: var(--site-space-x) var(--site-space-2x);
  background: var(--site-ink);
  color: var(--site-bg);
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  opacity: 0;
  pointer-events: none;
}
.toast.is-up { opacity: 1; }

/* ── color specimens ───────────────────────────────────────────────────── */

.swatch { display: block; }
/* The palette is drawn as circles, the way the guidelines set it out: a full
   round of the value itself, with the name and the hex read beneath it. */
.chip {
  display: block;
  width: 100%;
  max-width: var(--site-swatch-d, 180px);
  aspect-ratio: 1;
  border-radius: 50%;
}
/* A pale chip would otherwise vanish into the page's ground, so it takes the
   system's own hairline. A chip that reads on its own takes nothing. build.py
   decides which is which from the value, so no hex is listed here. */
.chip--pale { border: var(--rule); }

.chip__meta {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0 var(--site-space-x);
  margin-top: var(--site-space-x);
}
.chip__name { font-size: var(--site-text-p3); }
.chip__value {
  grid-column: 1;
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  color: var(--site-neutral-70);
  text-transform: uppercase;
}
.chip__meta .copy__hint { grid-column: 2; grid-row: 1 / span 2; align-self: center; }

/* The swatch is a small tile on a fixed shape, so it is padded on the interval
   between related elements rather than the one between parts of a block: the
   larger step leaves a tile with no room in it and clips the name inside. */
.pair {
  aspect-ratio: 3 / 2;
  min-width: 0;
  padding: var(--site-space-x);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  /* A pairing is a color field on a ground. The label names the pair and is
     not part of it, so it holds the page's own ink rather than the field's
     color, which is often a near-white. */
  color: var(--site-ink);
}
.pair__field {
  align-self: flex-start;
  width: 38%;
  aspect-ratio: 1;
  border-radius: 50%;
}
.pair__label {
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  /* Token names are long and hyphenated, so one turns inside the tile rather
     than running out of it. */
  overflow-wrap: anywhere;
}
.pair--pale { border: var(--rule); }

/* ── specimen rows ──────────────────────────────────────────────────────── */

.rows { display: flex; flex-direction: column; border-top: var(--rule); }
/* The name, the specimen and the values it is set in read as one row, so they
   start on one line at the top of it rather than on a shared baseline: a name
   or a value that turns would otherwise drag its row out of line with the rest.
   The specimen takes the whole measure and the values sit under it, because a
   96px specimen beside a column of values is either clipped or broken. */
.row {
  display: grid;
  grid-template-columns: 16ch minmax(0, 1fr) 8ch;
  gap: var(--site-space-2x);
  align-items: start;
  padding: var(--site-space-2x) 0;
  border-bottom: var(--rule);
}
.row__body { display: grid; row-gap: var(--site-space-x); min-width: 0; }
/* Each value is one unbreakable unit, so a row that runs out of width drops a
   whole value to the next line instead of splitting one down the middle. */
.row__spec { display: flex; flex-wrap: wrap; column-gap: var(--site-space-2x); row-gap: var(--site-space-half-x); }
.row__spec > span { white-space: nowrap; }
.row--space { grid-template-columns: 12ch 18ch minmax(0, 1fr) 8ch 8ch; }
.row__name { white-space: nowrap; }
.row__name, .row__spec, .row__role {
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
}
.row__spec, .row__role { color: var(--site-neutral-70); }
.row__role { text-transform: none; font-family: var(--site-font-body); font-weight: var(--site-weight-body); letter-spacing: var(--site-tracking-body); font-size: var(--site-text-p3); }
.row__sample { display: block; min-width: 0; }
.row__bar { display: block; }
.row__bar > span { display: block; height: var(--site-space-half-x); background: var(--site-accent); }
.row .copy__hint { justify-self: end; }

.face__name { font-size: 56px; line-height: var(--site-leading-display); }

.stack-demo { border-top: var(--rule); padding-top: var(--site-space-2x); }
.stack-demo .h3 { margin-top: var(--site-space-half-x); }
.stack-demo .note { margin-top: var(--site-space-2x); }

/* ── figures ────────────────────────────────────────────────────────────── */

.signature { align-items: stretch; }



/* ── imagery tiles ──────────────────────────────────────────────────────── */

.shot { border: var(--rule); background: var(--site-bg-2); }
.shot img { display: block; width: 100%; height: auto; }

/* ── the web module ─────────────────────────────────────────────────────── */

.module {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  border: var(--rule);
}
.module__image img { display: block; width: 100%; height: 100%; object-fit: cover; }
.module__text {
  padding: var(--site-space-6x);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}
.module__text .h3 { margin-top: var(--site-space-half-x); }
.module__text p:last-of-type { margin-top: var(--site-space-2x); }
.module__text .cta { margin-top: var(--site-space-6x); }

/* ── icons ──────────────────────────────────────────────────────────────── */

/* The icons share a height and never a box: the common height is five times the
   label's font size, and each width follows from the icon's own proportions. */
.icon-row {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--site-space-6x);
  align-items: end;
}
.icon-cell { align-items: flex-start; }
.icon { display: block; color: var(--site-ink); }
.icon svg { display: block; height: 96px; width: auto; }
.icon--web svg { height: calc(var(--text-eyebrow) * 5); }
.band--dark .icon { color: var(--site-feature-ink, var(--site-bg)); }

/* ── cards ──────────────────────────────────────────────────────────────── */

/* Cards sit on the grid as equal blocks, separated by thin rules, with a rule
   closing the row beneath them. No border, no radius, no shadow. */
.card-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-bottom: var(--rule);
}
.card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--site-space-x);
  padding: var(--site-space-2x) var(--site-space-2x) var(--site-space-6x);
  border-left: var(--rule);
}
.card:first-child { border-left: 0; padding-left: 0; }
/* Every card in a row carries the same parts in the same order, so the image
   band is one depth across the row and the headings start on one line. */
.card__image { width: 100%; aspect-ratio: 4 / 3; }
.card__image img { display: block; width: 100%; height: 100%; object-fit: cover; }
.card .h3 { margin-top: var(--site-space-x); }
.card .link { margin-top: var(--site-space-x); }

.specimen { padding: var(--site-space-6x); border: var(--rule); background: var(--site-bg-2); overflow-x: auto; }
.specimen--row { display: flex; flex-wrap: wrap; gap: var(--site-space-2x); align-items: center; }

/* The number and its label are one unit, so they sit on the small interval. */
.stat { gap: var(--site-space-half-x); }
.stat__figure {
  font-family: var(--site-font-display);
  font-weight: var(--site-weight-display);
  font-size: var(--text-h2);
  line-height: var(--site-leading-display);
  letter-spacing: var(--site-tracking-h2);
  text-transform: var(--site-case-display, uppercase);
}

/* ── tokens ─────────────────────────────────────────────────────────────── */

.scroll { overflow-x: auto; min-width: 0; max-width: 100%; }
.tokens {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
}
.tokens th, .tokens td {
  text-align: left;
  padding: var(--site-space-x);
  border-top: var(--rule);
  white-space: nowrap;
}
.tokens th { color: var(--site-neutral-70); }
.tokens tbody tr {
  cursor: pointer;
  transition: background var(--dur-react-out) var(--ease-standard),
              transform var(--dur-press) var(--ease-standard);
}
.tokens tbody tr:active { transform: scale(0.998); transition-duration: var(--dur-press); }
.tokens tbody tr:hover { background: var(--site-neutral-10); transition-duration: var(--dur-react-in); }
/* The hover state names itself: a rule in the accent down the leading edge, and
   the word for what a click does. Nothing else on the row moves. */
.tokens tbody tr td:first-child {
  box-shadow: inset 2px 0 0 0 transparent;
  transition: box-shadow var(--dur-react-out) var(--ease-standard);
}
.tokens tbody tr:hover td:first-child {
  box-shadow: inset 2px 0 0 0 var(--site-accent);
  transition-duration: var(--dur-react-in);
}
.tokens tbody tr .tokens__hint { color: var(--site-neutral-70); text-align: right; }
/* Copied is a confirmation, not an alarm. Filling the row with the accent put
   ink on a dark red, which fails to read, and flashed the whole row at full
   strength for a click the reader already knows they made. The brand's own 15%
   tint of the same accent says the same thing quietly and holds ink. */
.tokens tbody tr.is-copied { background: var(--red-ochre-15); }
.tokens tbody tr.is-copied .tokens__hint { opacity: 1; color: var(--site-ink); }
.tokens tbody tr:last-child td { border-bottom: var(--rule); }
.tokens--vocab { text-transform: none; font-family: var(--site-font-body); font-weight: var(--site-weight-body); font-size: var(--site-text-p3); letter-spacing: var(--site-tracking-body); }
.tokens--vocab tbody tr { cursor: default; }
.dot { display: inline-block; width: 12px; height: 12px; vertical-align: -1px; margin-right: var(--site-space-half-x); border: var(--rule); }

/* ── worked examples ────────────────────────────────────────────────────── */

/* A rule shown as the line that works and the line that does not. The pair
   reads top to bottom, and the marker is a character in the margin rather than
   a color, so it survives a black-and-white print of the page. */
.yes, .no {
  font-size: var(--site-text-p3);
  padding-left: var(--site-space-x);
  position: relative;
}
.yes { color: var(--site-ink); }
.no { color: var(--site-neutral-70); text-decoration: line-through; }
.yes::before, .no::before {
  position: absolute;
  left: 0;
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
}
.yes::before { content: "Y"; color: var(--site-accent); }
.no::before { content: "N"; color: var(--site-neutral-70); }

/* ── a reserved band ────────────────────────────────────────────────────── */

/* A section of the kit that is specified but not yet written. It is drawn as an
   empty block on the grid so the page shows where the work lands, rather than
   omitting it and reading as though nobody thought of it. */
.reserved {
  border: var(--site-rule-weight) dashed var(--site-rule-color);
  padding: var(--site-space-6x) var(--site-space-2x);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--site-space-half-x);
  text-align: center;
}
.reserved .eyebrow { color: var(--site-neutral-70); }

/* ── motion ─────────────────────────────────────────────────────────────── */

/* The composed reveal: the system's one reusable entrance. Opacity on
   ease.neutral and translateY on ease.enter run in parallel over the same
   duration, rather than one curve applied to the whole transition. An element
   starts slightly below rest and fades up into place.

   [data-reveal] sets the resting (pre-play) state. .is-in is added once, by
   the IntersectionObserver in script.js, and never removed: the reveal plays
   once. --reveal-lift and --reveal-delay are read per element, so one utility
   serves every distance and stagger interval in the system rather than a class
   per combination. */
/* A CSS transition needs real elapsed time in the "hidden" state before it
   reads as visible: replaying by removing then quickly re-adding .is-in
   (the same restartClass helper the distance and stagger demos use) doesn't
   give it that time, so the browser correctly computes a valid but nearly
   invisible interrupted reverse-transition from wherever the element already
   was, rather than the full reveal. A keyframe animation restarts cleanly
   from 0% on every reapplication regardless of timing, the same way the
   distance and stagger keyframes already do, so this uses two (one per
   property, so opacity and lift keep their own separate curves rather than
   sharing one easing the way a single animation would force). */
[data-reveal] {
  --reveal-lift: var(--lift-macro);
  --reveal-delay: 0ms;
  opacity: 0;
  transform: translateY(var(--reveal-lift));
}
/* --reveal-duration defaults to the token and is only ever overridden by an
   element that has to finish in step with something else, which is a timing
   relationship rather than a new duration value: the campaign hero's artwork
   opens with the first line of copy and settles as the last one does. */
[data-reveal] { --reveal-duration: var(--dur-reveal); }
[data-reveal].is-in {
  animation:
    dr-fade var(--reveal-duration) var(--ease-neutral) var(--reveal-delay) forwards,
    dr-rise var(--reveal-duration) var(--ease-enter) var(--reveal-delay) forwards;
}
@keyframes dr-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes dr-rise { from { transform: translateY(var(--reveal-lift)); } to { transform: translateY(0); } }

/* A demo band that wants to replay is marked .replayable and toggles .is-in
   off and back on; the utility above still only ever transitions forward, so
   a replay is a clean re-entrance rather than a reverse-play. */

/* ── motion demos ───────────────────────────────────────────────────────── */
/* Structure ported from the brand's own motion-guidelines deployment: a
   numbered row per topic, a specs rail on the left, a big interactive stage
   on the right, and a token table under every stage. */

.motion-rows { display: flex; flex-direction: column; }
.motion-row {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  gap: var(--site-space-6x);
  border-top: var(--rule);
  padding: var(--site-space-6x) 0;
  align-items: start;
}
.motion-row__num {
  font-family: var(--site-font-display);
  font-weight: var(--site-weight-display);
  font-size: 48px;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--site-neutral-70);
  margin-bottom: var(--site-space-x);
}
.motion-row__title {
  font-family: var(--site-font-body);
  font-weight: var(--site-weight-body);
  font-size: var(--site-text-p3);
  line-height: 1.4;
  color: var(--site-ink);
  margin: 0 0 var(--site-space-half-x);
}
.motion-row__info .note { margin-bottom: var(--site-space-x); }
.motion-specs {
  font-family: var(--site-font-body);
  font-size: var(--text-eyebrow);
  color: var(--site-neutral-70);
  line-height: 1.9;
}
/* Each row is a two-column flex line rather than an inline label followed by
   running text: the value gets its own column to wrap inside, so a long
   description breaks under itself rather than ragged under the short label
   the way inline wrapping does at the rail's narrow width. */
.motion-specs > div { display: flex; gap: var(--site-space-x); align-items: baseline; }
.motion-specs .k { color: var(--site-ink); flex: 0 0 76px; font-weight: 500; }
.motion-specs .v { flex: 1 1 auto; min-width: 0; }
.motion-row__stage { display: flex; flex-direction: column; gap: var(--site-space-2x); }

/* The easing carousel reads better at the band's own full measure than
   squeezed into the column a spec rail would otherwise leave it, the same way
   "what breaks the system" ignores any rail elsewhere on the page. The info
   block holds its normal column on the first row; the stage drops to a second
   row and spans both, breaking free of the rail underneath it. */
.motion-row--full {
  grid-template-columns: 220px minmax(0, 1fr);
  grid-template-rows: auto auto;
}
.motion-row--full .motion-row__info { grid-column: 1; grid-row: 1; }
.motion-row__stage--full {
  grid-column: 1 / -1;
  grid-row: 2;
  margin-top: var(--site-space-2x);
}

/* The motion tables' third column carries a free-text description rather
   than a short value, so it wraps instead of pushing the row into horizontal
   scroll the way a token name or a hex value is meant to. */
.motion-tokens .tokens td:nth-child(3) { white-space: normal; }

.stage-label {
  font-family: var(--site-font-mono);
  font-size: var(--text-eyebrow);
  font-weight: 500;
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--site-neutral-70);
  margin: 0;
}
.stage-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--site-space-x);
  flex-wrap: wrap;
}
.stage-head-controls { display: flex; align-items: center; gap: var(--site-space-half-x); flex-wrap: wrap; }

/* The pause control for the easing carousel: a small square icon-button that
   swaps its glyph rather than its label, so it reads at a glance next to the
   speed toggle it sits beside. */
.pause-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: var(--rule);
  background: var(--site-bg-2);
  color: var(--site-neutral-70);
  cursor: pointer;
  transition: background var(--dur-react-out) var(--ease-standard),
              color var(--dur-react-out) var(--ease-standard);
}
.pause-btn:hover { transition-duration: var(--dur-react-in); background: var(--site-neutral-10); color: var(--site-ink); }
.pause-btn:active { transform: scale(0.98); transition-duration: var(--dur-press); }
.pause-btn svg { display: block; fill: currentColor; }
.pause-btn .icon-pause { width: 9px; height: 11px; }
.pause-btn .icon-play { width: 10px; height: 11px; display: none; }
.pause-btn[aria-pressed="true"] { background: var(--site-ink); color: var(--site-bg); }
.pause-btn[aria-pressed="true"] .icon-pause { display: none; }
.pause-btn[aria-pressed="true"] .icon-play { display: block; }

/* The replay control every non-autoplaying stage carries, aligned to its own
   right edge under the demo rather than floating over it. */
.replay {
  display: inline-flex;
  align-items: center;
  gap: var(--site-space-half-x);
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  background: var(--site-bg);
  border: var(--rule);
  padding: var(--site-space-half-x) var(--site-space-x);
  cursor: pointer;
  color: var(--site-ink);
  transition: background var(--dur-react-out) var(--ease-standard),
              color var(--dur-react-out) var(--ease-standard);
}
/* The reaction is carried by the ground, not by the label. Inverting to a
   light label meant the text's contrast depended on the fill landing behind
   it, and a caller that set its own background (the control bar in 03 and 04
   does, to sit on the bar's own beige) left white type on a light ground. A
   dark label on a darkening ground cannot fail that way whatever the button
   is placed on. */
.replay:hover { background: var(--site-neutral-30); transition-duration: var(--dur-react-in); }
.replay:active { transform: scale(0.98); transition-duration: var(--dur-press); }

/* The speed toggle on the easing stage: two labelled states, the active one
   filled, the same treatment .pause-btn and .replay already use for hover. */
.speed-toggle { display: inline-flex; border: var(--rule); overflow: hidden; }
/* No italic. The kit ships Light and Regular and no italic cut at all, so an
   italic here was the browser slanting a roman face, which is the one thing a
   type sheet on this page cannot do. The toggle is a control, so it takes the
   page's control language: mono, uppercase, at eyebrow size. */
.speed-btn {
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  padding: var(--site-space-half-x) var(--site-space-x);
  border: 0;
  background: var(--site-bg-2);
  color: var(--site-neutral-70);
  cursor: pointer;
  transition: background var(--dur-react-out) var(--ease-standard),
              color var(--dur-react-out) var(--ease-standard);
}
.speed-btn.is-active { background: var(--site-ink); color: var(--site-bg); }
.speed-btn:hover:not(.is-active) { color: var(--site-ink); transition-duration: var(--dur-react-in); }
.speed-btn:active { transform: scale(0.98); transition-duration: var(--dur-press); }
.speed-btn + .speed-btn { border-left: var(--rule); }

/* ── 01 easing ──────────────────────────────────────────────────────────── */
/* Three cells, three roles: opacity, lift, composed. .is-active marks the one
   the carousel (or a hover, or a click) currently has focused; every other
   cell dims so the reader's eye has one thing to follow at a time. */
/* The right rule lives on the grid, not on the last cell: giving the last
   .easing-cell its own border-left + border-right (2px) while its siblings
   carry only border-left (1px) shrinks its content by 1px more than its
   neighbours, and since the curve SVG scales to that content width, its
   plotted points landed a visible pixel off from the other two cells'. */
/* grid-template-rows on the outer grid plus subgrid on each cell is what
   lets .ec-curve and .ec-anim share one row height across all three cells:
   a cell whose description wraps an extra line no longer grows only its own
   .ec-curve while its neighbours' stay shorter, which used to drift the
   plotted curve and the divider between curve and puck row out of alignment
   with the cells beside it. */
.easing-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-rows: auto auto;
  gap: 0;
  border-top: var(--rule);
  border-right: var(--rule);
}
.easing-cell {
  display: grid;
  grid-row: span 2;
  grid-template-rows: subgrid;
  background: transparent;
  overflow: hidden;
  cursor: pointer;
  border-left: var(--rule);
}
.easing-cell > * { transition: opacity 0.35s var(--ease-standard); }
/* 0.45 in the reference design drops its own already-muted text under AA
   contrast. 0.85 still reads as visually de-emphasized next to the focused
   cell, and stacked on the palette's own muted tones stays clear of the
   page's accessibility gate (checked against --site-neutral-70 on beige, the
   tightest case among this cell's text colors). */
.easing-cell:not(.is-active) > * { opacity: 0.85; }
.easing-cell.is-active > * { opacity: 1; }
.ec-curve {
  padding: var(--site-space-x) var(--site-space-x) var(--site-space-half-x);
  border-bottom: var(--rule);
  display: flex;
  flex-direction: column;
}
/* Pins the curve to the bottom of its own row: subgrid makes every cell's
   .ec-curve row the same height (the tallest description across the three
   cells), and this keeps the plotted curve flush against the shared divider
   in that shared height rather than drifting with each cell's own content. */
.ec-curve .ec-svg { margin-top: auto; }
.ec-eyebrow {
  font-family: var(--site-font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--site-neutral-70);
  margin: 0 0 var(--site-space-half-x);
}
.ec-name {
  font-family: var(--site-font-display);
  font-weight: var(--site-weight-body);
  font-size: 17px;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--site-ink);
  margin: 0 0 var(--site-space-half-x);
}
.ec-formula {
  font-family: var(--site-font-mono);
  font-size: var(--text-eyebrow);
  color: var(--site-neutral-70);
  margin: 0 0 var(--site-space-half-x);
  letter-spacing: 0.02em;
}
.ec-desc {
  font-size: var(--site-text-p3);
  line-height: 1.5;
  color: var(--site-neutral-70);
  margin: 0 0 var(--site-space-x);
}
.ec-svg { width: 100%; height: auto; display: block; }
.ec-anim { padding: var(--site-space-x); display: flex; flex-direction: column; justify-content: center; gap: var(--site-space-half-x); }
.ec-anim-label { font-family: var(--site-font-mono); font-size: var(--text-eyebrow); letter-spacing: 0.04em; color: var(--site-neutral-70); }
.ec-anim-track { position: relative; width: 100%; height: 24px; }
.easing-box { position: absolute; left: 0; top: 0; width: 24px; height: 24px; border-radius: 50%; background: var(--site-ink); }
.easing-cell.opacity .easing-box  { background: var(--site-accent-2); }
.easing-cell.lift .easing-box     { background: var(--site-accent-4); }
.easing-cell.composed .easing-box { background: var(--site-accent); }

/* Not-active cells hold a resting frame rather than a random mid-animation
   frame: opacity/composed sit invisible, lift sits fully arrived. */
.easing-cell:not(.is-active) .easing-box { animation: none; left: 0; }
.easing-cell:not(.is-active).opacity .easing-box,
.easing-cell:not(.is-active).composed .easing-box { opacity: 0; }
.easing-cell:not(.is-active).lift .easing-box { opacity: 1; }
[data-easing-grid].is-rest .easing-cell > * { opacity: 1; }
[data-easing-grid].is-rest .easing-box { animation: none; left: 0; }
[data-easing-grid].is-rest .easing-cell.opacity .easing-box,
[data-easing-grid].is-rest .easing-cell.composed .easing-box { opacity: 0; }
[data-easing-grid].is-rest .easing-cell.lift .easing-box { opacity: 1; }

[data-easing-grid] { --easing-play-dur: var(--dur-reveal); }
[data-easing-grid][data-speed="slo"] { --easing-play-dur: var(--motion-duration-easing-slo, 2000ms); }
.easing-cell.is-active.opacity .easing-box {
  animation: e-op var(--easing-play-dur) var(--ease-neutral) 2 alternate;
}
.easing-cell.is-active.lift .easing-box {
  animation: e-li var(--easing-play-dur) var(--ease-enter) 2 alternate;
}
.easing-cell.is-active.composed .easing-box {
  animation:
    e-op var(--easing-play-dur) var(--ease-neutral) 2 alternate,
    e-li var(--easing-play-dur) var(--ease-enter) 2 alternate;
}
@keyframes e-op { from { opacity: 0; } to { opacity: 1; } }
@keyframes e-li { from { left: 0; } to { left: calc(100% - 24px); } }

/* ── 02 interaction ─────────────────────────────────────────────────────── */
.bhv-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--site-space-2x); }
.bhv-tile { background: var(--site-bg-2); display: flex; flex-direction: column; }
.bhv-tile-label {
  font-family: var(--site-font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--site-neutral-70);
  padding: var(--site-space-x) var(--site-space-2x) var(--site-space-half-x);
  margin: 0;
}
.bhv-surface {
  position: relative;
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--site-space-6x) var(--site-space-2x);
  min-height: 180px;
}
.bhv-surface--dark { background: var(--site-ink); }
.bhv-surface--light { background: var(--site-bg); }

/* The translucent CTA: read directly off the live site's own hero button
   rather than approximated. White at 90% at rest, fully opaque on hover; a
   fixed 2px radius throughout, not an animated one. Ink label at rest,
   shifting to the accent (red ochre) on hover so the reaction reads in the
   label as well as the background. At 90% white it is a translucent button and
   not a glass one, and it is named for what it is. */
.cta--translucent {
  background: rgba(255, 255, 255, 0.9);
  color: var(--site-ink);
  border-radius: 2px;
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
}
.cta--translucent:hover { background: rgba(255, 255, 255, 1); color: var(--site-accent); }

/* ── 03 distance ────────────────────────────────────────────────────────── */
/* Three contexts, three distances. A lift value belongs to the kind of thing
   entering rather than to the individual element, so each tier gets the sort
   of interface it was written for and the reader switches between them.

   The stage, tabs and captions are 04's, reused rather than restyled: the two
   sections are the same instrument asking about different tokens, and reading
   as one pattern is the point. What is added here is the annotation rail and
   the three scenarios' own content. */

/* The rail sits beside the scene, so the stage is two columns here where 04's
   is one. */
.spec-el { will-change: transform, opacity; min-width: 0; }

.spec-eyebrow { color: var(--site-accent); margin: 0; }
.spec-display {
  font-family: var(--site-font-display);
  font-weight: var(--site-weight-display);
  font-size: var(--site-text-h1);
  line-height: var(--site-leading-display);
  letter-spacing: var(--site-tracking-h1);
  color: var(--site-ink);
  margin: 0;
}
.spec-lede { max-width: 44ch; margin: 0; }
.spec-cta { display: inline-flex; cursor: default; }
.spec-meta {
  font-family: var(--site-font-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--site-neutral-70);
  margin: 0;
}

/* micro: the compact furniture the token is written for. */
.spec-micro, .spec-hero { display: flex; flex-direction: column; gap: var(--site-space-x); }
.spec-hero { align-items: flex-start; }
/* macro: the eyebrow sits on its own line and the three cards form a row under
   it. Each card is still its own [data-reveal] wrapper, so they keep entering
   one beat apart across the row rather than as a single block. */
.spec-macro { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--site-space-x); align-content: start; }
.spec-macro > .spec-el:first-child { grid-column: 1 / -1; }
/* The wrapper is the grid item, so the card inside it has to be told to fill
   it; otherwise each card is only as tall as its own note and the row reads as
   three cards of three different heights. */
.spec-macro > .spec-el { display: flex; }
.spec-macro .spec-card { flex: 1 1 auto; }
.spec-tags { display: flex; flex-wrap: wrap; gap: var(--site-space-half-x); }
/* The role tag, which visual-system.json specifies under component.tag: a full
   round, the accent on its own tint, on the kit's own padding values. The page
   had no class for it yet, so this is that spec rendered rather than a new
   shape invented for this demo. */
.tag {
  display: inline-flex;
  align-items: center;
  font-family: var(--site-font-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--site-accent);
  background: var(--site-accent-tint);
  border-radius: var(--radius-pill);
  padding: var(--tag-padding-block) var(--tag-padding-inline);
}
.spec-metarow {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--site-space-x);
  padding-bottom: var(--site-space-half-x);
  border-bottom: var(--rule);
}
.spec-metarow__k {
  font-family: var(--site-font-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--site-neutral-70);
}
.spec-metarow__v { font-size: var(--site-text-p3); color: var(--site-ink); }

/* macro: content cards, the context the token names. */
.spec-card {
  border: var(--rule);
  padding: var(--site-space-x);
  background: var(--site-bg-2);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.spec-card__name {
  font-family: var(--site-font-body);
  font-weight: var(--site-weight-body-bold);
  font-size: var(--site-text-p2);
  color: var(--site-ink);
  margin: 0;
}
.spec-card__kind {
  font-family: var(--site-font-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--site-neutral-70);
  margin: 0;
}
.spec-card__note { margin: var(--site-space-half-x) 0 0; }


/* ── 04 stagger ─────────────────────────────────────────────────────────── */
/* Four intervals, four situations, one stage. Selecting an interval swaps the
   composition under it and replays; duration, easing and lift hold across all
   four, so the only variable the reader is shown is the gap between siblings.

   The whole demo is bordered as one specimen, the same way 03's stage is. */
.sg-demo {
  border: var(--rule);
  background: var(--site-bg-2);
  display: flex;
  flex-direction: column;
}
/* The tab strip reuses .speed-btn's treatment (the easing stage's own state
   control), so this section introduces no new control pattern. */
/* The tab strip and the replay sit on one row, so the control that repeats the
   animation is beside the controls that choose it rather than under the whole
   module. The tabs take the room; replay takes only what it needs, which is
   what keeps the primary interaction primary. */
.sg-bar { display: flex; align-items: stretch; border-bottom: var(--rule); }
.sg-bar .sg-tabs { flex: 1 1 auto; border-bottom: 0; }
/* Only the frame differs here: the bar supplies the button's edges, and the
   background is left to .replay so its hover is not overridden. */
.sg-bar .replay { border: 0; border-left: var(--rule); flex: 0 0 auto; }
.sg-tabs { display: flex; border-bottom: var(--rule); }
.sg-tab {
  flex: 1 1 0;
  font-family: var(--site-font-mono);
  font-style: normal;
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  padding: var(--site-space-x) var(--site-space-half-x);
  background: var(--site-bg-2);
  text-align: center;
}
.sg-tab + .sg-tab { border-left: var(--rule); }
/* The name under the interval is set in the kit's own muted ink rather than
   in a fraction of the current colour: opacity against this ground drops the
   pair below 4.5:1, and the token is a value the palette has already cleared.
   On the filled active tab it inherits instead, since the muted ink would be
   read against ink there. */
/* Concept first, value second: the tier is the thing being chosen and the
   number is what it measures. The name takes the label row's own uppercase and
   the value sits under it in the muted ink, which is the same relationship the
   caption below the stage uses. */
.sg-tab__val {
  display: block;
  font-size: var(--text-eyebrow);
  letter-spacing: 0.02em;
  text-transform: none;
  margin-top: 2px;
  color: var(--site-neutral-70);
}
.sg-tab.is-active .sg-tab__val { color: inherit; }

/* The stage's own height is reserved, so switching a scenario never moves the
   page under the reader. Each scene is absolutely positioned inside it and the
   tallest one sets the floor via min-height; the value is the campaign hero's
   natural height at the desktop measure, which is the tallest of the four.
   Scenes are stacked rather than swapped in and out of the document, so no
   scene's own layout can reflow the stage as it enters.

   The height is taken from the scenes themselves rather than from a number
   written here: every scene occupies the same single grid cell, so the cell is
   as tall as the tallest of them and the stage reserves exactly that at every
   breakpoint. A hand-set min-height had to be re-guessed for each breakpoint
   and clipped a scene whenever the content or the type ramp moved under it. */
.sg-stage {
  display: grid;
  grid-template-areas: "scene";
  padding: var(--site-space-2x);
  background: var(--site-bg);
  border-bottom: var(--rule);
}
.sg-scene {
  grid-area: scene;
  display: flex;
  flex-direction: column;
  gap: var(--site-space-x);
  min-width: 0;
}
/* Every scene stays in the grid so it keeps contributing its height; only the
   selected one is painted and reachable. visibility rather than display, so a
   hidden scene cannot be tabbed into or read out while it still holds the
   stage open. */
.sg-scene[hidden] { visibility: hidden; pointer-events: none; }
/* 05 has no scenarios to switch between, so its replay sits on the label row
   at the head of the specimen: the same relationship as the bar above, without
   inventing a tab strip that would have nothing in it. */
.sg-scene__head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--site-space-x);
  margin-bottom: var(--site-space-x);
}
.sg-scene__head .sg-scene__label { margin-bottom: 0; }
.sg-scene__label {
  font-family: var(--site-font-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--site-neutral-70);
  margin: 0 0 var(--site-space-x);
}
.sg-el { will-change: transform, opacity; }

/* simultaneous: skeleton rows, the one context with no story to tell. */
/* A loading state for a real screen: a title and its metadata, a row of
   summary figures, and a table of results still arriving. Three siblings, all
   entering at the same moment, because 0ms is what this token means. */
.sg-skeleton { display: flex; flex-direction: column; gap: var(--site-space-2x); }
.sk-bar { background: var(--site-bg-2); border: var(--rule); display: block; height: 14px; }

.sk-head { display: flex; flex-direction: column; gap: var(--site-space-half-x); }
.sk-bar--title { height: 26px; width: 44%; }
.sk-bar--meta { height: 12px; width: 26%; }

.sk-figs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--site-space-x); }
.sk-fig {
  display: flex;
  flex-direction: column;
  gap: var(--site-space-half-x);
  border: var(--rule);
  padding: var(--site-space-x);
}
.sk-bar--figlabel { height: 10px; width: 60%; }
.sk-bar--figval { height: 20px; width: 40%; }

/* The rows sit on the hairline the kit's own tables use, so the block reads as
   a table waiting for its data rather than as a stack of loose bars. */
.sk-table { display: flex; flex-direction: column; }
.sk-tr {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--site-space-x);
  align-items: center;
  padding: var(--site-space-x) 0;
  border-top: var(--rule);
}
.sk-tr:last-child { border-bottom: var(--rule); }
.sk-bar--c1 { width: 70%; }
.sk-bar--c2 { width: 50%; }
.sk-bar--c3 { width: 40%; justify-self: end; }

/* default: cards over the rows that follow them, which is what the token's own
   "cards, lists and UI reveals" describes. */
.sg-list { display: flex; flex-direction: column; }
.sg-cardrow { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--site-space-x); margin-bottom: var(--site-space-x); }
.sg-listrow {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--site-space-x);
  padding: var(--site-space-x) 0;
  border-top: var(--rule);
}
.sg-list .sg-el:last-child .sg-listrow { border-bottom: var(--rule); }
.sg-listrow__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.sg-listrow__note { font-size: var(--site-text-p3); color: var(--site-neutral-70); margin: 0; }
.sg-listrow__name {
  font-family: var(--site-font-body);
  font-weight: var(--site-weight-body-bold);
  font-size: var(--site-text-p2);
  color: var(--site-ink);
  margin: 0;
}
.sg-listrow__meta {
  font-family: var(--site-font-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--site-neutral-70);
  margin: 0;
}

/* feature: a collection, where each item wants to register on its own. */
.sg-collection { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--site-space-x); }
.sg-tile { display: flex; flex-direction: column; gap: var(--site-space-half-x); }
/* A capped height rather than aspect-ratio alone: four tiles across a narrowing
   grid get proportionally taller as each one narrows, and an uncapped ratio
   pushed the collection past twice the height of every other scenario, which
   the stage would then have to reserve for all four. */
/* A capped height rather than aspect-ratio alone: four tiles across a
   narrowing grid grow proportionally taller as each one narrows, and an
   uncapped ratio pushed the collection to twice the height of every other
   scenario, which the shared stage would then have to reserve for all four. */
.sg-tile__art {
  aspect-ratio: 4 / 3;
  max-height: 300px;
  border: var(--rule);
  background: var(--site-bg-2);
  overflow: hidden;
}
.sg-tile__art img { display: block; width: 100%; height: 100%; object-fit: cover; }
.sg-tile__cap {
  font-family: var(--site-font-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--site-neutral-70);
  margin: 0;
}

/* narrative: the campaign hero, the most deliberate cadence in the system. */
/* Copy on the left, the image on the right, at the ratio a hero in this system
   runs rather than a forced half-and-half. The two sit on the same top edge and
   the column fills the stage, so the composition reads as a hero rather than a
   text block in an oversized box. */
.sg-hero-mod {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--site-space-2x);
  align-items: center;
}
.sg-hero {
  display: flex;
  flex-direction: column;
  gap: var(--site-space-x);
  align-items: flex-start;
  justify-content: center;
}
.sg-hero__art { margin: 0; min-height: 0; max-height: 480px; overflow: hidden; border: var(--rule); background: var(--site-bg-2); align-self: center; }
.sg-hero__art img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* The art is a [data-reveal] wrapper's child, so the wrapper has to fill the
   grid cell for the image to have a height to cover. */
.sg-hero-mod > .sg-el { display: flex; min-width: 0; }
.sg-hero-mod > .sg-el > .sg-hero__art { flex: 1 1 auto; width: 100%; }
.sg-hero__eyebrow { color: var(--site-accent); margin: 0; }
.sg-hero__display {
  font-family: var(--site-font-display);
  font-weight: var(--site-weight-display);
  font-size: var(--site-text-h1);
  line-height: var(--site-leading-display);
  letter-spacing: var(--site-tracking-h1);
  color: var(--site-ink);
  margin: 0;
}
.sg-hero__lede { max-width: 44ch; margin: 0; }
/* The kit's own button, shown rather than operated: it is part of the
   composition being revealed, not a control the reader is meant to press. */
/* The text link the composed entrance's cards use, not the filled button. On
   this near-white ground the button's own white ground read as a slab laid on
   the page rather than as the way out of the hero.

   Ink rather than the accent, which is also what those cards set it in: the
   eyebrow already spends the composition's one accent, and a second red at the
   foot of the same block put two competing marks at either end of it. */
.sg-hero__cta { cursor: default; }

.sg-captions { padding: var(--site-space-2x); border-bottom: var(--rule); }
/* The stage and the caption panel carry a bottom rule to divide themselves
   from whatever sits under them inside the frame. Whichever one ends the frame
   has nothing under it, and its rule landed a hairline inside the container's
   own border, reading as a doubled edge. The last panel drops it, so the frame
   is drawn once by the frame. */
.sg-demo > *:last-child { border-bottom: 0; }
.sg-caption[hidden] { display: none; }
/* The value and what the tier is for share the first line; the reason sits
   under them. Reading the head alone answers "which one is this and when do I
   use it", which is what most readers of a guideline actually want. */
.sg-caption__head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--site-space-half-x) var(--site-space-x);
  margin: 0;
}
.sg-caption__val {
  font-family: var(--site-font-display);
  font-weight: var(--site-weight-display);
  font-size: 22px;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--site-ink);
}
.sg-caption__role {
  font-family: var(--site-font-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--site-neutral-70);
}
.sg-caption__body {
  font-size: var(--site-text-p3);
  color: var(--site-neutral-70);
  max-width: 68ch;
  margin: var(--site-space-half-x) 0 0;
}


/* ── 05 composed reveal ─────────────────────────────────────────────────── */
/* The team's own graphite portraits, entering as one composed reveal: the
   same opacity + lift + stagger composition the brand's own motion-guidelines
   deployment demonstrates on this exact set of cards. */
/* Four across on a wide measure, dropping to 2x2 at 1200px and one column at
   560px on the page's own breakpoints. The cards wrap down rather than run off
   to the right, and DOM order carries the stagger unchanged at every width:
   left to right, top to bottom, whatever the row length. */
.reveal-demo { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--site-space-2x); }
/* The card asks about its own width rather than the window's. Whether a name
   fits on one line depends on how wide the card ended up, and that is a
   different question at four across, at two, and at one: a viewport breakpoint
   answering it has to guess at the stage's padding and the page's margins, and
   guesses wrong the moment either changes. */
.reveal-card {
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  gap: var(--site-space-x);
}
.reveal-card__img { width: 100%; aspect-ratio: 1 / 1; background: var(--site-bg-2); border: var(--rule); overflow: hidden; }
.reveal-card__img img { display: block; width: 100%; height: 100%; object-fit: cover; }
.reveal-card__body { display: flex; flex-direction: column; gap: var(--site-space-half-x); }
/* A card-scale name, not the kit's section-heading .h3: at that size "Alan
   Tomusiak, PhD" wraps to two lines in this narrower column while shorter
   names sit on one, reading as uneven. One size down keeps every name on a
   single line without needing per-card overrides. */
.reveal-card__name {
  font-family: var(--site-font-body);
  font-weight: var(--site-weight-body-bold);
  font-size: var(--site-text-p1);
  line-height: var(--site-leading-display);
  letter-spacing: var(--site-tracking-h3);
  margin: 0;
}
/* Below the width the longest name needs, it takes one step down rather than
   wrapping and pulling the role and View Bio under it off the shared baseline
   the row reads across. */
@container (max-width: 215px) {
  .reveal-card__name { font-size: var(--site-text-p2); }
}
@container (max-width: 170px) {
  .reveal-card__name { font-size: var(--site-text-p3); }
}
.reveal-card .link { margin-top: var(--site-space-half-x); }

/* ── the scroll cue ─────────────────────────────────────────────────────── */

/* The affordance the system's own site closes its first screen with: a label
   and a single rule running down away from it. */
.scroll-cue {
  display: inline-flex;
  align-items: center;
  gap: var(--site-space-x);
  color: inherit;
}
.scroll-cue::after {
  content: "";
  width: var(--site-rule-weight);
  height: var(--site-space-2x);
  background: currentColor;
  opacity: 0.6;
}

/* ── grain ──────────────────────────────────────────────────────────────── */

/* A system whose graphic language is pigment and noise reads as flat digital
   design without a texture under it. The overlay sits above the ground and
   below the type, takes no pointer events, and is off unless the brand sets
   --site-grain. */
.band--dark { position: relative; isolation: isolate; }
.band--dark > * { position: relative; z-index: 1; }
.band--dark::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: var(--site-grain, none);
  opacity: var(--site-grain-opacity, 0);
  mix-blend-mode: var(--site-grain-blend, multiply);
}

/* ── footer ─────────────────────────────────────────────────────────────── */

/* The page's closing field. It carries the wordmark and
   nothing else: the mark spans the band between the side margins, and one
   measure holds it off all four sides, so it sits centered in the band. */
.foot { background: var(--site-foot-bg, var(--site-accent)); color: var(--site-foot-ink, var(--site-ink)); }
.foot__inner { padding: var(--margin); }
.foot__wordmark { display: block; width: 100%; height: auto; }

/* ── responsive ─────────────────────────────────────────────────────────── */

/* A phone layout is its own surface, so it sets its own sizes. */
@media (max-width: 1200px) {
  :root {
    --gcols: 2;
    --gspan-loose: 1;
    --text-h3: 20px;
    --text-eyebrow: 11px;
    --text-h1: clamp(32px, 8vw, 44px);
    --tile: 56px;
    --rail-w: 100%;
  }
  /* A phone layout is its own surface, so it sets its own sizes. The rail lays
     down into a bar across the top and the sections move behind a toggle. */
  .shell { flex-direction: column; align-items: stretch; }
  main { width: 100%; }
  .rail {
    height: auto;
    flex: none;
    flex-direction: row;
    align-items: center;
    padding: var(--site-space-x) 0;
    border-right: 0;
    border-bottom: var(--rule);
  }
  .cover { margin-left: 0; width: 100%; padding-left: 0; }
  .cover__meta { left: var(--margin); }
  .rail__toggle { display: inline-block; margin-left: auto; }
  .rail__nav {
    display: none;
    position: absolute;
    top: var(--tile);
    left: 0;
    right: 0;
    padding: var(--site-space-2x) var(--margin);
    background: var(--site-rail-bg, var(--site-bg));
    border-bottom: var(--rule);
  }
  .rail__nav.is-open { display: flex; }

  .rail__link { padding-left: 0; }
  .rail__link::before { display: none; }
  .band__inner { padding-top: var(--site-space-6x); padding-bottom: var(--site-space-6x); }
  /* A two-up cell below this width is narrower than the words in it: headings
     and eyebrows were overflowing their own cells from 900 down to 560, where
     the collapse used to happen. Everything goes single column here. */
  .cols--2, .cols--3, .cols--4, .cols--5, .icon-row { grid-template-columns: minmax(0, 1fr); }
  .index { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: var(--site-space-2x); }
  .cols--ruled > * { border-left: 0; padding-left: 0; }
  .cols--flush .shot { margin-left: 0; }
  .module, .card-row { grid-template-columns: minmax(0, 1fr); }
  .card { border-left: 0; padding-left: 0; border-top: var(--rule); }
  .card:first-child { border-top: 0; }
  .row { grid-template-columns: minmax(0, 1fr); gap: var(--site-space-half-x); }
  .row { grid-template-columns: minmax(0, 1fr); }
  .row--space { grid-template-columns: minmax(0, 1fr); }
  .row .copy__hint { justify-self: start; }
  .tier { grid-template-columns: minmax(0, 1fr); gap: var(--site-space-half-x); }
  .face__name { font-size: 40px; }
  .specimen { padding: var(--site-space-2x); }
  .module__text { padding: var(--site-space-2x); }
  .motion-row { grid-template-columns: minmax(0, 1fr); gap: var(--site-space-2x); }
  .easing-grid { grid-template-columns: minmax(0, 1fr); border-right: 0; }
  .easing-cell { border-left: 0; border-right: 0; border-top: var(--rule); }
  .easing-cell:first-child { border-top: 0; }
  .spec-macro { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .bhv-grid { grid-template-columns: minmax(0, 1fr); }
  .reveal-demo { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .sg-captions { padding-left: var(--site-space-2x); padding-right: var(--site-space-2x); }
  .sg-collection { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .spec-display { font-size: var(--site-text-h2); }
}

@media (max-width: 560px) {
  :root { --gcols: 1; --gspan-loose: 1; }
  .cols--2, .cols--3, .cols--4, .cols--5, .icon-row { grid-template-columns: minmax(0, 1fr); }
  .index { grid-template-columns: minmax(0, 1fr); }
  .cols--ruled > * { border-left: 0; padding-left: 0; }
  .cols--flush .shot { margin-left: 0; }
  .reveal-demo { grid-template-columns: minmax(0, 1fr); }
  /* 04's four tabs stop fitting on one line here, so they wrap to a 2x2 grid
     rather than shrinking their labels past reading size. Scoped to that demo:
     03 has three tabs, which still fit, and wrapping them would leave a lone
     tab on a second row. */
  [data-stagger-demo] .sg-tabs { flex-wrap: wrap; }
  [data-stagger-demo] .sg-tab { flex: 1 1 50%; }
  [data-stagger-demo] .sg-tab:nth-child(3) { border-left: 0; }
  [data-stagger-demo] .sg-tab:nth-child(n + 3) { border-top: var(--rule); }
  /* 03's cards stack once two across would put the notes below reading width. */
  .spec-macro { grid-template-columns: minmax(0, 1fr); }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  /* Translation, scale and stagger delay drop across every demo in the Motion
     section; state still has to read, so a restrained, fast opacity change
     carries each one rather than dropping feedback entirely. */
  [data-reveal] {
    --reveal-lift: 0px;
    --reveal-delay: 0ms;
  }
  [data-reveal].is-in,
  .viewer__art.is-entering { animation-duration: var(--dur-react-in); }
  /* The drawing still fades, so the swap reads; it no longer travels. */
  .viewer__art.is-entering { --reveal-lift: 0px; }
  .cta:active,
  .replay:active,
  .copy:active .copy__hint,
  .seg__opt:active:not(:disabled),
  .switch:active:not(:disabled),
  .viewer__get:active,
  .tokdl:active,
  .tokfmt__opt:active,
  .tokens tbody tr:active { transform: none; }

  /* Easing carousel: the puck still fades to mark which cell is active
     (state has to read), but no longer slides across the track. The
     autoplay loop itself already starts paused under this preference
     (script.js checks the same media query), so this only covers what
     happens if a reader clicks a cell by hand. */
  .easing-box { animation-duration: var(--dur-react-in) !important; left: 0 !important; }

  /* 03 and 04 set --reveal-lift and --reveal-delay per element, inline, so the
     token overrides at the top of this block cannot reach them: an inline
     custom property beats a stylesheet one whatever the selector. The used
     values are therefore overridden directly here instead.

     Both specimens then arrive whole, immediately, and in one piece: no
     element travels, and none waits its turn behind another. A section whose
     subject is distance and cadence has nothing to demonstrate under this
     preference, so it shows the composition rather than performing it.

     03's annotation still names the distance in text beside the specimen, so
     the value stays readable when the movement itself is suppressed. */
  #demo-distance [data-reveal],
  #demo-stagger [data-reveal] {
    /* The lift is neutralised at its source rather than with a transform
       override: dr-rise interpolates translateY(var(--reveal-lift)), and a
       running animation outranks any transform declared on the element, so
       zeroing the property the keyframes read is what actually stops the
       move. It is set on the element, which is where the inline value the
       markup wrote also lives, and !important carries it over that. */
    --reveal-lift: 0px !important;
    --reveal-delay: 0ms !important;
    animation-delay: 0ms !important;
    animation-duration: var(--dur-react-in) !important;
  }

}

/* ── the viewer ─────────────────────────────────────────────────────────── */

/* The section's artifact is the stage, and everything under it is one
   instrument. Four bordered boxes in a row read as four unrelated things and
   left dead space inside the shorter ones; the rail is the box now, and a
   control is a column inside it divided by a hairline. The three parts of
   every column (label, options, reason) sit on shared rows, so the row
   reads across as well as down.

   A selector is not a copy button. A selector holds state, so it is drawn at
   rest, its options are always readable, and the current one is filled.
   Nothing here appears on hover that was not already there. */

.viewer { display: grid; gap: var(--site-space-2x); }

/* The panel beside the stage, not under it. Stacked under the stage, the
   controls and the artwork they change could not be seen at once: pressing a
   control meant scrolling up to see what it did. The panel is a fixed column
   on the left and the stage takes the rest, so the whole instrument sits on
   one screen and the section is read without scrolling inside it. */
.viewer--split {
  grid-template-columns: minmax(0, 34rem) minmax(0, 1fr);
  align-items: stretch;
}
.viewer--split .viewer__stage { min-height: 0; }

/* Inside the panel the four fields sit two by two rather than in one tall
   column. Stacked, the panel ran past the foot of the screen and took the
   stage's height with it, which is the defect this composition exists to fix. */
.viewer--split .viewer__controls { grid-template-columns: 1fr 1fr; }
.viewer--split .seg { border-top: 0; }
.viewer--split .seg:nth-child(even) { border-left: var(--rule); }
.viewer--split .seg:nth-child(n+3) { border-top: var(--rule); }

.viewer__stage {
  position: relative;
  min-height: calc(var(--site-space-12x) + var(--site-space-6x));
  display: grid;
  place-items: center;
  padding: var(--site-space-6x);
  border: var(--rule);
  border-radius: var(--radius-card);
  background: var(--off-white);
  background-size: cover;
  background-position: center;
  overflow: hidden;
  transition: background-color var(--dur-react-out) var(--ease-standard);
}
/* One box for every form. The four drawings are wildly different shapes: the
   horizontal lockup is 1400x273, the logomark is square, the wordmark is a
   1017x65 strip. Sizing each to its own bounds made the stage jump on every
   click. The box is fixed and the drawing is contained inside it, so switching
   form changes the artwork and nothing else. */
.viewer__art {
  width: 100%;
  max-width: 30rem;
  height: calc(var(--site-space-12x) + var(--site-space-2x));
  object-fit: contain;
  display: block;
  position: relative;
  z-index: 1;
}
.viewer__stage[data-ground="imagery"] .viewer__art { filter: brightness(0) invert(1); }

/* Pressing a control replaces the drawing outright, so the new file arrives
   the way every other reveal on the page arrives rather than cutting: the
   page's own dr-fade and dr-rise keyframes, off the micro lift, over the
   slower react duration because this is a result rather than a hover. It has
   to be an animation and not a transition for the reason written against
   [data-reveal] above: a replayed transition computes a nearly invisible
   reverse from wherever the element already was. script.js restarts it the
   same way the demos do. */
.viewer__art {
  --reveal-lift: var(--lift-micro);
}
.viewer__art.is-entering {
  animation:
    dr-fade var(--dur-react-out) var(--ease-neutral) forwards,
    dr-rise var(--dur-react-out) var(--ease-enter) forwards;
}

/* The deck labels its artifacts tiny, uppercase and centered, low in the
   frame. The plate does that and adds the one thing a page can do that the
   printed deck cannot: hand over the file it is showing. The caption that used
   to sit above the button named the form and the step, which the control panel
   beside the stage already shows filled, so the plate is the download alone. */
.viewer__plate {
  position: absolute;
  z-index: 1;
  left: var(--site-space-2x);
  right: var(--site-space-2x);
  bottom: var(--site-space-2x);
  display: grid;
  justify-items: center;
  text-align: center;
}
.viewer__get {
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--site-ink);
  padding: var(--site-space-half-x) var(--site-space-x);
  border: var(--rule);
  border-radius: var(--radius-pill);
  background: var(--site-bg-2);
  transition: background-color var(--dur-react-out) var(--ease-standard),
              color var(--dur-react-out) var(--ease-standard),
              transform var(--dur-press) var(--ease-standard);
}
.viewer__get:hover {
  background: var(--site-ink);
  color: var(--off-white);
  transition-duration: var(--dur-react-in);
}
.viewer__get:active { transform: scale(0.98); transition-duration: var(--dur-press); }
.viewer__get:focus-visible { outline: 2px solid var(--site-accent); outline-offset: 2px; }
/* On imagery the ground is dark under the plate, so the label and the pill
   invert with the mark rather than sinking into the picture. The scrim is the
   one the cover already uses: short, from the foot, holding the type
   off the artwork without washing the artwork out. */
.viewer__stage[data-ground="imagery"]::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(to top,
    color-mix(in srgb, var(--site-ink) 55%, transparent) 0%,
    transparent 30%);
}
/* The zone is drawn in the accent, which is a dark red and disappears against
   a dark picture. On imagery it goes white with the mark. */
.viewer__stage[data-ground="imagery"] .viewer__clear {
  border-color: var(--off-white);
  background: color-mix(in srgb, var(--off-white) 10%, transparent);
}
.viewer__stage[data-ground="imagery"] .viewer__get {
  color: var(--off-white);
  border-color: var(--off-white);
  background: color-mix(in srgb, var(--site-ink) 45%, transparent);
}
.viewer__stage[data-ground="imagery"] .viewer__get:hover {
  background: var(--off-white);
  color: var(--site-ink);
}

/* The clear-space zone is 1X on every side, where X is the wordmark height.
   The box is drawn around the artwork rather than described in a caption. */
.viewer__clear {
  position: absolute;
  border: 1px dashed var(--site-accent);
  background: color-mix(in srgb, var(--site-accent) 6%, transparent);
  pointer-events: none;
  z-index: 0;
}

/* The minimum-size proof sits inside the stage, at its head. Below the stage
   it was a second box that pushed the controls off the screen; the rule it
   states is about the artwork, so it is drawn on the artwork's own ground. */
.viewer__min[hidden] { display: none; }
.viewer__min {
  position: absolute;
  z-index: 1;
  top: var(--site-space-2x);
  left: var(--site-space-2x);
  right: var(--site-space-2x);
  display: flex;
  align-items: center;
  gap: var(--site-space-2x);
  padding: var(--site-space-x) var(--site-space-2x);
  border: var(--rule);
  border-radius: var(--radius-card);
  background: var(--site-bg-2);
}
.viewer__min img { max-width: 50%; object-fit: contain; }
.viewer__minlabel {
  font-size: var(--site-text-p3);
  color: var(--site-neutral-70);
  margin: 0;
}

/* The rail. One border, one radius, four columns, hairlines between them. The
   three rows are declared here and every field takes them by subgrid, so the
   labels sit on one line, the option stacks start on one line, and the reasons
   sit on one line however many options a field holds. */
/* The column count is set at each width rather than left to auto-fit, because
   the seam between two fields is a hairline this rule has to draw: a wrapped
   field needs a rule above it and none to its left, and auto-fit gives no way
   to say which field wrapped. */
.viewer__controls {
  display: grid;
  align-content: start;
  border: var(--rule);
  border-radius: var(--radius-card);
  background: var(--site-bg-2);
  overflow: hidden;
}

.seg {
  display: grid;
  align-content: start;
  border-top: var(--rule);
  padding: var(--site-space-x) 0;
}
.seg:first-child { border-top: 0; }

/* Under 900px the two columns become two rows: the panel first, the stage
   under it. The pairing is lost at that width whatever the order, and the
   controls are what the reader steers with. */
@media (max-width: 900px) {
  .viewer--split { grid-template-columns: minmax(0, 1fr); }
  .viewer--split .viewer__stage { min-height: 22rem; }
}

.seg__label {
  display: flex;
  align-items: baseline;
  gap: var(--site-space-half-x);
  flex-wrap: wrap;
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--site-neutral-70);
  margin: 0 0 var(--site-space-half-x);
  padding: 0 var(--site-space-2x);
  /* Two lines, whether or not the state tag is on the label. The tag wraps the
     label in its column, so a field that gained one grew by a line and moved
     the panel and the stage beside it. The room is held either way. */
  min-height: 3.75em;
  align-content: start;
}
/* The tag says what the state is on the field itself, so an unavailable
   control never has to be explained by its own greyness. */
.seg__state { color: var(--site-accent); }
.seg__state[hidden] { display: none; }

/* One option per row. The rail carries the border, so a field's options run
   edge to edge inside their column and the four stacks line up. */
.seg__set { display: grid; align-content: start; }
.seg__opt {
  appearance: none;
  cursor: pointer;
  font: inherit;
  font-size: var(--site-text-p3);
  text-align: left;
  /* An option is a label on a control, not prose: "Horizontal lockup" was
     breaking across two lines inside its own row. */
  white-space: nowrap;
  display: flex;
  align-items: center;
  gap: var(--site-space-x);
  width: 100%;
  padding: var(--site-space-half-x) var(--site-space-2x);
  border: 0;
  background: none;
  color: var(--site-ink);
  transition: background-color var(--dur-react-out) var(--ease-standard),
              color var(--dur-react-out) var(--ease-standard),
              transform var(--dur-press) var(--ease-standard);
}
.seg__opt:hover:not(:disabled):not(.is-on) { background: var(--dark-beige); transition-duration: var(--dur-react-in); }
.seg__opt:active:not(:disabled) { transform: scale(0.98); transition-duration: var(--dur-press); }
.seg__opt.is-on { background: var(--site-ink); color: var(--off-white); }
.seg__opt:focus-visible { outline: 2px solid var(--site-accent); outline-offset: -2px; }

/* The drawing an option puts on the stage, beside its name. Every piece of the
   system is therefore on screen at once: the panel is the set, the stage is
   the one in hand. The box is fixed and the drawing is contained inside it, so
   a 1400x273 lockup and a square logomark sit on the same baseline. The
   selected option inverts, and its drawing inverts with it. */
.seg__thumb {
  width: 2.75rem;
  height: 1.25rem;
  flex: none;
  object-fit: contain;
  object-position: left center;
}
.seg__opt.is-on:not(:disabled) .seg__thumb { filter: brightness(0) invert(1); }
.seg__opt:disabled .seg__thumb { opacity: 0.45; }
.seg__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* A field whose every option is a drawing is a set, not a list: two tiles to a
   row, the drawing over its name. Four pieces then take two rows instead of
   four, which is what lets the panel stand beside the stage inside one
   screen, and the drawings come out large enough to tell apart. */
.seg--tiles .seg__set {
  grid-template-columns: 1fr 1fr;
  gap: 1px;
  padding: 0 var(--site-space-x);
}
.seg--tiles .seg__opt {
  flex-direction: column;
  align-items: stretch;
  gap: var(--site-space-half-x);
  white-space: normal;
  padding: var(--site-space-half-x) var(--site-space-x);
  border-radius: var(--radius-card);
}
.seg--tiles .seg__thumb {
  width: 100%;
  height: 1.75rem;
  object-position: center;
}
.seg--tiles-3 .seg__set { grid-template-columns: repeat(3, 1fr); }
.seg--tiles .seg__name { text-align: center; overflow: visible; }

/* A ground is a color, so the option shows the color rather than naming it
   twice. The chip is the tile's drawing: same box, same tile. */
.seg__chip {
  display: block;
  height: 1.75rem;
  border: var(--rule);
  border-radius: calc(var(--radius-card) / 2);
}

/* Unavailable, not broken. The old state faded the whole column to 35% and
   read as a rendering fault; the reader could not tell a disabled control from
   a dead one. The options stay at reading contrast, the field takes a hatch
   that says "not now", and the reason replaces the hint underneath. */
.seg[data-unavailable] .seg__set {
  background: repeating-linear-gradient(
    -45deg,
    transparent 0 5px,
    color-mix(in srgb, var(--site-neutral-70) 12%, transparent) 5px 6px);
}
.seg__opt:disabled { cursor: not-allowed; color: var(--site-neutral-70); }
.seg__opt:disabled.is-on { background: none; color: var(--site-neutral-70); }

.seg__hint {
  font-size: var(--site-text-p3);
  color: var(--site-neutral-70);
  margin: var(--site-space-half-x) 0 0;
  padding: 0 var(--site-space-2x);
  align-self: start;
}
.seg[data-unavailable] .seg__hint { color: var(--site-neutral-70); }

/* The overlays are the same field in the same rail, so the row reads as four
   of one thing. They differ where it matters: a switch holds on or off and
   says which at rest, rather than being one of a set. A pressed switch is
   filled and its row is tinted, which is the same language the selected option
   speaks one column over, at the weight a toggle deserves. */
.switch {
  appearance: none;
  cursor: pointer;
  font: inherit;
  font-size: var(--site-text-p3);
  display: flex;
  align-items: center;
  gap: var(--site-space-x);
  width: 100%;
  text-align: left;
  white-space: nowrap;
  padding: var(--site-space-x) var(--site-space-2x);
  border: 0;
  background: none;
  color: var(--site-ink);
  transition: background-color var(--dur-react-out) var(--ease-standard),
              transform var(--dur-press) var(--ease-standard);
}
.switch:hover:not(:disabled) { background: var(--dark-beige); transition-duration: var(--dur-react-in); }
.switch:active:not(:disabled) { transform: scale(0.98); transition-duration: var(--dur-press); }
.switch:disabled { cursor: not-allowed; color: var(--site-neutral-70); }
.switch__box {
  width: 0.9em;
  height: 0.9em;
  flex: none;
  border: 1px solid currentColor;
  background: none;
  transition: background-color var(--dur-react-out) var(--ease-standard);
}
.switch[aria-pressed="true"] { background: var(--dark-beige); }
.switch[aria-pressed="true"] .switch__box { background: var(--site-ink); }
.switch:disabled .switch__box {
  border-style: dashed;
  background: none;
}
.switch:focus-visible { outline: 2px solid var(--site-accent); outline-offset: -2px; }

/* The per-form note lives in the Form control's own hint slot, so a form and
   what it is for are one thing rather than two. */
.viewer__note { margin: 0; }
.viewer__note[hidden] { display: none; }

/* ── round 3: artifact-first bands, the standard behind a section ─────── */

/* The band's own header rail, lifted from the brand's deck: the group name,
   tiny and mono. It runs above the work so nothing
   arrives unlabeled, and it carries none of the weight the title carries. */
.mark {
  display: flex;
  gap: var(--site-space-x);
  align-items: baseline;
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--site-neutral-70);
  padding-bottom: var(--site-space-6x);
}
.band--dark .mark { color: var(--site-feature-note, var(--site-neutral-30)); }

/* The artifact owns the band. It comes first and it is the largest thing. */
.stage {
  min-width: 0;
  display: grid;
  grid-template-columns: repeat(var(--gcols), minmax(0, 1fr));
  column-gap: 0;
  row-gap: var(--site-space-6x);
}
.stage > * { grid-column: 1 / -1; min-width: 0; }
.stage > .note, .stage > .lead, .stage > .statement {
  grid-column: 1 / span var(--gspan-column);
  padding-right: var(--site-space-2x);
}

/* The words land after the work, on one bottom band: title large on the left,
   body small and narrow beside it. Roughly a third the visual weight. */
/* The title heads its section. It sat under the artifact, on the deck's own
   caption-band pattern, and on a page a reader scrolls and jumps into that put
   every heading at the foot of the thing it named: landing on a section showed
   artwork with no title, and the title still on screen belonged to the section
   above. The rule that closed the band now opens it. */
.caption {
  display: grid;
  grid-template-columns: repeat(var(--gcols), minmax(0, 1fr));
  column-gap: 0;
  row-gap: var(--site-space-2x);
  padding-bottom: var(--site-space-6x);
  border-bottom: var(--rule);
}
.caption__title { grid-column: 1 / span var(--gspan-column); padding-right: var(--site-space-2x); }
.caption__copy {
  /* One measure, not two. The deck runs two narrow columns because it carries
     five times this much copy; at two or three lines a second column is an
     orphan, not a rhythm. */
  grid-column: span var(--gspan-loose) / -1;
}
.caption__copy .lead { font-size: var(--site-text-p3); color: var(--site-neutral-70); }
.band--dark .caption { border-color: var(--site-feature-rule, var(--site-neutral-80)); }
.band--dark .caption__copy .lead { color: var(--site-feature-note, var(--site-neutral-70)); }

/* The standard behind a section runs on in the same scroll. It used to fold
   behind a control, which put the rules a reader needs one click away from the
   overview they belong to. The label is the mono device the band header uses,
   so the run-on reads as a second register inside one section. */
.more { margin-top: var(--site-space-6x); }
.more__label {
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--site-neutral-70);
  border-top: var(--rule);
  padding-top: var(--site-space-2x);
}
.band--dark .more__label { color: var(--site-feature-note, var(--site-neutral-30)); }
.more__body {
  display: grid;
  grid-template-columns: repeat(var(--gcols), minmax(0, 1fr));
  column-gap: 0;
  row-gap: var(--site-space-6x);
  padding-top: var(--site-space-2x);
}
.more__body > * { grid-column: 1 / -1; min-width: 0; }
.more__body > .note, .more__body > .lead, .more__body > .statement {
  grid-column: 1 / span var(--gspan-column);
  padding-right: var(--site-space-2x);
}


/* The copy affordance is persistent everywhere now, matching the tokens table,
   which is the one place on the page that was never ambiguous. */
.copy__hint {
  opacity: 1;
  border: var(--rule);
  border-radius: 999px;
  padding: 0.15em 0.6em;
  display: inline-block;
  /* The pill is a label on a control, not prose. "Copy CSS" was breaking across
     two lines inside its own border wherever the column was narrow. */
  white-space: nowrap;
  /* The pill is a grid item, so it is stretched to its column rather than sized
     to its text, and the label was landing wherever the cell's alignment put it.
     The pill sizes to the label and the label is centred in it. */
  width: fit-content;
  text-align: center;
}
/* The pill carries the same reaction the controls do: a quick arrival, a
   slower leave, and a press that reads as depressed rather than recolored. The
   scale is on the pill alone, because a specimen row is the work and does not
   move when it is clicked. */
.copy .copy__hint {
  transition: opacity var(--dur-react-out) var(--ease-standard),
              background var(--dur-react-out) var(--ease-standard),
              color var(--dur-react-out) var(--ease-standard),
              transform var(--dur-press) var(--ease-standard);
}
.copy:hover .copy__hint,
.copy:focus-visible .copy__hint { transition-duration: var(--dur-react-in); }
.copy:hover .copy__hint { background: var(--site-ink); color: var(--site-bg); }
.copy:active .copy__hint { transform: scale(0.98); transition-duration: var(--dur-press); }
.copy.is-copied .copy__hint {
  background: var(--red-ochre-15);
  border-color: var(--site-accent);
  color: var(--site-ink);
}

@media (max-width: 900px) {
  .caption__title, .caption__copy { grid-column: 1 / -1; padding-right: 0; }
  .caption__copy { columns: 1; }
  .mark { flex-wrap: wrap; }
}

/* The chip's pill sat in its own grid column, which pushed it to the far edge
   of the cell and off the specimen it belongs to. It reads as part of the
   swatch only when it sits under the value. */
.chip__meta .copy__hint {
  grid-column: 1;
  grid-row: 3;
  justify-self: start;
  margin-top: var(--site-space-x);
}

/* The deck sets its titles against a short rule to the left. The caption title
   carries the same device, so the words at the foot of a band read as the
   deck's title block rather than as another heading. */
.caption__title {
  font-size: clamp(28px, 3vw, 44px);
  position: relative;
  padding-left: var(--site-space-2x);
}
.caption__title::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: var(--site-space-x);
  border-top: 1px solid var(--site-accent);
}

@media (max-width: 900px) {
  .display { grid-column: 1 / -1; padding-right: 0; }
}

/* A specimen is a stack: the thing being shown, and whatever labels it. */
.specimen { margin: 0; display: grid; row-gap: var(--site-space-x); }

/* ── the token families ─────────────────────────────────────────────────── */

/* The filter and the families it filters are one control, so they sit together
   rather than on the band's own wide rhythm. */
.tokset { display: block; }
/* The whole set is 110 values. Flat, it is a scroll; gathered into families and
   folded, it is a reference you can aim at. */

.tokfind {
  display: flex;
  align-items: center;
  gap: var(--site-space-x);
  margin-bottom: var(--site-space-2x);
}
.tokfind__label {
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--site-neutral-70);
}
.tokfind__input {
  flex: 1 1 auto;
  max-width: 22rem;
  font: inherit;
  font-size: var(--site-text-p3);
  padding: var(--site-space-x) var(--site-space-2x);
  border: var(--rule);
  border-radius: var(--radius-card);
  background: var(--site-bg-2);
  color: var(--site-ink);
}
.tokfind__input:focus-visible { outline: 2px solid var(--site-accent); outline-offset: 2px; }
.tokfind__count {
  margin: 0;
  font-size: var(--site-text-p3);
  color: var(--site-neutral-70);
}

.tokgroup { border-top: var(--rule); }
.tokgroup:last-of-type { border-bottom: var(--rule); }
.tokgroup[hidden] { display: none; }
.tokgroup__head {
  display: flex;
  align-items: center;
  gap: var(--site-space-x);
  cursor: pointer;
  list-style: none;
  padding: var(--site-space-2x) 0;
}
.tokgroup__head::-webkit-details-marker { display: none; }
.tokgroup__name { font-size: var(--site-text-p2); }
.tokgroup__count {
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  color: var(--site-neutral-70);
  margin-right: auto;
}
.tokgroup .tokens { margin-bottom: var(--site-space-2x); }

/* ── token downloads ────────────────────────────────────────────────────── */

/* The format choice is two labelled states with the current one filled, the
   treatment the page's own controls already use. It sits with the find field
   because it governs the whole set, not one family. */
.tokfmt { display: inline-flex; border: var(--rule); overflow: hidden; border-radius: var(--radius-card); }
.tokfmt__opt {
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  padding: var(--site-space-x) var(--site-space-2x);
  border: 0;
  background: var(--site-bg-2);
  color: var(--site-neutral-70);
  cursor: pointer;
  transition: background var(--dur-react-out) var(--ease-standard),
              color var(--dur-react-out) var(--ease-standard),
              transform var(--dur-press) var(--ease-standard);
}
.tokfmt__opt + .tokfmt__opt { border-left: var(--rule); }
.tokfmt__opt.is-on { background: var(--site-ink); color: var(--site-bg); }
.tokfmt__opt:hover:not(.is-on) { color: var(--site-ink); transition-duration: var(--dur-react-in); }
.tokfmt__opt:active { transform: scale(0.98); transition-duration: var(--dur-press); }

/* The download sits on the family's head row but outside its summary, so the
   fold and the download are two controls rather than one inside the other. The
   head reserves the space it occupies. */
.tokfam { position: relative; }
.tokfam[hidden] { display: none; }
/* The head keeps the fold cue at its own right edge and clears a lane for the
   download beside it, so the two controls sit on one line without overlapping. */
.tokfam .tokgroup__head { padding-right: 8.5rem; }
.tokfam .depth__cue { position: absolute; right: 0; }

/* The family control is a copy pill by another name: same persistent border,
   same fill on hover, same confirmation inside the control that was pressed. */
.tokdl {
  position: absolute;
  top: var(--site-space-2x);
  right: var(--site-space-2x);
  z-index: 1;
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  white-space: nowrap;
  padding: 0.15em 0.6em;
  border: var(--rule);
  border-radius: 999px;
  background: none;
  color: var(--site-neutral-70);
  cursor: pointer;
  transition: background var(--dur-react-out) var(--ease-standard),
              color var(--dur-react-out) var(--ease-standard),
              transform var(--dur-press) var(--ease-standard);
}
.tokdl:hover { background: var(--site-ink); color: var(--site-bg); transition-duration: var(--dur-react-in); }
.tokdl:active { transform: scale(0.98); transition-duration: var(--dur-press); }
.tokdl.is-copied, .tokdl-all.is-copied {
  background: var(--red-ochre-15);
  border-color: var(--site-accent);
  color: var(--site-ink);
}

/* The set-level button is the heavier of the two, because it carries the whole
   file. It is pushed to the end of the find row. */
.tokdl-all { margin-left: auto; }
.tokdl-all:disabled { opacity: 0.4; cursor: not-allowed; }

/* The find row carries four things now and wraps rather than crushing the
   input on a narrow window. */
.tokfind { flex-wrap: wrap; row-gap: var(--site-space-x); }


/* ── plates: the brand's own artwork, shown ─────────────────────────────── */
/* The deck opens every page on the work and puts the words underneath. These
   carry that into the sections that had none: the artwork at full width, a
   short caption under it in the label role. */

.plates { display: grid; gap: var(--site-space-2x); }
.plates--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.plates--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.plate { margin: 0; display: grid; gap: var(--site-space-x); }
/* The four art-direction plates ship at two different source ratios, and a grid
   of them reads as a mistake when one tile is shorter than its neighbor. The
   set takes the ratio the majority of the artwork is cut to, and the odd one
   is covered into it. */
.plates--even .plate img { aspect-ratio: 1200 / 802; object-fit: cover; }
.plate img {
  display: block;
  width: 100%;
  height: auto;
  border: var(--rule);
  border-radius: var(--radius-card);
}
.plate__cap {
  font-family: var(--site-font-mono);
  font-weight: var(--site-weight-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--site-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--site-neutral-70);
}

@media (max-width: 900px) {
  .plates--2, .plates--3 { grid-template-columns: minmax(0, 1fr); }
}

