/* ===========================================================================
   tomekk.pl — "Plate 01": the drawing sheet as interface.

   The page is treated as a single technical plate produced by a precision
   plotter: a calm paper-white (or backlit graphite) ground, hairline
   construction grids that appear only where they mean something, measurement
   ticks along structural edges, and monospace part numbers labelling every
   object. Each project is a specimen mounted on its own drawing sheet.

   Contents
     1. Tokens (light)            6. Hero
     2. Tokens (dark)             7. Collections + cards
     3. Reset + base              8. Placeholder drawings
     4. Utilities + primitives    9. Modal (expanded project)
     5. Header                   10. Footer
   =========================================================================== */

/* ===========================================================================
   1. TOKENS — light ("paper plate"). Declared on bare :root so this is the
      default in every context, including when no theme has been chosen.
   =========================================================================== */
:root {
  /* -- surfaces ---------------------------------------------------------- */
  --bg:              #F4F6F9;
  --surface:         #FFFFFF;
  --surface-sunken:  #EDF0F5;
  --surface-footer:  #EFF2F7;

  /* -- lines ------------------------------------------------------------- */
  --border:          #DDE3EC;
  --border-strong:   #838EA0;   /* 3.32:1 on --surface — WCAG 1.4.11 */
  --hairline:        #E7EBF2;

  /* -- ink --------------------------------------------------------------- */
  --text-primary:    #0F1720;   /* 16.6:1 on --bg */
  --text-secondary:  #4A5566;   /*  6.9:1 on --bg */
  --text-muted:      #626D7D;   /*  4.86:1 on --bg — AA at 11px mono */

  /* -- accent: marks the datum, nothing else ----------------------------- */
  --accent:          #4457D8;   /*  5.38:1 on --bg */
  --accent-hover:    #3646BE;
  --accent-soft:     #E9ECFC;
  --accent-line:     #B9C2F4;
  --on-accent:       #FFFFFF;   /*  5.82:1 on --accent */

  /* -- plotter ink inside the drawings ----------------------------------- */
  --viz-line:        #C7D0DE;
  --viz-panel:       #131822;   /* the 3D viewport, dark even in light mode */

  /* -- elevation --------------------------------------------------------- */
  --shadow-1:        0 1px 2px rgba(15, 23, 32, .05);
  --shadow-2:        0 12px 28px -12px rgba(15, 23, 32, .18), 0 2px 6px rgba(15, 23, 32, .05);
  --shadow-modal:    0 32px 80px -24px rgba(15, 23, 32, .32), 0 2px 8px rgba(15, 23, 32, .08);
  --shadow-cta:      0 6px 16px -8px rgba(68, 87, 216, .70);
  --backdrop:        rgba(11, 14, 20, .55);
  --header-bg:       rgba(244, 246, 249, .82);

  /* -- type -------------------------------------------------------------- */
  --font-display: "Space Grotesk", "Segoe UI", system-ui, sans-serif;
  --font-body:    "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-mono:    "JetBrains Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;

  --t-display:      clamp(2.25rem, 1.40rem + 3.4vw, 3.25rem);
  --t-lede:         clamp(1.125rem, 1.00rem + .55vw, 1.375rem);
  --t-h2:           clamp(1.625rem, 1.30rem + 1.3vw, 2rem);
  --t-modal-title:  clamp(1.5rem, 1.20rem + 1.2vw, 1.75rem);
  --t-h3:           1.125rem;
  --t-body-lg:      1.0625rem;
  --t-body:         1rem;
  --t-sm:           .9375rem;
  --t-nav:          .9375rem;
  --t-meta:         .8125rem;
  --t-micro:        .6875rem;
  --t-chip:         .75rem;

  /* -- space (4px ladder; nothing outside it) ---------------------------- */
  --s1: 4px;   --s2: 8px;   --s3: 12px;  --s4: 16px;  --s5: 20px;
  --s6: 24px;  --s7: 32px;  --s8: 40px;  --s9: 48px;  --s10: 56px;
  --s11: 64px; --s12: 80px; --s13: 96px; --s14: 120px;

  --container: 1200px;
  --header-h:  68px;

  /* -- radii: step down as things nest ----------------------------------- */
  --r-card: 14px;  --r-plate: 10px;  --r-inner: 6px;
  --r-chip: 6px;   --r-btn: 999px;   --r-control: 8px;  --r-modal: 18px;

  /* -- easing ------------------------------------------------------------ */
  --e-out:    cubic-bezier(.2, .8, .2, 1);
  --e-in-out: cubic-bezier(.65, 0, .35, 1);
  --e-in:     cubic-bezier(.4, 0, 1, 1);
  --e-lift:   cubic-bezier(.34, 1.2, .64, 1);

  color-scheme: light;
}

