/* swazee.net — TUI Dashboard (2026-05-07)
   spec: design_handoff_swazee_tui (zip handoff, README.md)

   Tokens, dashboard chrome (head/tabs/grid/panels/footer), inventory
   table, detail panel, uses panel, ring panel, article-page reading
   layout, mobile breakpoints, prefers-reduced-motion guard.

   Admin styles (.admin-*) live in /admin/admin.css and read the
   tokens defined here. */

:root {
  /* TUI tokens — dark default. */
  --bg:        #0d0f10;
  --fg:        #e6e7e3;
  --dim:       rgba(230, 231, 227, 0.62);
  --dim2:      rgba(230, 231, 227, 0.38);
  --rule:      rgba(230, 231, 227, 0.10);
  --rule-soft: rgba(230, 231, 227, 0.06);
  --surface:   rgba(230, 231, 227, 0.025);
  --surface-hi:rgba(230, 231, 227, 0.05);
  --accent:    #71f0a1;

  /* Aliases admin.css already references. */
  --plate: var(--surface-hi);
  --muted: var(--dim);

  --sans: 'Inter', system-ui, -apple-system, 'Helvetica Neue', sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, 'IBM Plex Mono', monospace;

  --container: 1320px;
  --pad-x: 14px;
  --pad-x-wide: 22px;
  --nav-compact-h: 56px;

  /* Block-print shadow used by panels + chrome cards. Off in dark mode
     (panels lift via surface tint instead); in grey mode this becomes a
     hard offset shadow so cards read as printed blocks on paper. */
  --shadow: 0 0 0 0 transparent;
}

/* Alternate to dark mode is *muted paper grey*, not the original
   bright cream (#f4f2ec — too harsh) and not a dim near-dark
   (#2a2826 read as just a brightness shift). #b8b6b0 sits at ~68 %
   lightness with a slight warm undertone; dark text on muted paper
   gives the toggle a real polarity flip instead of an elevation.
   data-theme="light" still names the toggle position internally —
   the visible palette is grey. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --bg:         #b8b6b0;
    --fg:         #1a1c1e;
    --dim:        rgba(26, 28, 30, 0.62);
    --dim2:       rgba(26, 28, 30, 0.38);
    /* Stronger rule + surface alphas than the original cream palette
       had — cards/chrome on paper-grey need real edge weight to read
       as distinct printed blocks instead of flat regions. */
    --rule:       rgba(26, 28, 30, 0.22);
    --rule-soft:  rgba(26, 28, 30, 0.12);
    --surface:    rgba(26, 28, 30, 0.06);
    --surface-hi: rgba(26, 28, 30, 0.12);
    /* Lime #71f0a1 (80% lightness) reads as washed-out on the
       70%-lightness paper bg. Drop to deeper emerald in same hue
       family for proper contrast (~4.2:1) without losing the
       'lime' brand character. */
    --accent:     #1d8a4a;
    /* Hard offset shadow — block-print stamp on paper. Only present
       in grey mode; dark mode keeps shadow off. */
    --shadow:     3px 3px 0 0 rgba(26, 28, 30, 0.18);
  }
}
:root[data-theme="light"] {
  --bg:         #b8b6b0;
  --fg:         #1a1c1e;
  --dim:        rgba(26, 28, 30, 0.62);
  --dim2:       rgba(26, 28, 30, 0.38);
  --rule:       rgba(26, 28, 30, 0.22);
  --rule-soft:  rgba(26, 28, 30, 0.12);
  --surface:    rgba(26, 28, 30, 0.06);
  --surface-hi: rgba(26, 28, 30, 0.12);
  --accent:     #1d8a4a;
  --shadow:     3px 3px 0 0 rgba(26, 28, 30, 0.18);
}

/* ---- Reset + base ---- */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html { background: var(--bg); color: var(--fg); }
body {
  font-family: var(--sans);
  font-size: 13px;
  line-height: 1.55;
  background: var(--bg);
  color: var(--fg);
  height: 100vh;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}
/* Long-form pages (article, admin, 404) opt out of the locked viewport. */
body.scroll-y, body.article-page, body.subpage, body.admin { height: auto; overflow-y: auto; }
a { color: inherit; text-decoration: none; }

.skip-link { position: absolute; left: -9999px; top: -9999px; }
.skip-link:focus {
  left: 8px; top: 8px;
  padding: 8px 12px;
  background: var(--surface-hi);
  color: var(--fg);
  border: 1px solid var(--rule);
  z-index: 100;
}

/* ---- Dashboard root ---- */
.dash {
  display: grid;
  grid-template-rows: auto auto 1fr auto;
  height: 100vh;
}

/* ---- Top bar ---- */
.dash__head {
  padding: 14px 22px;
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 20px;
  align-items: center;
  background: var(--surface-hi);
  border-bottom: 1px solid var(--rule);
}
/* Brand wordmark — Instrument Sans 700 with blinking caret, ported from
   the secnull flagship mark (caret swapped to swazee lime). */