/* ===========================================================================
   2. TOKENS — dark ("backlit graphite"). A real second design, not an
      inversion: elevation becomes a top highlight rather than a drop shadow,
      the accent rises in luminance and the CTA inverts to dark-on-light, and
      the construction grids turn faintly emissive.

      Declared twice, identically, so the toggle wins in both directions.
   =========================================================================== */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:             #0E1116;
    --surface:        #151A21;
    --surface-sunken: #11151B;
    --surface-footer: #0B0E13;
    --border:         #262D38;
    --border-strong:  #566173;
    --hairline:       #1E252F;
    --text-primary:   #E8ECF2;
    --text-secondary: #A7B2C2;
    --text-muted:     #7A8798;
    --accent:         #8A9BFF;
    --accent-hover:   #A3B1FF;
    --accent-soft:    #1B2036;
    --accent-line:    #3B457A;
    --on-accent:      #0B0E13;
    --viz-line:       #39424F;
    --viz-panel:      #0B0E13;
    --shadow-1:       inset 0 1px 0 rgba(255, 255, 255, .03);
    --shadow-2:       0 16px 34px -16px rgba(0, 0, 0, .72), 0 0 0 1px rgba(138, 155, 255, .10);
    --shadow-modal:   0 40px 90px -30px rgba(0, 0, 0, .85);
    --shadow-cta:     0 6px 18px -8px rgba(138, 155, 255, .35);
    --backdrop:       rgba(4, 6, 9, .72);
    --header-bg:      rgba(14, 17, 22, .78);
    color-scheme: dark;
  }
}

:root[data-theme="dark"] {
  --bg:             #0E1116;
  --surface:        #151A21;
  --surface-sunken: #11151B;
  --surface-footer: #0B0E13;
  --border:         #262D38;
  --border-strong:  #566173;
  --hairline:       #1E252F;
  --text-primary:   #E8ECF2;
  --text-secondary: #A7B2C2;
  --text-muted:     #7A8798;
  --accent:         #8A9BFF;
  --accent-hover:   #A3B1FF;
  --accent-soft:    #1B2036;
  --accent-line:    #3B457A;
  --on-accent:      #0B0E13;
  --viz-line:       #39424F;
  --viz-panel:      #0B0E13;
  --shadow-1:       inset 0 1px 0 rgba(255, 255, 255, .03);
  --shadow-2:       0 16px 34px -16px rgba(0, 0, 0, .72), 0 0 0 1px rgba(138, 155, 255, .10);
  --shadow-modal:   0 40px 90px -30px rgba(0, 0, 0, .85);
  --shadow-cta:     0 6px 18px -8px rgba(138, 155, 255, .35);
  --backdrop:       rgba(4, 6, 9, .72);
  --header-bg:      rgba(14, 17, 22, .78);
  color-scheme: dark;
}

/* ===========================================================================
   3. RESET + BASE
   =========================================================================== */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 88px;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* The single most important overflow guard on the page. */
  overflow-x: clip;
}

h1, h2, h3, h4 { font-family: var(--font-display); margin: 0; line-height: 1.15; }
p  { margin: 0; }
ul, ol, dl, dd { margin: 0; padding: 0; }
li { list-style: none; }
img, svg, video { display: block; max-width: 100%; }
img { height: auto; }
button { font: inherit; color: inherit; background: none; border: 0; margin: 0; padding: 0; }
a { color: inherit; text-decoration: none; }

/* One focus treatment, everywhere, never animated. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 3px;
}
:focus:not(:focus-visible) { outline: none; }

::selection { background: var(--accent-soft); color: var(--text-primary); }

/* Theme cross-fade. Applied only while html.theme-anim is present (added by
   app.js for 320ms) so no element on the page ever carries a global
   transition during normal use. */
html.theme-anim,
html.theme-anim *,
html.theme-anim *::before,
html.theme-anim *::after {
  transition: background-color 260ms var(--e-in-out),
              border-color     260ms var(--e-in-out),
              fill             260ms var(--e-in-out),
              stroke           260ms var(--e-in-out),
              color            200ms var(--e-in-out) !important;
}

/* ===========================================================================
   4. UTILITIES + PRIMITIVES
   =========================================================================== */
.wrap {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: clamp(20px, 4vw, 40px);
}

.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: -100px; left: var(--s4);
  z-index: 200;
  padding: var(--s3) var(--s5);
  background: var(--accent);
  color: var(--on-accent);
  border-radius: 0 0 var(--r-control) var(--r-control);
  font-weight: 600;
  transition: top 160ms var(--e-out);
}
.skip-link:focus { top: 0; }

/* -- micro label: the mono voice of the whole page ----------------------- */
.micro {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: .12em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* -- buttons ------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  height: 48px;
  padding: 0 26px;
  border-radius: var(--r-btn);
  font-family: var(--font-display);
  font-size: .9375rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color 180ms var(--e-out), color 180ms var(--e-out),
              border-color 180ms var(--e-out), transform 180ms var(--e-out),
              box-shadow 180ms var(--e-out);
}
.btn svg { flex: none; transition: transform 200ms var(--e-out); }

.btn--primary {
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: var(--shadow-cta);
}
.btn--primary:hover { background: var(--accent-hover); transform: translateY(-1px); }
.btn--primary:hover svg { transform: translateX(3px); }
.btn--primary:active { transform: translateY(0) scale(.99); transition-duration: 90ms; }
/* An accent ring on an accent fill would vanish. */
.btn--primary:focus-visible { outline-color: var(--text-primary); }

.btn--ghost {
  border: 1px solid var(--border-strong);
  color: var(--text-primary);
}
.btn--ghost:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-soft);
}
.btn--ghost:active { transform: scale(.99); transition-duration: 90ms; }

/* -- chips --------------------------------------------------------------- */
.chip {
  display: inline-flex;
  align-items: center;
  padding: 3px 8px;
  border: 1px solid var(--border);
  border-radius: var(--r-chip);
  font-family: var(--font-mono);
  font-size: var(--t-chip);
  font-weight: 500;
  letter-spacing: .04em;
  line-height: 1.5;
  color: var(--text-muted);
}
.chips { display: flex; flex-wrap: wrap; gap: var(--s2); }