.brand { display: flex; align-items: baseline; gap: 0; }
.brand__name {
  font-family: 'Instrument Sans', var(--sans);
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--fg);
  line-height: 1;
}
.brand__caret {
  display: inline-block;
  /* Match the wordmark's font + size on the caret itself, so `1cap`
     and `em` resolve against Instrument Sans 700 at 22 px. Without
     this the caret inherits body's Inter at 13 px and 1cap comes out
     ~60 % of the wordmark's cap-height. Baseline alignment on .brand
     then puts the caret's bottom on the SWAZEE/NET baseline and its
     top on the cap-line. */
  font-family: 'Instrument Sans', var(--sans);
  font-size: 22px;
  line-height: 1;
  width: 0.5ch;
  height: 0.74em;
  height: 1cap;
  background: var(--accent);
  margin: 0 6px;
  /* Realistic terminal-cursor blink: smooth fade (not hard step), with
     asymmetric duty cycle — visible ~80 % of the cycle, off briefly,
     and short ease-in/ease-out fades on either side of the off phase.
     Period 1.06 s matches macOS Terminal's default cursor rate. */
  animation: brand-blink 1.06s ease-in-out infinite;
}
@keyframes brand-blink {
  0%, 60% { opacity: 1; }
  70%, 90% { opacity: 0; }
  100%    { opacity: 1; }
}
/* Department-style label sitting at the leading edge of head-right.
   Mono small caps, same scale as the existing right-side mono text. */
.head-tag {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--dim2);
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.head-mid {
  text-align: center;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--dim2);
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.head-right {
  display: flex;
  align-items: center;
  gap: 16px;
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--dim);
  justify-self: end;
}
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--surface-hi);
  border: 1px solid var(--rule);
  color: var(--fg);
  font-size: 11px;
  letter-spacing: 0.02em;
}
.status-dot {
  width: 6px; height: 6px;
  border-radius: 999px;
  background: var(--accent);
  box-shadow: 0 0 8px var(--accent);
}
.theme-toggle {
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--dim);
  font-family: var(--mono);
  font-size: 11px;
  padding: 3px 9px;
  border-radius: 4px;
  cursor: pointer;
  letter-spacing: 0.04em;
}
.theme-toggle:hover { color: var(--fg); border-color: var(--fg); }

/* ---- Tabs ---- */
.dash__tabs {
  display: flex;
  gap: 0;
  padding: 0 14px;
  border-bottom: 1px solid var(--rule-soft);
  overflow-x: auto;
}
.tab {
  padding: 12px 14px;
  font-size: 13px;
  color: var(--dim);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  display: flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
  font-weight: 400;
  transition: color 200ms ease, border-color 200ms ease;
}
.tab:hover { color: var(--fg); }
.tab.is-active {
  color: var(--fg);
  font-weight: 500;
  border-bottom-color: var(--accent);
}
.tab__key {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--dim2);
  letter-spacing: 0.04em;
}
.tab__count {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--dim2);
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--surface-hi);
  margin-left: 4px;
  /* Pin chip width so digit-count changes (8 → 22) don't shift surrounding
     tab spacing. Tabular nums + min-width fits two- to three-digit counts. */
  display: inline-block;
  min-width: 26px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.tab--right {
  margin-left: auto;
  color: var(--dim2);
}

/* ---- Main grid ---- */
.dash__main {
  display: grid;
  grid-template-columns: minmax(300px, 1fr) 2fr 1.1fr;
  gap: 14px;
  padding: 14px;
  min-height: 0;
  overflow: hidden;
}
.dash__main--two { grid-template-columns: minmax(300px, 1fr) 2fr; }
.dash__main--one { grid-template-columns: 1fr; }

/* ---- Panels ---- */
.panel {
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--surface);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  min-height: 0;
  box-shadow: var(--shadow);
}
.panel__head {
  padding: 14px 18px 10px;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  border-bottom: 1px solid var(--rule-soft);
}
.panel__head--stack { flex-direction: column; align-items: flex-start; gap: 6px; }
.panel__title {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--dim);
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.panel__meta {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--dim2);
  letter-spacing: 0.04em;
}
.panel__body {
  padding: 18px 20px 20px;
  overflow-y: auto;
  flex: 1;
  min-height: 0;
}
.panel__body--tight { padding: 14px 14px 8px; }
.panel__foot {
  padding: 10px 20px 14px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--dim2);
  border-top: 1px solid var(--rule-soft);
  display: flex;
  justify-content: space-between;
  letter-spacing: 0.04em;
}

/* Stacked columns (left + right rails). Both lay out a column of
   panels that size to their content, no inner scrollbars; the
   column itself scrolls if its total height exceeds the viewport
   row.

   Flex column rather than grid auto-rows: grid auto-rows + the
   global .panel { display: flex; min-height: 0 } combination was
   sizing rows to min-content, letting overflow:visible body content
   spill into the next grid row (text overlap). Plain block layout
   for the panels side-steps that. */
.rcol, .lcol {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-height: 0;
  overflow-y: auto;
}
.rcol .panel, .lcol .panel {
  display: block;       /* override the global .panel flex-column */
  overflow: visible;
  flex-shrink: 0;       /* don't let column overflow scroll squeeze panels */
}
.rcol .panel__body, .lcol .panel__body {
  overflow: visible;    /* no inner scrollbar in the rail cards */
}

/* ---- Proprietor panel ---- */
.eyebrow {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--accent);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  margin-bottom: 10px;
}
.h1 {
  font-family: var(--sans);
  font-size: 36px;
  letter-spacing: -0.025em;
  font-weight: 600;
  line-height: 1.05;
  margin: 0;
  color: var(--fg);
}
.bio {
  color: var(--dim);
  font-size: 13.5px;
  line-height: 1.65;
  margin: 14px 0 0;
  max-width: 360px;
}
.meta-list {
  margin-top: 20px;
  border-top: 1px solid var(--rule-soft);
  padding-top: 14px;
}
.kv {
  display: grid;
  grid-template-columns: 110px 1fr;
  padding: 5px 0;
  font-family: var(--mono);
  font-size: 12px;
}
.kv__k { color: var(--dim2); letter-spacing: 0.04em; }
.kv__v { color: var(--fg); }
.desk {
  margin-top: 18px;
  padding: 14px;
  border-radius: 6px;
  background: var(--surface-hi);
  font-size: 13px;
  line-height: 1.65;
  color: var(--dim);
}
.desk__label {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--accent);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin-bottom: 8px;
}

/* ---- Inventory ---- */
.inv__filters { display: flex; gap: 8px; }
.chip {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--dim);
  padding: 2px 7px;
  border-radius: 4px;
  background: var(--surface-hi);
  border: 1px solid var(--rule);
  text-transform: lowercase;
  letter-spacing: 0;
}
.chip--btn {
  background: transparent;
  border-color: var(--rule-soft);
  cursor: pointer;
}
.chip--btn:hover { color: var(--fg); border-color: var(--rule); }
.chip--btn.is-active {
  background: var(--surface-hi);
  border-color: var(--rule);
  color: var(--fg);
}
.inv-head {
  display: grid;
  grid-template-columns: 34px 84px 1fr 100px 50px;
  column-gap: 14px;
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--dim2);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 0 8px 8px;
  border-bottom: 1px solid var(--rule-soft);
}
.inv-head > :last-child { text-align: right; }
.inv-row {
  display: grid;
  grid-template-columns: 34px 84px 1fr 100px 50px;
  column-gap: 14px;
  align-items: baseline;
  padding: 11px 8px;
  border-radius: 6px;
  border-left: 2px solid transparent;
  text-decoration: none;
  color: inherit;
  transition: background 120ms ease;
}
.inv-row:hover { background: var(--surface-hi); }
.inv-row.is-active {
  background: var(--surface-hi);
  border-left-color: var(--accent);
}
.r-idx {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--dim2);
}
.r-kind {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--dim);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.r-main { min-width: 0; }
.r-name {
  font-size: 14.5px;
  font-weight: 600;
  color: var(--fg);
  letter-spacing: -0.005em;
}
.inv-row.is-active .r-name { color: var(--accent); }
.r-dot {
  display: inline-block;
  width: 6px; height: 6px;
  border-radius: 999px;
  background: var(--accent);
  margin-right: 8px;
  opacity: 0.55;
  vertical-align: middle;
  transform: translateY(-1px);
}
.inv-row.is-active .r-dot { opacity: 1; }
.r-status {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--dim);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-left: 10px;
}
.r-blurb {
  color: var(--dim);
  font-size: 12.5px;
  line-height: 1.5;
  margin-top: 3px;
  max-width: 540px;
}
.r-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 6px;
}
.r-year {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--dim);
  text-align: right;
  white-space: nowrap;
}

/* ---- Detail panel (right column top) ---- */
.d-head {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--dim2);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.d-head .accent { color: var(--accent); }
.d-name {
  font-family: var(--sans);
  font-size: 26px;
  font-weight: 600;
  color: var(--fg);
  letter-spacing: -0.02em;
  margin: 4px 0 6px;
}
.d-blurb {
  color: var(--dim);
  font-size: 13px;
  line-height: 1.6;
  margin-top: 10px;
}
.d-actions {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px dashed var(--rule-soft);
}
.d-link {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--accent);
  letter-spacing: 0.04em;
}
.d-link:hover { text-decoration: underline; }

/* Health bar (kept for completeness; we don't ship dummy metrics — use kv list instead) */
.health-row {
  display: grid;
  grid-template-columns: 70px 1fr 50px;
  align-items: center;
  column-gap: 10px;
  padding: 5px 0;
  font-family: var(--mono);
  font-size: 11.5px;
}
.health-bar {
  position: relative;
  height: 5px;
  border-radius: 999px;
  background: var(--surface-hi);
  border: 1px solid var(--rule-soft);
  overflow: hidden;
}
.health-bar__fill {
  position: absolute;
  inset: 0;
  background: var(--accent);
  border-radius: 999px;
}

/* ---- Uses panel ---- */
.use-row {
  display: grid;
  grid-template-columns: 92px 1fr;
  padding: 4px 0;
  font-family: var(--mono);
}
.use-k { color: var(--dim2); font-size: 11.5px; letter-spacing: 0.04em; }
.use-v { color: var(--fg); font-size: 12px; }

/* ---- Workstation specs panel ----
   4-column grid: category · manufacturer (logo + name) · model · detail.
   Logos inline via spec_logo_html(); fall back to a typographic monogram
   chip for brands not in Simple Icons. Currentcolor on the SVG keeps
   them on-token in dark and grey themes alike. */