/* -- status: encoded as GEOMETRY, never as colour ------------------------
   shipped = filled datum square, beta = hollow, in development = dashed.
   Readable in greyscale and to colour-blind users, and it keeps the page
   genuinely single-accent (no green/amber/red anywhere). */
.status {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-muted);
  white-space: nowrap;
}
.status__dot {
  width: 6px; height: 6px;
  flex: none;
  transform: rotate(45deg);
  border: 1px solid var(--border-strong);
  background: none;
}
.status--released .status__dot { background: var(--text-primary); border-color: var(--text-primary); }
.status--beta     .status__dot { border-style: solid; }
.status--wip      .status__dot { border-style: dashed; }
.status--proto    .status__dot { border-style: dotted; }

/* -- reveal on scroll ---------------------------------------------------- */
[data-reveal] {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 460ms var(--e-out), transform 460ms var(--e-out);
  transition-delay: calc(var(--i, 0) * 45ms);
}
[data-reveal].is-in { opacity: 1; transform: none; }
/* Nothing may ever be stranded invisible: if JS never runs, show everything. */
html.no-js [data-reveal],
html.no-motion [data-reveal] { opacity: 1; transform: none; transition: none; }

/* ===========================================================================
   5. HEADER
   =========================================================================== */
.site-head {
  position: sticky;
  top: 0;
  z-index: 100;
  height: var(--header-h);
  background: var(--header-bg);
  border-bottom: 1px solid var(--hairline);
  transition: border-color 200ms linear, box-shadow 200ms linear;
}
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .site-head { -webkit-backdrop-filter: blur(14px) saturate(1.4); backdrop-filter: blur(14px) saturate(1.4); }
}
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .site-head { background: var(--bg); }
}

.site-head.is-scrolled {
  border-bottom-color: var(--border);
  box-shadow: 0 1px 0 var(--border), 0 8px 20px -16px rgba(15, 23, 32, .25);
}
:root[data-theme="dark"] .site-head.is-scrolled { box-shadow: 0 1px 0 var(--border), 0 8px 24px -18px rgba(0, 0, 0, .9); }

.site-head__inner {
  display: flex;
  align-items: center;
  gap: var(--s4);
  height: 100%;
}

/* -- the header's ONE instrument motif: the scroll needle --------------
   A tick strip on the header's bottom edge was cut deliberately. The motif
   budget allows at most two instrument marks per region and the header
   already carries the needle; the page's single ruler lives on the footer's
   top edge, where it reads as the bottom edge of the drawing sheet. */
.scroll-progress {
  position: absolute;
  left: 0; bottom: -1px;
  height: 2px; width: 100%;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;
  pointer-events: none;
}

/* -- wordmark ------------------------------------------------------------ */
.brand { display: flex; align-items: center; gap: 10px; flex: none; }
.brand__stamp {
  width: 28px; height: 28px;
  display: grid; place-items: center;
  border: 1px solid var(--accent);
  border-radius: var(--r-inner);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .06em;
  color: var(--accent);
  transition: background-color 160ms var(--e-out);
}
.brand:hover .brand__stamp { background: var(--accent-soft); }
.brand__name {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -.01em;
  color: var(--text-primary);
}
.brand__rule { width: 1px; height: 14px; background: var(--border); margin-inline: 2px; }

/* -- nav ----------------------------------------------------------------- */
.site-nav { margin-left: auto; }
.site-nav__list { position: relative; display: flex; align-items: center; gap: var(--s7); }
.site-nav__link {
  position: relative;
  display: block;
  padding-block: 14px;
  margin-block: -14px;           /* 44px hit area, text stays optically centred */
  font-size: var(--t-nav);
  font-weight: 500;
  letter-spacing: -.002em;
  color: var(--text-secondary);
  transition: color 140ms linear;
}
.site-nav__link:hover { color: var(--text-primary); }
.site-nav__link[aria-current="true"] { color: var(--accent); font-weight: 600; }

/* Each link carries both a full and an abbreviated label; exactly one is ever
   shown. Below 760px they swap (see responsive.css) — there is no hamburger,
   because a menu button for three destinations is the clearest template tell. */
.site-nav__abbr { display: none; }

/* One shared cursor rule that slides between links — a measured span rather
   than a generic underline. It sits on the header's tick strip. */
.nav-cursor {
  position: absolute;
  bottom: -23px;
  left: 0;
  height: 1px;
  width: 0;
  background: var(--accent);
  transform: translateX(0);
  transition: transform 280ms var(--e-out), width 280ms var(--e-out), opacity 200ms linear;
  opacity: 0;
  pointer-events: none;
}
.nav-cursor.is-on { opacity: 1; }

/* -- theme toggle: an aperture dial, not a sun/moon cliché --------------- */
.theme-toggle {
  flex: none;
  width: 34px; height: 34px;
  margin-left: var(--s6);
  display: grid; place-items: center;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-control);
  color: var(--text-secondary);
  cursor: pointer;
  transition: color 180ms var(--e-out), border-color 180ms var(--e-out),
              background-color 180ms var(--e-out);
}
.theme-toggle:hover { color: var(--text-primary); background: var(--accent-soft); border-color: var(--accent); }
.theme-toggle svg { transition: transform 320ms var(--e-in-out); }