.spec-table {
  display: flex;
  flex-direction: column;
  font-family: var(--mono);
  font-size: 12px;
}
.spec-row {
  display: grid;
  grid-template-columns: 140px minmax(0, 1.1fr) minmax(0, 1.4fr) minmax(0, 1fr);
  align-items: center;
  column-gap: 18px;
  padding: 9px 0;
  border-bottom: 1px dashed var(--rule-soft);
}
.spec-row:first-child { padding-top: 4px; }
.spec-row:last-child  { border-bottom: 0; padding-bottom: 4px; }
.spec-cat {
  color: var(--dim2);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.spec-mfr {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--fg);
  min-width: 0;
}
.spec-mfr-name {
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.spec-model {
  color: var(--fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.spec-detail {
  color: var(--dim);
  font-size: 11.5px;
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.spec-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  flex: 0 0 24px;
  color: var(--fg);
}
.spec-logo--svg svg {
  width: 18px;
  height: 18px;
  display: block;
  fill: currentColor;
}
.spec-logo--mono {
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--dim);
  border: 1px solid var(--rule);
  border-radius: 3px;
}
@media (max-width: 720px) {
  .spec-row {
    grid-template-columns: 1fr 1fr;
    column-gap: 14px;
    row-gap: 2px;
    padding: 10px 0;
  }
  .spec-cat   { grid-column: 1 / -1; }
  .spec-detail { text-align: left; }
}

/* ---- Ring panel ---- */
.ring-row {
  display: grid;
  grid-template-columns: 24px 1fr auto;
  column-gap: 10px;
  padding: 6px 0;
  border-bottom: 1px dashed var(--rule-soft);
  align-items: baseline;
  text-decoration: none;
  color: inherit;
  transition: color 120ms ease;
}
.ring-row:last-child { border-bottom: 0; }
.ring-row:hover .ring-name { color: var(--accent); }
.ring-idx {
  color: var(--dim2);
  font-family: var(--mono);
  font-size: 11px;
}
.ring-name {
  font-size: 13px;
  color: var(--fg);
  transition: color 120ms ease;
}
.ring-host {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--dim);
}

/* ---- Footer ----
   Matches the dashboard head as a printed band — surface-hi fill and
   a hard top rule mark it off as chrome rather than content. Three-
   column grid keeps the middle key-hint row centered in the viewport
   regardless of how wide the left copyright and right mark are; flex
   space-between would let the middle drift as side widths change. */
.dash__foot {
  background: var(--surface-hi);
  border-top: 1px solid var(--rule);
  padding: 10px 20px;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--dim);
  letter-spacing: 0.04em;
}
.dash__foot > :first-child { justify-self: start; }
.dash__foot > :last-child  { justify-self: end; }
.foot-keys { display: flex; gap: 16px; justify-self: center; }
.foot-mark { display: inline-flex; align-items: center; }
/* Muted credit on the left — drops to dim2 (one step softer than the
   surrounding chrome) and strips the accent off its separators so the
   copyright recedes against the centered key-hints. */
.foot-credit { color: var(--dim2); }
.foot-credit .mark { color: inherit; }
/* Cross-host link in the footer credit — points at the alternate canonical
   domain (idira.ai ↔ swazee.net) for sameAs / discoverability. Matches the
   dim2 credit tone; hover lifts to accent. */
.foot-credit .foot-alt {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-underline-offset: 0.18em;
}
.foot-credit .foot-alt:hover,
.foot-credit .foot-alt:focus-visible { color: var(--accent); text-decoration-color: currentColor; }
/* Printer's-chop mark — sits next to the copyright line, dimmed so it
   reads as a watermark stamp rather than a logo. Sizes a touch larger
   than the surrounding mono text. */
.foot-stamp {
  display: inline-block;
  /* Tunable via the temporary ?tweak=1 control panel. The fallbacks
     are the current locked-in values; adjust those once a final
     design is settled, then remove the panel + var setters. */
  width:           var(--foot-stamp-size,    1.8em);
  height:          var(--foot-stamp-size,    1.8em);
  fill:            var(--foot-stamp-fill,    currentColor);
  opacity:         var(--foot-stamp-opacity, 0.32);
  /* No vertical-align hack now that the mark stands alone in its own
     flex cell — its container (.foot-mark inline-flex) centers it on
     the row. The 10 px left margin that used to space it from the
     copyright text is also gone for the same reason. */
}
/* Reusable: wrap any chrome punctuation (·, —, abbreviation periods) in
   <span class="mark"> to tint it with the accent. Used across the top
   bar, brand, footer, cmdk, and panel meta. Avoid in body content. */
.mark { color: var(--accent); }
.key-hint { display: inline-flex; align-items: center; gap: 6px; color: var(--dim); }
.k-key {
  /* inline-flex centers any content (text or SVG glyph) inside the chip
     and pairs cleanly with min-width so the chip never collapses around a
     short or thin glyph. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 1px 6px;
  border-radius: 3px;
  /* Bumped from 35% → 55% — at 35% the chip outline reads as a faint
     ghost against surface-hi; 55% gives the chip real edge weight without
     becoming a hard accent stroke. */
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
  color: var(--accent);
  background: transparent;
  font-family: var(--mono);
  font-size: 10.5px;
  line-height: 1.4;
  /* Floor sized to the widest natural chip in this row ("Ctrl K" on PC ≈
     50 px at 10.5 px JetBrains Mono + 12 px padding). Every shorter chip
     (j/k, /, ⏎, ⌘K) falls back to the same 52 px so the row reads as a
     single uniform pill row instead of stair-stepping by content length.
     Mac users see slightly airy chips around the short content; that's the
     trade-off for actual visual uniformity. */
  min-width: 52px;
  font-variant-numeric: tabular-nums;
  vertical-align: middle;
}
/* Glyph-only chips: bump the SVG up to 1.1em so the rendered key icon
   sits at the same visual weight as the surrounding text-only chips
   (Unicode ⏎ at 10.5 px in JetBrains Mono renders thin and small; an
   SVG sized to 1em alone ends up reading the same way). */
.k-key--glyph .g { width: 1.1em; height: 1.1em; vertical-align: -0.18em; }

/* ====================================================
   Article (project detail) page — TUI-styled.
   Reuses the dashboard's head + tab strip + foot,
   but the main grid swaps to a 3-column reading layout.
   ==================================================== */
.article-grid {
  display: grid;
  grid-template-columns: minmax(260px, 1fr) 2.4fr;
  gap: 14px;
  padding: 14px;
  align-items: start;
}
.article-rail { display: flex; flex-direction: column; gap: 14px; }
.article-rail .panel__body { padding: 14px 18px 16px; }

.article-panel {
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--surface);
  padding: 38px 48px 44px;
  box-shadow: var(--shadow);
}

.article__header {
  margin-bottom: 28px;
  padding-bottom: 22px;
  border-bottom: 1px solid var(--rule-soft);
}
.article__kicker {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 14px;
}
.article__h1 {
  font-family: var(--sans);
  font-weight: 600;
  font-size: clamp(28px, 4vw, 42px);
  letter-spacing: -0.025em;
  line-height: 1.05;
  margin: 0;
  color: var(--fg);
}
.article__h1 em {
  font-style: normal;
  font-weight: 400;
  color: var(--accent);
}
.article__standfirst {
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.55;
  color: var(--dim);
  margin: 14px 0 0;
  max-width: 66ch;
}
.article__chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 16px; }

/* Plain-language callout sitting between the article header and the
   lede. Holds the recruiter_blurb — a 1-2 sentence translation of the
   technical tagline. Same visual register as the splash's .desk
   callout: accent left rule, mono kicker, surface-hi fill. */
.plain-words {
  margin: 0 0 32px;
  padding: 16px 20px;
  border-left: 2px solid var(--accent);
  border-radius: 0 6px 6px 0;
  background: var(--surface-hi);
  font-family: var(--sans);
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--fg);
  max-width: 66ch;
}
.plain-words__label {
  display: block;
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--accent);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin-bottom: 8px;
}

.article__lede {
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.7;
  color: var(--fg);
  margin: 0 0 32px;
  max-width: 66ch;
}

.article__h2 {
  font-family: var(--sans);
  font-weight: 600;
  font-size: 23px;
  letter-spacing: -0.015em;
  color: var(--fg);
  margin: 48px 0 14px;
}
.article__h2:first-of-type { margin-top: 8px; }
.article__h2 .chapter {
  display: block;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.18em;
  color: var(--accent);
  text-transform: uppercase;
  margin-bottom: 6px;
}
.article__h2 .chapter:empty { display: none; }

.article-panel p {
  font-family: var(--sans);
  font-size: 15.5px;
  line-height: 1.72;
  color: var(--fg);
  margin: 0 0 16px;
  max-width: 66ch;
}
.article-panel ul {
  font-family: var(--sans);
  font-size: 15.5px;
  line-height: 1.7;
  padding-left: 22px;
  margin: 0 0 18px;
  max-width: 66ch;
}
.article-panel ul li { margin-bottom: 8px; }
.article-panel h3 {
  font-family: var(--sans);
  font-weight: 600;
  font-size: 16.5px;
  margin: 22px 0 8px;
}
.article-panel code {
  font-family: var(--mono);
  font-size: 0.92em;
  color: var(--accent);
}
.article-panel a {
  color: var(--accent);
  border-bottom: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
}
.article-panel a:hover { border-bottom-color: var(--accent); }
.article-panel pre {
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.5;
  background: var(--surface-hi);
  border: 1px solid var(--rule-soft);
  padding: 14px 16px;
  border-radius: 6px;
  margin: 0 0 16px;
  overflow-x: auto;
}
.article-panel pre code { color: inherit; border: 0; }

/* Pull-quote */
.pullquote {
  margin: 32px auto;
  max-width: 50ch;
  padding: 20px 24px;
  border-left: 2px solid var(--accent);
  background: var(--surface-hi);
  border-radius: 4px;
}
.pullquote p {
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.5;
  font-weight: 500;
  color: var(--fg);
  margin: 0;
  max-width: none;
}
.pullquote cite {
  display: block;
  margin-top: 12px;
  font-family: var(--mono);
  font-style: normal;
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--dim2);
}

/* Figures inside article */
.fig--inline {
  position: relative;
  background: var(--surface-hi);
  border: 1px solid var(--rule);
  border-radius: 6px;
  padding: 30px 18px 18px;
  margin: 28px 0;
}
.fig--inline svg { display: block; width: 100%; height: auto; }
.fig--inline figcaption {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  color: var(--dim);
  text-align: center;
  margin-top: 12px;
  text-transform: none;
}
.fig__corner {
  position: absolute;
  top: 10px;
  left: 18px;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.18em;
  font-weight: 500;
  color: var(--accent);
  text-transform: uppercase;
  line-height: 1;
}

/* ---- SVG figure primitives ----
   Ported from the legacy site/styles.css during the TUI rebuild —
   these were left behind, which is why bars / pipelines / graphs /
   treemaps / timelines were rendering black-on-black against the
   warm-black panel background. Tokens (--accent, --rule, --plate
   alias for --surface-hi, --muted alias for --dim, --bg, --fg) all
   resolve cleanly under both dark and light themes. */

/* 1. Sparkbar row */
.fig--bars .bar       { fill: var(--accent); }
.fig--bars .bar-bg    { fill: var(--rule); opacity: 0.55; }
.fig--bars .bar-axis  { stroke: var(--rule); stroke-width: 1; opacity: 0.6; }
.fig--bars .bar-label,
.fig--bars .bar-value,
.fig--bars .bar-pct {
  font-family: var(--mono);
  font-size: 11px;
  fill: var(--fg);
  font-variant-numeric: tabular-nums;
}
.fig--bars .bar-label { letter-spacing: 0.02em; }
.fig--bars .bar-value { fill: var(--accent); font-weight: 500; }
.fig--bars .bar-pct   { fill: var(--dim); font-size: 10px; letter-spacing: 0.05em; }

/* 2. Pipeline diagram */
.fig--pipeline .stage-box  { fill: var(--bg); stroke: var(--accent); stroke-width: 1.25; }
.fig--pipeline .stage-text {
  font-family: var(--mono); font-size: 11px; fill: var(--fg);
  text-anchor: middle; dominant-baseline: middle;
  font-variant-numeric: tabular-nums;
}
.fig--pipeline .stage-num {
  font-family: var(--mono); font-size: 9px; fill: var(--dim);
  letter-spacing: 0.22em; text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}