/* ===========================================================================
   6. HERO
   =========================================================================== */
/* No min-height and no vh units anywhere: the hero must end well inside the
   first screen so the top of the project grid is already in view on a
   900px-tall laptop. The bottom padding is deliberately one step smaller than
   the between-section rhythm — the hero already carries the section's air. */
.hero { position: relative; padding: var(--s12) 0 var(--s11); overflow: clip; }

/* The full-bleed blueprint wash was cut: it was a third instrument motif in
   one region and a DPR-1.25 shimmer risk. The hero's construction grid lives
   inside the plate drawing, where it does a real job. */

.hero__inner {
  position: relative;
  display: grid;
  grid-template-columns: 1.05fr .95fr;
  gap: var(--s11);
  align-items: center;
}
.hero__copy { max-width: 560px; }

.hero__eyebrow { display: flex; align-items: center; gap: 10px; margin-bottom: var(--s5); }
.hero__eyebrow::before { content: ""; width: 24px; height: 1px; background: var(--accent); flex: none; }

.hero__title {
  font-size: var(--t-display);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -.022em;
  color: var(--text-primary);
}
.hero__title-sub {
  display: block;
  margin-top: var(--s3);
  font-size: var(--t-lede);
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: -.005em;
  color: var(--text-secondary);
}
.hero__lede {
  margin-top: var(--s5);
  max-width: 46ch;
  font-size: var(--t-body-lg);
  line-height: 1.7;
  color: var(--text-secondary);
}
.hero__cta { display: flex; flex-wrap: wrap; gap: 14px; margin-top: var(--s7); }

/* -- readout strip: the instrument summary under the CTA ----------------- */
.readout {
  display: flex;
  align-items: center;
  gap: var(--s6);
  margin-top: var(--s9);
  padding-top: var(--s5);
  border-top: 1px solid var(--hairline);
}
/* Source order is <dt> then <dd> (the only valid <dl> content model);
   column-reverse puts the value back above its label visually. */
.readout__cell { display: flex; flex-direction: column-reverse; gap: 2px; }
.readout__cell + .readout__cell { border-left: 1px solid var(--hairline); padding-left: var(--s6); }
.readout__value {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
  color: var(--text-primary);
}

.hero__plate { width: min(100%, 520px); margin-inline: auto; }
.hero__plate svg { width: 100%; height: auto; }

/* ===========================================================================
   7. COLLECTIONS + CARDS
   =========================================================================== */
/* Offset for anchored jumps comes from `scroll-padding-top` on <html>;
   adding scroll-margin here as well would double it. */
.collection { padding-block: var(--s13); }
.collection:first-of-type { padding-top: var(--s10); }

.collection__head {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: end;
  gap: var(--s4) var(--s6);
  margin-bottom: var(--s8);
  padding-bottom: var(--s5);
  border-bottom: 1px solid var(--hairline);
}
.collection__kicker { display: flex; align-items: center; gap: 10px; grid-column: 1 / -1; }
.collection__idx { color: var(--accent); }
.collection__title {
  grid-column: 1;
  font-size: var(--t-h2);
  font-weight: 700;
  letter-spacing: -.018em;
  color: var(--text-primary);
}
.collection__title:focus { outline: none; }
.collection__note {
  grid-column: 1;
  max-width: 52ch;
  margin-top: var(--s2);
  font-size: var(--t-sm);
  color: var(--text-secondary);
}
.collection__count { grid-column: 2; grid-row: 2 / span 2; align-self: end; }
.collection__count span { color: var(--text-primary); font-weight: 500; }

.grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: var(--s6);
  row-gap: var(--s7);
}

/* -- the card ------------------------------------------------------------ */
.cell { display: flex; }

.card {
  position: relative;
  display: flex;
  width: 100%;
}

/* The lift transform lives on this inner element, never on .card. That keeps
   .card's layout box — and therefore the stretched hit area below — perfectly
   still, which is what stops the classic hover flicker at the tile boundary. */
.card__lift {
  display: flex;
  flex-direction: column;
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-1);
  overflow: hidden;
  contain: paint;
  transition: transform 220ms var(--e-out), box-shadow 220ms var(--e-out),
              border-color 220ms var(--e-out);
}

/* Exactly one Tab stop per tile: an <h3><button> whose hit area is stretched
   over the whole card. The focus ring is drawn on .card__lift so it traces the
   real 14px radius rather than the invisible button box. */
.card__open { cursor: pointer; text-align: left; color: inherit; font: inherit; }
.card__open::after { content: ""; position: absolute; inset: 0; z-index: 2; }
.card__open:focus-visible { outline: none; }

.card:has(.card__open:focus-visible) .card__lift,
.card.is-focused .card__lift {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-color: var(--accent);
}
:root[data-theme="dark"] .card:has(.card__open:focus-visible) .card__lift,
:root[data-theme="dark"] .card.is-focused .card__lift {
  box-shadow: var(--shadow-2), 0 0 0 5px var(--accent-soft);
}

/* 1. index strip */
.card__index {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  min-height: 28px;
  padding: 0 14px;
  border-bottom: 1px solid var(--hairline);
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}
.card__num { white-space: nowrap; transition: color 200ms linear; }

/* 2. the plate */
.card__plate { position: relative; display: block; }