.fig--pipeline .arrow      { stroke: var(--accent); stroke-width: 1.25; fill: none; opacity: 0.7; }
.fig--pipeline .arrow-head { fill: var(--accent); opacity: 0.85; }

/* 3. Node graph (dial + topology variants share the primitives) */
.fig--graph .node          { fill: var(--bg); stroke: var(--accent); stroke-width: 1.5; }
.fig--graph .node-hub      { fill: var(--accent); stroke: var(--accent); stroke-width: 2; paint-order: stroke fill; }
.fig--graph .node-hub-aura { fill: none; stroke: var(--accent); stroke-width: 1; opacity: 0.35; }
.fig--graph .spoke-dot     { fill: var(--accent); stroke: none; }
.fig--graph .node-text     {
  font-family: var(--mono); font-size: 11px; fill: var(--fg);
  text-anchor: middle; dominant-baseline: middle;
  font-variant-numeric: tabular-nums;
}
.fig--graph .node-hub-text   { fill: var(--bg); font-weight: 600; letter-spacing: 0.02em; }
.fig--graph .node-spoke-text { font-size: 10.5px; letter-spacing: 0.04em; }
.fig--graph .edge          { stroke: var(--accent); stroke-width: 1; opacity: 0.55; }

/* Dial chrome (single-hub graphs render as a clock + roster) */
.fig--graph .dial-ring        { fill: none; stroke: var(--accent); stroke-width: 0.75; stroke-dasharray: 2 4; opacity: 0.30; }
.fig--graph .dial-tick-minor  { stroke: var(--accent); stroke-width: 0.75; opacity: 0.18; }
.fig--graph .dial-tick        { stroke: var(--accent); stroke-width: 1; opacity: 0.55; }
.fig--graph .spoke-num        {
  font-family: var(--mono); font-size: 9.5px; fill: var(--accent);
  text-anchor: middle; letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums; opacity: 0.85;
}
.fig--graph .roster-title     {
  font-family: var(--mono); font-size: 10px; fill: var(--fg);
  letter-spacing: 0.18em; text-transform: uppercase;
  font-variant-numeric: tabular-nums; opacity: 0.7;
}
.fig--graph .roster-rule      { stroke: var(--rule); stroke-width: 1; }
.fig--graph .roster-num       {
  font-family: var(--mono); font-size: 11.5px; fill: var(--accent);
  font-weight: 500; font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
}
.fig--graph .roster-label     {
  font-family: var(--mono); font-size: 12.5px; fill: var(--fg);
  letter-spacing: 0.02em; text-transform: uppercase;
}
.fig--graph .roster-meta      {
  font-family: var(--mono); font-size: 9.5px; fill: var(--dim);
  letter-spacing: 0.20em; text-transform: uppercase;
}
.fig--graph .roster-meta-label {
  font-family: var(--sans); font-size: 22px; fill: var(--accent);
  font-style: italic; letter-spacing: 0.005em;
}

/* 4. Treemap */
.fig--treemap .cell        { stroke: var(--bg); stroke-width: 2; }
.fig--treemap .cell-text   {
  font-family: var(--mono); font-size: 10px; fill: var(--bg);
  font-variant-numeric: tabular-nums; letter-spacing: 0.02em;
}
.fig--treemap .cell-label  { font-size: 12px; font-weight: 500; letter-spacing: 0.04em; }
.fig--treemap .cell-value  { font-size: 16px; font-weight: 600; letter-spacing: -0.01em; }
.fig--treemap .cell-pct    { font-size: 10px; opacity: 0.85; letter-spacing: 0.06em; }

/* 5. Timeline strip */
.fig--timeline .axis       { stroke: var(--rule); stroke-width: 1; }
.fig--timeline .tick       { stroke: var(--accent); stroke-width: 1.2; }
.fig--timeline .tick-major { stroke-width: 2; }
.fig--timeline .label      { font-family: var(--mono); font-size: 10px; fill: var(--dim); }

/* Pager */
.pager {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  border-top: 1px solid var(--rule-soft);
  border-bottom: 1px solid var(--rule-soft);
  margin: 36px 0 0;
}
.pager > a, .pager > span {
  padding: 18px 16px;
  font-family: var(--mono);
  font-size: 10px;
  color: var(--dim);
  text-decoration: none;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.pager a:hover { color: var(--accent); background: var(--surface-hi); }
.pager .pager__center {
  border-left: 1px solid var(--rule-soft);
  border-right: 1px solid var(--rule-soft);
  align-items: center;
  padding: 18px 22px;
}
.pager .pager__name {
  font-family: var(--sans);
  font-size: 16px;
  letter-spacing: -0.005em;
  text-transform: none;
  font-weight: 500;
  color: var(--fg);
}
.pager .pager__next { text-align: right; align-items: flex-end; }
.pager .pager__prev--disabled, .pager .pager__next--disabled { opacity: 0.3; }

/* Colophon */
.colophon {
  margin-top: 36px;
  padding: 24px 28px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--surface);
  box-shadow: var(--shadow);
}
.colophon__head {
  text-align: center;
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 22px;
}
.colophon__cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
}
.colophon__cols > * + * {
  border-left: 1px dashed var(--rule);
  padding-left: 24px;
}
.colophon__title {
  text-align: center;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--dim2);
  padding-bottom: 8px;
  margin-bottom: 14px;
  border-bottom: 1px dashed var(--rule-soft);
}
.colophon dl { margin: 0; }
.colophon dt {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  color: var(--dim2);
  margin-bottom: 4px;
}
.colophon dd {
  font-family: var(--sans);
  font-size: 13px;
  color: var(--fg);
  margin: 0 0 12px;
  line-height: 1.5;
}
.colophon dd a { color: var(--accent); }
.num-row {
  display: flex;
  justify-content: space-between;
  padding: 6px 0;
  border-bottom: 1px dashed var(--rule-soft);
  font-family: var(--mono);
}
.num-row:last-child { border-bottom: 0; }
.num-label { font-size: 11px; color: var(--dim); }
.num-val { font-size: 12px; font-weight: 600; color: var(--accent); font-variant-numeric: tabular-nums; }

/* ---- Subpage tab bodies (about, contact, etc.) ---- */
.tab-content {
  padding: 8px 4px 8px;
  max-width: 64ch;
}
.tab-content h2 {
  font-family: var(--sans);
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--fg);
  margin: 24px 0 10px;
}
.tab-content h2:first-child { margin-top: 0; }
.tab-content p {
  font-size: 14px;
  line-height: 1.7;
  color: var(--fg);
  margin: 0 0 14px;
}
.tab-content a {
  color: var(--accent);
  border-bottom: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
}
.tab-content a:hover { border-bottom-color: var(--accent); }
.tab-content ul {
  font-size: 14px;
  line-height: 1.65;
  padding-left: 22px;
  margin: 0 0 16px;
}
.tab-content code {
  font-family: var(--mono);
  font-size: 0.92em;
  color: var(--accent);
}
.tab-content em { font-style: italic; color: var(--dim); }
.tab-content blockquote {
  border-left: 2px solid var(--accent);
  margin: 18px 0;
  padding: 4px 0 4px 16px;
  color: var(--dim);
}

.contact-mail {
  font-family: var(--mono);
  font-size: 24px;
  font-weight: 600;
  color: var(--accent);
  letter-spacing: -0.01em;
  display: inline-block;
  margin: 8px 0 24px;
}

/* ---- Mobile / narrow ---- */
@media (max-width: 1100px) {
  body { height: auto; overflow-y: auto; }
  .dash {
    height: auto;
    grid-template-rows: auto auto auto auto;
  }
  .dash__main {
    grid-template-columns: 1fr;
    overflow: visible;
  }
  .rcol { grid-template-rows: auto auto auto; min-height: 0; }
  .panel { overflow: visible; }
  .panel__body { overflow: visible; }
  .article-grid { grid-template-columns: 1fr; }
}

@media (max-width: 760px) {
  .dash__head {
    grid-template-columns: 1fr;
    gap: 6px;
    padding: 12px 14px;
  }
  .head-mid { display: none; }
  .head-right { font-size: 10.5px; gap: 10px; justify-self: start; flex-wrap: wrap; }
  .dash__tabs { padding: 0 8px; }
  .tab { padding: 10px 10px; }
  .inv-head { display: none; }
  .inv-row {
    grid-template-columns: 50px 1fr 50px;
    column-gap: 10px;
    grid-template-areas: "idx main year" "tags tags tags";
    padding: 12px 8px;
  }
  .inv-row > .r-idx { grid-area: idx; }
  .inv-row > .r-kind { display: none; }
  .inv-row > .r-main { grid-area: main; }
  .inv-row > .r-tags { grid-area: tags; }
  .inv-row > .r-year { grid-area: year; }
  .article-panel { padding: 24px 22px 32px; }
  .article__h1 { font-size: 26px; }
  .colophon__cols { grid-template-columns: 1fr; }
  .colophon__cols > * + * {
    border-left: 0;
    border-top: 1px dashed var(--rule);
    padding-left: 0;
    padding-top: 18px;
  }
  .pager { grid-template-columns: 1fr; }
  .pager .pager__center, .pager .pager__next {
    border-left: 0;
    border-right: 0;
    border-top: 1px solid var(--rule-soft);
    text-align: left;
    align-items: flex-start;
  }
  .foot-keys { display: none; }
}

/* ---- Command palette (cmdk) ---- */
.cmdk {
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--bg);
  color: var(--fg);
  padding: 0;
  width: min(92vw, 580px);
  max-height: 70vh;
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6);
}
.cmdk::backdrop { background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(2px); }
.cmdk__form { display: flex; flex-direction: column; max-height: 70vh; }
.cmdk__header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--rule-soft);
  font-family: var(--mono);
}
.cmdk__prompt { color: var(--accent); font-size: 12px; letter-spacing: 0.06em; }
.cmdk__input {
  flex: 1;
  background: transparent;
  border: 0;
  outline: none;
  color: var(--fg);
  font-family: var(--sans);
  font-size: 14px;
}
.cmdk__esc {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--dim2);
  border: 1px solid var(--rule);
  background: var(--surface-hi);
  padding: 1px 6px;
  border-radius: 3px;
}
.cmdk__results { overflow-y: auto; flex: 1; padding: 6px 0; }
.cmdk__row {
  display: grid;
  grid-template-columns: 70px 1fr auto;
  align-items: baseline;
  gap: 12px;
  padding: 8px 14px;
  text-decoration: none;
  color: inherit;
}
.cmdk__row.is-active { background: var(--surface-hi); }
.cmdk__row-kind {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--dim2);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.cmdk__row-body { display: flex; flex-direction: column; min-width: 0; }
.cmdk__row-label { font-size: 13.5px; color: var(--fg); font-weight: 500; }
.cmdk__row-sub {
  font-size: 12px;
  color: var(--dim);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cmdk__row-tag {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--dim2);
  white-space: nowrap;
}
.cmdk__empty {
  padding: 20px 14px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--dim2);
  text-align: center;
  letter-spacing: 0.04em;
}
.cmdk__footer {
  border-top: 1px solid var(--rule-soft);
  padding: 8px 14px;
  display: flex;
  justify-content: space-between;
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--dim2);
  letter-spacing: 0.04em;
}