/* 3. body */
.card__body { padding: 18px 20px 0; }
.card__title {
  font-family: var(--font-display);
  font-size: var(--t-h3);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -.01em;
  color: var(--text-primary);
}
.card__desc {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin-top: var(--s2);
  /* Fixed two-line height so one- and two-line titles still bottom-align
     across a row. */
  min-height: calc(2 * 1.6 * var(--t-sm));
  font-size: var(--t-sm);
  line-height: 1.6;
  color: var(--text-secondary);
}

/* 4. meta footer */
.card__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  margin-top: auto;              /* pins the footer to the bottom of the tile */
  padding: 14px 20px 16px;
  border-top: 1px solid var(--hairline);
}
/* One line of chips, faded rather than hard-clipped where they run out of
   room. The "+N" counter sits outside this box so it is never what gets cut. */
.card__chips {
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
  min-width: 0;
  flex: 0 1 auto;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent);
}
.card__chips .chip { white-space: nowrap; flex: none; }
.chip--more { flex: none; border-style: dashed; }
.card__cue { flex: none; display: grid; place-items: center; color: var(--text-muted);
             transition: transform 200ms var(--e-out), color 200ms var(--e-out); }

/* Hover only where a real pointer exists, so nothing sticks on touch.
   Keyboard focus gets the identical treatment via .is-focused. */
@media (hover: hover) and (pointer: fine) {
  .card:hover .card__lift, .card.is-focused .card__lift {
    transform: translateY(-4px);          /* 4px, not 8 — instruments do not bounce */
    box-shadow: var(--shadow-2);
    border-color: var(--accent-line);
  }
  .card:hover .card__num, .card.is-focused .card__num { color: var(--accent); }
  .card:hover .card__cue, .card.is-focused .card__cue { transform: translate(3px, -3px); color: var(--accent); }
  .card:hover .plate-grid, .card.is-focused .plate-grid { opacity: 1; }
  .card:hover .frame__reg, .card.is-focused .frame__reg { color: var(--accent-line); }
  .card:active .card__lift { transform: translateY(-1px) scale(.995); transition-duration: 90ms; }
}
@media (hover: none) {
  .card:active .card__lift { background: var(--surface-sunken); }
}

/* The 5-item mobile grid leaves one hole in a 3-column row. Filling it
   deliberately reads as intent; centring the orphans would not. */
.cell--ghost {
  display: grid;
  place-items: center;
  align-content: center;
  gap: 10px;
  min-height: 100%;
  border: 1px dashed var(--border);
  border-radius: var(--r-card);
}
.cell__datum {
  width: 10px; height: 10px;
  background: var(--accent);
  transform: rotate(45deg);
}

/* ===========================================================================
   8. PLACEHOLDER DRAWINGS
   =========================================================================== */
.svg-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

.frame {
  position: relative;
  display: block;
  aspect-ratio: 16 / 10;
  background: var(--surface-sunken);
  border-bottom: 1px solid var(--hairline);
  overflow: hidden;
}
.frame > .art { position: absolute; inset: 0; width: 100%; height: 100%; }

/* The plate's construction grid and registration crosses are drawn inside the
   art SVG (see artwork.py): an SVG <pattern> with crispEdges instead of a CSS
   gradient, so 1px lines stay crisp at fractional device-pixel ratios. */
.plate-grid { opacity: .55; transition: opacity 260ms var(--e-out); }
.plate-grid__cell { stroke: var(--hairline); stroke-width: 1; }
.frame__reg { color: var(--hairline); stroke-width: 1; transition: color 260ms var(--e-out); }

.frame__badge {
  position: absolute;
  right: 8px; bottom: 8px;
  padding: 3px 7px;
  border-radius: 4px;
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* A real screenshot lands ONLY in the frame's content region, so the drawn
   chrome always survives and a half-populated grid still reads as one system. */
.frame__shot { position: absolute; overflow: hidden; border-radius: 4px; }
.frame__shot img { width: 100%; height: 100%; object-fit: cover; object-position: top center; }
.frame--windows .frame__shot { inset: 16% 6.5625% 7.5% 6.5625%; }
.frame--phone   .frame__shot { inset: 6% 24.375% 6% 48.125%; border-radius: 8px; }
.frame.has-shot .art .stage { display: none; }

/* Per-project accent. --p-h comes from the generated css/projects.css and is
   applied ONLY to the drawing ink, so the eleven plates are distinguishable
   while every piece of UI chrome keeps the single site accent. The class is on
   both the card and the detail node, so the hue survives the move into the
   dialog. */
.art { --accent: hsl(var(--p-h, 232) 55% 48%); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .art { --accent: hsl(var(--p-h, 232) 88% 76%); }
}
:root[data-theme="dark"] .art { --accent: hsl(var(--p-h, 232) 88% 76%); }

/* --- the ink inventory -------------------------------------------------- */
/* Authored at 320px, rendered up to ~700px: non-scaling strokes keep every
   hairline at exactly 1px, so the drawing gains detail instead of going soft. */
.art * { vector-effect: non-scaling-stroke; }
.art { shape-rendering: geometricPrecision; }

.art text {
  font-family: var(--font-mono);
  font-size: 7px;
  letter-spacing: .09em;
  fill: var(--text-muted);
  opacity: .85;
}

/* window chrome */
.w-body    { fill: var(--surface); stroke: var(--viz-line); stroke-width: 1; }
.w-bar     { fill: var(--surface-sunken); }
.w-hair    { stroke: var(--viz-line); stroke-width: 1; }
.w-ctl     { stroke: var(--viz-line); stroke-width: 1; fill: none; }
.w-file    { font-size: 7px; fill: var(--text-muted); opacity: .8; }

/* phone chrome */
.p-ghost    { fill: none; stroke: var(--viz-line); stroke-width: 1; opacity: .45; }
.p-ghost rect:not(:first-child) { fill: var(--viz-line); stroke: none; opacity: .5; }
.p-body     { fill: var(--surface); stroke: var(--viz-line); stroke-width: 1; }
.p-screen   { fill: var(--surface-sunken); stroke: none; }
.p-speaker,
.p-home     { fill: var(--viz-line); }

/* shared motif ink */
.v-hair, .v-axis   { stroke: var(--viz-line); stroke-width: 1; fill: none; }
.v-grid, .v-cgrid  { stroke: var(--viz-line); stroke-width: 1; opacity: .45; fill: none; }
.v-cgrid           { opacity: .30; }
.v-tick            { stroke: var(--viz-line); stroke-width: 1; }
.v-rail            { fill: var(--surface-sunken); }
.v-tool            { fill: none; stroke: var(--viz-line); stroke-width: 1; }
.v-tool.is-on      { fill: var(--accent); stroke: var(--accent); opacity: .16; }
.v-ghost           { fill: none; stroke: var(--viz-line); stroke-width: 1; opacity: .5; }
.v-plate           { fill: var(--surface); stroke: var(--viz-line); stroke-width: 1; }
.v-datum           { stroke: var(--accent); stroke-width: 1.4; fill: none; }
.v-line            { stroke: var(--accent); stroke-width: 1.5; fill: none; stroke-linejoin: round; stroke-linecap: round; }
.v-area            { fill: var(--accent); opacity: .10; stroke: none; }
.v-lbl             { font-size: 7px; }
.v-lbl--acc        { fill: var(--accent); opacity: 1; }
.v-lbl--on-dark    { fill: #8892A6; opacity: .9; }
.v-blk             { fill: var(--viz-line); }
.v-blk--acc        { fill: var(--accent); }
.v-wipe            { stroke: var(--text-muted); stroke-width: 1; opacity: .6; }
.v-handle          { fill: var(--surface); stroke: var(--text-muted); stroke-width: 1; }
.v-chev            { stroke: var(--text-muted); stroke-width: 1; fill: none; }
.v-toolbar rect    { fill: var(--surface-sunken); }
.v-pill            { fill: var(--viz-line); opacity: .5; }
.v-bar             { fill: var(--viz-line); }
.v-bar--dn         { fill: var(--viz-line); opacity: .45; }
.v-viewport        { fill: var(--viz-panel); }
.v-wire            { fill: none; stroke: #4A566B; stroke-width: 1; }
.v-wire--edge      { stroke: #6C7A92; }
.v-hgrid           { stroke: #2A3243; stroke-width: 1; }
.v-horizon         { stroke: #46506A; stroke-width: 1; }
.v-gizmo           { stroke: #7E8AA3; stroke-width: 1; fill: #7E8AA3; }
.v-gizmo--z        { stroke: var(--accent); }
.v-part            { fill: var(--accent); fill-opacity: .10; stroke: var(--text-muted); stroke-width: 1; }
.v-hidden          { stroke: var(--viz-line); stroke-width: 1; stroke-dasharray: 3 3; fill: none; }
.v-hole            { fill: var(--surface); stroke: var(--text-muted); stroke-width: 1; }
.v-dim             { stroke: var(--text-muted); stroke-width: 1; fill: none; opacity: .75; }
.v-arrow           { fill: var(--text-muted); stroke: none; }
.v-contour         { fill: none; stroke: var(--viz-line); stroke-width: 1; opacity: .6; }
.v-node            { fill: var(--surface); stroke: var(--accent); stroke-width: 1.4; }
.v-node--start     { fill: var(--accent); stroke: var(--accent); }
.v-scale           { stroke: var(--text-muted); stroke-width: 1; fill: none; opacity: .8; }
.v-north           { fill: var(--text-muted); stroke: none; opacity: .8; }

/* mobile motif ink */
.m-bar        { fill: var(--surface); }
.m-title      { fill: var(--viz-line); }
.m-avatar     { fill: var(--viz-line); opacity: .6; }
.m-grid       { stroke: var(--viz-line); stroke-width: 1; opacity: .35; fill: none; }
.m-rose       { fill: none; stroke: var(--viz-line); stroke-width: 1; }
.m-rtick      { stroke: var(--viz-line); stroke-width: 1; }
.m-heading    { fill: var(--text-muted); }
.m-cdi        { stroke: var(--accent); stroke-width: 1.6; }
.m-dot        { fill: var(--viz-line); }
.m-corridor   { fill: var(--accent); fill-opacity: .10; stroke: var(--viz-line); stroke-width: 1; }
.m-center     { stroke: var(--viz-line); stroke-width: 1; stroke-dasharray: 3 3; }
.m-bars       { stroke: var(--viz-line); stroke-width: 1; }
.m-ac         { fill: var(--accent); }
.m-ac--dim    { fill: var(--viz-line); }
.m-read       { font-size: 11px; font-weight: 500; fill: var(--text-primary); opacity: .85; letter-spacing: 0; }
.m-lbl        { font-size: 7px; }
.m-lbl--on-dark { fill: #8892A6; opacity: .95; }
.m-sky, .m-night { fill: var(--viz-panel); }
.m-moon       { fill: none; stroke: #6C7A92; stroke-width: 1; }
.m-star       { fill: #8892A6; opacity: .8; }
.m-dragon     { stroke: #8892A6; stroke-width: 1; fill: none; }
.m-body       { fill: var(--accent); fill-opacity: .16; }
.m-head       { fill: var(--accent); fill-opacity: .16; }
.m-eye        { stroke: var(--accent); stroke-width: 1.6; }
.m-wing       { fill: #8892A6; fill-opacity: .12; }
.m-wing--far  { fill-opacity: .06; }
.m-ground     { fill: #2A3243; stroke: none; }
.m-ground--far { fill: #1D2431; }
.m-hud rect   { fill: #8892A6; opacity: .5; }
.m-coast      { fill: var(--viz-line); fill-opacity: .18; stroke: var(--viz-line); stroke-width: 1; }
.m-waves      { fill: none; stroke: var(--accent); stroke-width: 1; opacity: .5; }
.m-mast       { stroke: var(--text-muted); stroke-width: 1; fill: var(--text-muted); }
.m-station    { fill: var(--accent); }
.m-track      { fill: none; stroke: var(--text-muted); stroke-width: 1; opacity: .8; }
.m-track--dim { opacity: .4; }
.m-beam       { fill: var(--accent); fill-opacity: .10; stroke: none; }
.m-pulses     { fill: none; stroke: var(--accent); stroke-width: 1; opacity: .55; }
.m-lamp       { fill: none; stroke: #8892A6; stroke-width: 1; }
.m-lampcore   { fill: var(--accent); }
.m-tower      { fill: #2A3243; stroke: #4A566B; stroke-width: 1; }
.m-morse rect { fill: #8892A6; opacity: .7; }
.m-contour    { fill: none; stroke: var(--viz-line); stroke-width: 1; opacity: .55; }
.m-line       { fill: none; stroke: var(--accent); stroke-width: 1.6; stroke-linejoin: round; stroke-linecap: round; }
.m-node       { fill: var(--surface); stroke: var(--accent); stroke-width: 1.4; }
.m-node--start { fill: var(--accent); }
.m-readout rect { fill: var(--viz-line); opacity: .55; }
.m-elev       { fill: var(--accent); fill-opacity: .12; stroke: var(--accent); stroke-width: 1.2; }

/* hero plate ink */
.plate__sheet  { fill: var(--surface); stroke: var(--border); stroke-width: 1; }
.plate__inner  { fill: none; stroke: var(--hairline); stroke-width: 1; }
.plate__grid   { stroke: var(--hairline); stroke-width: 1; opacity: .55; }
.plate__grid5  { stroke: var(--hairline); stroke-width: 1; }
.plate__dial   { fill: none; stroke: var(--hairline); stroke-width: 1; }
.plate__tick   { stroke: var(--hairline); stroke-width: 1; }
.plate__deg    { font-family: var(--font-mono); font-size: 9px; fill: var(--text-muted); opacity: .7; }
.plate__rule   { stroke: var(--viz-line); stroke-width: 1; fill: none; }
.plate__ground { fill: none; stroke: var(--hairline); stroke-width: 1; stroke-dasharray: 4 4; }
.plate__proj   { stroke: var(--hairline); stroke-width: 1; stroke-dasharray: 3 3; }
.plate__edge   { stroke: var(--text-muted); stroke-width: 1; fill: none; }
.plate__key    { stroke: var(--accent); stroke-width: 1.5; fill: none; }
.plate__datum  { fill: var(--accent); }
.plate__dim    { stroke: var(--text-muted); stroke-width: 1; fill: none; opacity: .8; }
.plate__arrow  { fill: var(--text-muted); }
.plate__chipbg { fill: var(--surface); }
.plate__lbl    { font-family: var(--font-mono); font-size: 10px; fill: var(--text-muted); letter-spacing: .06em; }
.plate__block  { fill: none; stroke: var(--hairline); stroke-width: 1; }
.plate__reg    { color: var(--hairline); stroke-width: 1; }

/* ===========================================================================
   9. MODAL — the expanded project view
   =========================================================================== */
.pdlg {
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--r-modal);
  width: min(1080px, calc(100vw - 48px));
  max-width: none;
  max-height: min(88vh, 860px);
  background: var(--surface);
  color: var(--text-primary);
  box-shadow: var(--shadow-modal);
  overflow: hidden;
  opacity: 0;
  transform: translateY(16px) scale(.985);
  transition: opacity 180ms var(--e-in), transform 180ms var(--e-in);
}
/* Column flex, so .detail (via display:contents on .pdlg__body) stretches to
   the full width of the sheet. The grab handle must NOT be a flex sibling —
   as one it steals horizontal space and shunts the whole sheet sideways. */
.pdlg[open] { display: flex; flex-direction: column; }
.pdlg .detail { flex: 1 1 auto; min-height: 0; }
.pdlg__grab { display: none; }
.pdlg.is-open { opacity: 1; transform: none; transition: opacity 260ms var(--e-lift), transform 260ms var(--e-lift); }

.pdlg::backdrop { background: var(--backdrop); opacity: 0; transition: opacity 140ms linear; }
.pdlg.is-open::backdrop { opacity: 1; transition: opacity 160ms linear; }
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .pdlg::backdrop { -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); }
}

.pdlg__body { display: contents; }

.pdlg__close {
  position: absolute;
  top: 14px; right: 14px;
  z-index: 3;
  width: 36px; height: 36px;
  display: grid; place-items: center;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-control);
  background: color-mix(in srgb, var(--surface) 78%, transparent);
  color: var(--text-secondary);
  cursor: pointer;
  transition: color 180ms var(--e-out), border-color 180ms var(--e-out), background-color 180ms var(--e-out);
}
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .pdlg__close { -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); }
}
.pdlg__close:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }

/* the detail node, relocated here from its card */
.detail { display: grid; grid-template-columns: 1.15fr .85fr; width: 100%; min-height: 0; }
.detail[hidden] { display: none; }

.detail__visual {
  display: flex;
  flex-direction: column;
  background: var(--surface-sunken);
  border-right: 1px solid var(--border);
  padding: 28px;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.detail__visual .specs { margin-top: auto; }
.detail__info {
  padding: 32px 32px 36px;
  overflow-y: auto;
  overscroll-behavior: contain;
  min-height: 0;
  scroll-padding-top: var(--s6);
}

.detail__eyebrow {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2);
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.detail__num { color: var(--accent); }
.detail__sep { opacity: .5; }

.detail__title {
  margin-top: var(--s3);
  font-size: var(--t-modal-title);
  font-weight: 700;
  letter-spacing: -.018em;
  line-height: 1.2;
}
.detail__title:focus { outline: none; }
.detail__tagline { margin-top: var(--s2); font-size: var(--t-body-lg); color: var(--text-secondary); }
.detail__desc { max-width: 62ch; color: var(--text-secondary); }

/* One rule, above the first content block — separating the title group from
   the body, not the label from its own paragraph. */
.detail__info > .detail__block:first-of-type {
  margin-top: var(--s6);
  padding-top: var(--s6);
  border-top: 1px solid var(--hairline);
}

.detail__cols { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s6); margin-top: var(--s7); }
.detail__block + .detail__block, .detail__cols + .detail__block { margin-top: 0; }
.detail__info > .detail__block { margin-top: var(--s7); }
.detail__h {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--s3);
}

/* Feature bullets reuse the status datum glyph — one mark, two jobs. */
.ticks li {
  position: relative;
  padding-left: 22px;
  font-size: var(--t-sm);
  color: var(--text-secondary);
}
.ticks li + li { margin-top: 10px; }
.ticks li::before {
  content: "";
  position: absolute;
  left: 2px; top: .55em;
  width: 6px; height: 6px;
  background: var(--accent);
  transform: rotate(45deg);
}

.facts { display: grid; grid-template-columns: auto 1fr; gap: 8px var(--s4); align-items: baseline; }
.facts dt {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.facts dd { font-family: var(--font-mono); font-size: var(--t-meta); color: var(--text-primary); }

.detail__links { display: flex; flex-wrap: wrap; gap: var(--s3); margin-top: var(--s7); }

.detail__caption { margin-top: var(--s3); text-align: center; }

/* 2x2 spec grid, ruled by its own background showing through 1px gaps. */
.specs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1px;
  margin-top: var(--s5);
  background: var(--hairline);
  border: 1px solid var(--hairline);
  border-radius: var(--r-inner);
  overflow: hidden;
}
.specs__cell { background: var(--surface-sunken); padding: 12px 14px; }
.specs__cell dd {
  margin-top: 4px;
  font-family: var(--font-mono);
  font-size: var(--t-meta);
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

.gallery { display: grid; gap: var(--s4); }
.gallery__item { margin: 0; }
.detail__visual .frame { border-radius: var(--r-plate); border: 1px solid var(--border); }

/* ===========================================================================
   10. FOOTER
   =========================================================================== */
.site-foot { background: var(--surface-footer); border-top: 1px solid var(--border); }
.site-foot__ruler {
  height: 24px;
  border-bottom: 1px solid var(--hairline);
  background-image:
    repeating-linear-gradient(to right, var(--hairline) 0 1px, transparent 1px 8px),
    repeating-linear-gradient(to right, var(--hairline) 0 1px, transparent 1px 40px);
  background-size: 8px 6px, 40px 12px;
  background-position: top left, top left;
  background-repeat: repeat-x, repeat-x;
}

.site-foot__main {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s7);
  padding: var(--s8) 0 var(--s7);
}
.site-foot__note { max-width: 46ch; margin-top: var(--s3); font-size: var(--t-sm); color: var(--text-secondary); }
.site-foot__cols { display: flex; gap: var(--s12); }
.site-foot__col h2 { font-size: var(--t-micro); }
.site-foot__col ul { margin-top: var(--s3); display: grid; gap: 10px; }
.site-foot__col a { font-size: var(--t-sm); color: var(--text-secondary); transition: color 180ms var(--e-out); }
.site-foot__col a:hover { color: var(--accent); }
.site-foot__bar {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--s4);
  padding: var(--s4) 0;
  border-top: 1px solid var(--hairline);
}

/* ===========================================================================
   Reduced motion — a hard stop. Nothing may be stranded invisible.
   =========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  html { scroll-behavior: auto; }
  [data-reveal] { opacity: 1 !important; transform: none !important; }
}