/* ---- OS-aware keyboard glyphs ----
   <html> gets .is-mac (macOS/iOS) or .is-pc (everyone else) set
   pre-paint by the script in partials_head(). Markup carries both
   .kbd-mac and .kbd-pc spans; CSS hides the wrong one. Default is
   to show the mac variant so a no-JS reader still gets a working
   visual. */
.kbd-pc { display: none; }
html.is-pc .kbd-mac { display: none; }
html.is-pc .kbd-pc  { display: inline; }

/* ---- Inline SVG glyphs (sprite.svg #g-*) ----
   Sized to current text via 1em; picks up parent text color via
   currentColor. Use: <svg class="g" aria-hidden="true"><use href="#g-tool"/></svg> */
.g {
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
  fill: currentColor;
  flex-shrink: 0;
}
.g--lg { font-size: 1.25em; }

/* ---- Reduced motion ---- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ====================================================
   2026-05-08 wow-lift — production additions, lifted
   from /wow/ lab after design review. Four prototypes:
   02 hover→media, 03 boot sequence, 05 generative hero,
   06 view transitions. CSS for 01 (ambient backdrop)
   and 04 (live ticker) deliberately omitted.
   ==================================================== */

/* ---- 02. Hover → media (splash Detail panel) ----
   The Detail panel's existing layout is preserved; the media block
   sits at the top as a hero strip. Canvas is the always-on default
   (zero asset weight); the &lt;video&gt; overlay activates if/when an
   MP4 lands at /assets/projects/&lt;slug&gt;.mp4. */
.detail-media {
  position: relative;
  aspect-ratio: 16 / 9;
  background: #000;
  border: 1px solid var(--rule);
  border-radius: 6px;
  overflow: hidden;
  margin-bottom: 16px;
}
.detail-media__canvas,
.detail-media__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.detail-media__video {
  opacity: 0;
  transition: opacity 320ms ease;
  pointer-events: none;
}
.detail-media__video.is-loaded { opacity: 1; }
.detail-media__canvas { transition: opacity 320ms ease; }
.detail-media__canvas.is-hidden { opacity: 0; }

/* ---- 03. Boot sequence ----
   Fires once per tab session via sessionStorage flag set in app.js.
   .dash gets data-boot="play" only on the first eligible load; CSS
   keyframes activate from the attribute. Per-element delay is set
   inline by JS (one rAF before play). */
@keyframes sw-boot-fade {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes sw-boot-row {
  from { opacity: 0; transform: translateX(-6px); }
  to   { opacity: 1; transform: translateX(0); }
}
@keyframes sw-boot-scan {
  0%   { opacity: 0;    top: 0; }
  10%  { opacity: 0.35; }
  90%  { opacity: 0.35; }
  100% { opacity: 0;    top: calc(100% - 1px); }
}
.dash[data-boot="play"] .panel,
.dash[data-boot="play"] .article-panel {
  animation: sw-boot-fade 320ms ease forwards;
  animation-delay: var(--boot-d, 0ms);
  opacity: 0;
}
.dash[data-boot="play"] .panel {
  position: relative;
}
.dash[data-boot="play"] .panel::after {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 1px;
  background: color-mix(in srgb, var(--accent) 55%, transparent);
  box-shadow: 0 0 4px color-mix(in srgb, var(--accent) 40%, transparent);
  opacity: 0;
  pointer-events: none;
  z-index: 5;
  animation: sw-boot-scan 280ms ease-out forwards;
  animation-delay: var(--boot-d, 0ms);
}
.dash[data-boot="play"] .inv-row {
  animation: sw-boot-row 220ms ease forwards;
  animation-delay: var(--row-d, 0ms);
  opacity: 0;
}
@media (prefers-reduced-motion: reduce) {
  .dash[data-boot="play"] .panel,
  .dash[data-boot="play"] .article-panel,
  .dash[data-boot="play"] .inv-row {
    animation-duration: 80ms !important;
    transform: none !important;
  }
  .dash[data-boot="play"] .panel::after { display: none; }
}

/* ---- 05. Generative project hero ----
   Canvas sits behind the article header on /projects/<slug>/. Subtle
   (~55% canvas opacity); pauses off-screen via IntersectionObserver
   in app.js. Project-specific signature seeded from the slug. */
.article__hero {
  position: relative;
  isolation: isolate;
  /* Bleed canvas past the article-panel inner padding so the field
     touches the panel edge instead of leaving a thin token-bg gutter. */
  margin: -38px -48px 28px;
  padding: 38px 48px 22px;
  border-bottom: 1px solid var(--rule-soft);
}
.article__hero-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  opacity: 0.6;
  z-index: -1;
}
@media (max-width: 760px) {
  .article__hero {
    margin: -24px -22px 22px;
    padding: 24px 22px 18px;
  }
}

/* ---- 06. View transitions ----
   Cross-document VT (Chromium 126+, Safari 18+). Both pages opt in
   via @view-transition; matching `view-transition-name` on the
   inventory row (.r-name) and the project page H1 morphs between
   them. Browsers without support fall back to standard navigation. */
@view-transition { navigation: auto; }
::view-transition-old(*),
::view-transition-new(*),
::view-transition-group(*) {
  animation-duration: 380ms;
  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(*),
  ::view-transition-new(*),
  ::view-transition-group(*) {
    animation-duration: 0.01ms !important;
  }
}
/* The view-transition-name itself is set inline per element in PHP
   (e.g. style="view-transition-name: proj-secnull"). Keeping the name
   server-rendered means it works without JS and survives back/forward
   nav cleanly. */
