daemon-sec-lotl

DÆMONBins: GTFOBins × LOLBAS × WADComs × LOOBins in one filterable catalog
git clone https://git.daemon-sec.xyz/daemon-sec-lotl.git
Log | Files | Refs | Submodules | README | LICENSE

global.css (44360B)


      1 /* ============================================================================
      2    Base elements, layout, chrome, and components.
      3    Built out of the objects in chrome.css. Nothing here rounds a corner,
      4    blurs a background, or casts a shadow — see the form-language note at
      5    the top of tokens.css for why.
      6    ========================================================================== */
      7 
      8 *, *::before, *::after { box-sizing: border-box; }
      9 * { margin: 0; }
     10 
     11 html {
     12   color-scheme: light;
     13   scroll-behavior: smooth;
     14   scroll-padding-top: 82px;
     15   -webkit-text-size-adjust: 100%;
     16 }
     17 :root[data-theme='dark'] { color-scheme: dark; }
     18 
     19 body {
     20   font-family: var(--font-body);
     21   font-size: var(--step-0);
     22   line-height: 1.6;
     23   color: var(--fg);
     24   background: var(--bg);
     25   min-height: 100vh;
     26   -webkit-font-smoothing: antialiased;
     27   text-rendering: optimizeLegibility;
     28   overflow-x: hidden;
     29 }
     30 
     31 ::selection { background: var(--selection); }
     32 
     33 a { color: inherit; text-decoration: none; }
     34 img, svg { display: block; max-width: 100%; }
     35 button { font: inherit; color: inherit; cursor: pointer; background: none; border: none; }
     36 
     37 h1, h2, h3, h4 {
     38   font-family: var(--font-display);
     39   font-weight: 700;
     40   line-height: 1.06;
     41   letter-spacing: -0.02em;
     42 }
     43 
     44 /* Square, like everything else — a rounded focus ring on a square page
     45    reads as a rendering artefact rather than as focus. */
     46 :focus-visible {
     47   outline: 2px solid var(--accent);
     48   outline-offset: 2px;
     49 }
     50 
     51 /* Page content sits above the grain film. */
     52 main, .site-header, .site-footer { position: relative; z-index: 2; }
     53 
     54 /* Script adds these hooks only after the content is ready, which prevents a
     55    failed bundle from leaving the archive invisible. The tiny lateral drift
     56    keeps the entrance in the site's terminal/register language rather than a
     57    generic card float. */
     58 @media (prefers-reduced-motion: no-preference) {
     59   /* The daemon-sec entrance: about a second on a curve that spends most
     60      of its travel in the last fifth, so a block is seen arriving rather
     61      than noticed having arrived. */
     62   .scroll-reveal {
     63     opacity: 0;
     64     transform: translate3d(0, 28px, 0);
     65     transition:
     66       opacity var(--dur-reveal) var(--ease-out-expo) var(--reveal-delay, 0ms),
     67       transform var(--dur-reveal) var(--ease-out-expo) var(--reveal-delay, 0ms);
     68     will-change: opacity, transform;
     69   }
     70   .scroll-reveal.is-revealed {
     71     opacity: 1;
     72     transform: translate3d(0, 0, 0);
     73     will-change: auto;
     74   }
     75 }
     76 
     77 .scroll-progress {
     78   position: fixed;
     79   z-index: 90;
     80   inset: 0 0 auto;
     81   height: 2px;
     82   pointer-events: none;
     83   background: linear-gradient(90deg, var(--pine), var(--iris), var(--love));
     84   transform: scaleX(var(--scroll-progress, 0));
     85   transform-origin: 0 50%;
     86 }
     87 @media (prefers-reduced-motion: no-preference) {
     88   .scroll-progress { transition: transform 90ms linear; }
     89 }
     90 
     91 /* ---- Layout -------------------------------------------------------------- */
     92 .wrap {
     93   width: 100%;
     94   max-width: var(--maxw);
     95   margin-inline: auto;
     96   padding-inline: clamp(1.15rem, 3.2vw, 2.75rem);
     97 }
     98 /* Full-bleed: a band breaks the measure and runs edge to edge, but its
     99    *contents* return to it. This is the page's basic rhythm — paper on the
    100    measure, bands across the whole width. */
    101 .bleed { width: 100%; }
    102 .stack > * + * { margin-top: var(--gap, 1rem); }
    103 .accent-text { color: var(--accent); }
    104 
    105 /* ---- Masthead ------------------------------------------------------------ */
    106 /* Opaque, not frosted: the bar has to hold its own value against whatever
    107    scrolls under it, and on this page that is as often a dark band as it is
    108    cream. A translucent bar over a passing band inverts mid-scroll. */
    109 .site-header {
    110   position: sticky;
    111   top: 0;
    112   z-index: 60;
    113   border-bottom: 1px solid var(--fg);
    114   background: var(--base);
    115 }
    116 /* The `.segbar` object (chrome.css) draws a `border-block`; the header sits
    117    flush with the viewport top, where that top hairline reads as a stray line
    118    above the bar — keep the bottom rule only. */
    119 .site-header.segbar { border-top: none; }
    120 /* No gaps inside the bar: the brand/nav split is carried by the auto margin
    121    on `.mast-nav`, and the segments within each cluster sit flush so the
    122    hairlines `.seg + .seg` draws are the only dividers — tmux, not a row of
    123    boxed buttons. */
    124 .site-header .wrap {
    125   display: flex;
    126   align-items: stretch;
    127   gap: 0;
    128   min-height: 58px;
    129 }
    130 
    131 /* The wordmark segment of the status bar. Noto Sans Display — heavy,
    132    compressed, uppercase, tracked tight — is the daemon-sec signature face;
    133    the masthead brand is its smallest application. Same `.brand` hook as
    134    before, so the footer lockup picks the face up too. */
    135 .brand {
    136   font-family: var(--font-wordmark);
    137   font-weight: 850;
    138   font-stretch: 70%;
    139   text-transform: uppercase;
    140   font-size: 1.18rem;
    141   letter-spacing: 0.01em;
    142   display: inline-flex;
    143   align-items: center;
    144   gap: 0.06rem;
    145   white-space: nowrap;
    146   color: var(--fg);
    147 }
    148 .brand .slash { color: var(--accent); }
    149 .brand .blink { color: var(--accent); }
    150 @media (prefers-reduced-motion: no-preference) {
    151   .brand .blink { animation: dmn-blink 1.15s steps(1) infinite; }
    152 }
    153 /* On the masthead the brand is the accent-filled left segment
    154    (`.seg--accent`), so its ink flips to the base and the glyph garnish
    155    follows — accent slashes on an accent ground would be invisible. The
    156    footer lockup keeps the un-filled treatment above. Scoped rather than
    157    changed in place because `.brand` is shared with the footer. */
    158 .site-header .brand { color: var(--base); }
    159 .site-header .brand .slash,
    160 .site-header .brand .blink { color: color-mix(in oklab, var(--base) 78%, transparent); }
    161 
    162 /* The numbered nav. The entries are `.seg`s, so the JetBrains Mono
    163    furniture type, the full-height flex and the hairline dividers all come
    164    from chrome.css; what is left here is the colour logic. Each entry owns a
    165    hue and its index wears it, so the bar reads as the colour legend the
    166    rest of the site keys back to rather than as five grey words. The index
    167    keeps its space at rest — revealing it on hover would reflow the whole
    168    bar every time the pointer crossed an item. */
    169 .mast-nav {
    170   display: flex;
    171   align-items: stretch;
    172   margin-left: auto;
    173 }
    174 .mast-nav a {
    175   color: var(--fg-dim);
    176   transition: color 200ms ease, background-color 220ms ease;
    177 }
    178 .mast-nav a:hover { color: var(--fg); background: var(--wash-strong); }
    179 .mast-nav a[aria-current='page'] { color: var(--fg); }
    180 .mast-nav .n {
    181   font-size: var(--step-micro);
    182   color: var(--fg-faint);
    183   transition: color 200ms ease;
    184 }
    185 .mast-nav a:hover .n,
    186 .mast-nav a[aria-current='page'] .n { color: var(--acc); }
    187 @media (max-width: 900px) { .mast-nav .lbl { display: none; } }
    188 @media (max-width: 640px) { .mast-nav { display: none; } }
    189 
    190 .icon-btn.mobile-nav-trigger { display: none; }
    191 .mobile-nav-glyph {
    192   display: grid;
    193   gap: 4px;
    194   width: 16px;
    195 }
    196 .mobile-nav-glyph i {
    197   display: block;
    198   width: 100%;
    199   height: 1px;
    200   background: currentColor;
    201   transform-origin: center;
    202   transition: transform 180ms ease, opacity 140ms ease;
    203 }
    204 .mobile-nav-trigger[aria-expanded='true'] .mobile-nav-glyph i:first-child {
    205   transform: translateY(5px) rotate(45deg);
    206 }
    207 .mobile-nav-trigger[aria-expanded='true'] .mobile-nav-glyph i:nth-child(2) { opacity: 0; }
    208 .mobile-nav-trigger[aria-expanded='true'] .mobile-nav-glyph i:last-child {
    209   transform: translateY(-5px) rotate(-45deg);
    210 }
    211 
    212 .mobile-nav { display: none; }
    213 @media (max-width: 640px) {
    214   .icon-btn.mobile-nav-trigger { display: grid; }
    215   .mobile-nav:not([hidden]) {
    216     position: absolute;
    217     top: 100%;
    218     left: 0;
    219     right: 0;
    220     display: grid;
    221     background: var(--base);
    222     border-bottom: 1px solid var(--fg);
    223   }
    224   .mobile-nav a {
    225     position: relative;
    226     display: grid;
    227     grid-template-columns: 2.25rem 1fr;
    228     align-items: center;
    229     gap: 0.8rem;
    230     min-height: 48px;
    231     padding: 0.65rem clamp(1.15rem, 3.2vw, 2.75rem);
    232     border-bottom: 1px solid var(--rule);
    233     font-family: var(--font-mono);
    234     font-size: 11px;
    235     letter-spacing: var(--track-micro);
    236     text-transform: uppercase;
    237     color: var(--fg-dim);
    238   }
    239   .mobile-nav a:last-child { border-bottom: 0; }
    240   .mobile-nav a::before {
    241     content: '';
    242     position: absolute;
    243     inset: 0 auto 0 0;
    244     width: 3px;
    245     background: var(--acc);
    246     transform: scaleY(0);
    247     transform-origin: 50% 100%;
    248     transition: transform 180ms var(--ease-in);
    249   }
    250   .mobile-nav a:hover,
    251   .mobile-nav a[aria-current='page'] {
    252     color: var(--fg);
    253     background: color-mix(in oklab, var(--acc) 9%, transparent);
    254   }
    255   .mobile-nav a:hover::before,
    256   .mobile-nav a[aria-current='page']::before { transform: scaleY(1); }
    257   .mobile-nav .n { color: var(--acc); }
    258 }
    259 
    260 /* The action cluster runs the full height of the bar, flush against the
    261    nav, so its own border-left is the divider between the two clusters and
    262    `.seg + .seg` draws the rest. */
    263 .mast-actions {
    264   display: flex;
    265   align-items: stretch;
    266   border-left: 1px solid var(--rule);
    267 }
    268 /* The base `.icon-btn` is a small boxed control — the search modal's close
    269    button still uses it as one. In the segbar the same buttons are segments,
    270    so the box comes off and they stretch to the bar's full height; the
    271    scoped rules below carry that without touching the modal. */
    272 .icon-btn {
    273   width: 34px; height: 34px;
    274   display: grid; place-items: center;
    275   border: 1px solid var(--rule);
    276   color: var(--fg-dim);
    277   transition: color 200ms ease, border-color 220ms ease, background-color 240ms ease;
    278 }
    279 .icon-btn:hover { color: var(--base); background: var(--fg); border-color: var(--fg); }
    280 .icon-btn svg { width: 17px; height: 17px; }
    281 .mast-actions .icon-btn {
    282   width: auto; height: auto;
    283   padding-inline: 0.8rem;
    284   border: 0;
    285 }
    286 .mast-actions .icon-btn:hover { color: var(--fg); background: var(--wash-strong); }
    287 /* `border: 0` above ties `.seg + .seg` in specificity and would eat the
    288    dividers between the action segments, so they are re-asserted here. */
    289 .mast-actions .seg + .seg { border-inline-start: 1px solid var(--rule); }
    290 
    291 /* The search segment. `.seg` carries the JetBrains Mono furniture type and
    292    the full-height flex; this block is only the state colours and the kbd. */
    293 .search-trigger {
    294   color: var(--fg-dim);
    295   transition: color 200ms ease, background-color 220ms ease;
    296 }
    297 .search-trigger:hover { color: var(--fg); background: var(--wash-strong); }
    298 .search-trigger kbd {
    299   font-family: var(--font-term); font-size: var(--step-micro);
    300   padding: 0.08rem 0.32rem;
    301   border: 1px solid var(--rule); color: var(--fg-faint);
    302 }
    303 .search-trigger .st-label { display: none; }
    304 @media (min-width: 560px) { .search-trigger .st-label { display: inline; } }
    305 
    306 @media (max-width: 420px) {
    307   .site-header .wrap { padding-inline: 0.65rem; }
    308   .brand { font-size: 0.93rem; }
    309   .mast-actions .icon-btn { padding-inline: 0.55rem; }
    310   .search-trigger { padding-inline: 0.55rem; }
    311   .search-trigger kbd { display: none; }
    312 }
    313 
    314 /* ---- Nav rail (desktop) -------------------------------------------------- */
    315 .layout { display: grid; grid-template-columns: 1fr; gap: 2.5rem; }
    316 @media (min-width: 960px) {
    317   .layout.has-rail { grid-template-columns: var(--rail) minmax(0, 1fr); }
    318 }
    319 .rail { position: sticky; top: 78px; align-self: start; max-height: calc(100vh - 100px); overflow-y: auto; display: none; }
    320 @media (min-width: 960px) { .rail { display: block; } }
    321 .rail h4 {
    322   font-family: var(--font-mono); font-size: var(--step-micro);
    323   letter-spacing: var(--track-wide);
    324   text-transform: uppercase; color: var(--fg-faint);
    325   margin: 1.3rem 0 0.6rem;
    326   padding-bottom: 0.5rem;
    327   border-bottom: 1px solid var(--rule);
    328 }
    329 .rail a {
    330   display: flex; align-items: center; gap: 0.5rem;
    331   padding: 0.34rem 0 0.34rem 0.7rem;
    332   color: var(--fg-dim); font-size: 0.88rem;
    333   border-left: 2px solid var(--rule);
    334   transition: color 180ms ease, border-color 180ms ease, padding-left 260ms var(--ease-in);
    335 }
    336 .rail a:hover { color: var(--fg); border-left-color: var(--fg); padding-left: 1rem; }
    337 .rail a[aria-current='page'] { color: var(--accent); border-left-color: var(--accent); }
    338 .rail .rail-tag { font-family: var(--font-mono); font-size: var(--step-micro); color: var(--fg-faint); margin-left: auto; }
    339 
    340 /* ---- Badges + tags ------------------------------------------------------- */
    341 .badge {
    342   display: inline-flex; align-items: center; gap: 0.35rem;
    343   font-family: var(--font-mono); font-size: var(--step-micro);
    344   letter-spacing: var(--track-micro);
    345   padding: 0.22rem 0.5rem;
    346   border: 1px solid var(--rule); color: var(--fg-dim);
    347   text-transform: uppercase;
    348 }
    349 .badge svg { width: 12px; height: 12px; }
    350 .badge--beginner { color: var(--foam); border-color: color-mix(in oklab, var(--foam) 50%, transparent); }
    351 .badge--intermediate { color: var(--gold); border-color: color-mix(in oklab, var(--gold) 50%, transparent); }
    352 .badge--advanced { color: var(--love); border-color: color-mix(in oklab, var(--love) 50%, transparent); }
    353 
    354 .tag-pill {
    355   font-family: var(--font-mono); font-size: var(--step-micro);
    356   letter-spacing: var(--track-micro);
    357   text-transform: uppercase;
    358   padding: 0.24rem 0.55rem;
    359   border: 1px solid var(--rule); color: var(--fg-dim);
    360   transition: color 180ms ease, border-color 180ms ease, background-color 220ms ease;
    361 }
    362 .tag-pill:hover { color: var(--base); background: var(--fg); border-color: var(--fg); }
    363 
    364 .tags-row { display: flex; flex-wrap: wrap; gap: 0.4rem; }
    365 
    366 /* ---- Domain plates ------------------------------------------------------- */
    367 /* The twelve domains, as tiles. A tile is a slab with its hue on the top
    368    edge and nowhere else: twelve fully-coloured cards would be a paint
    369    chart, and the point of the colour is to key the domain, not to fill
    370    the grid. */
    371 .grid-plates {
    372   display: grid;
    373   grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
    374   gap: 1px;
    375   background: var(--rule);
    376   border: 1px solid var(--rule);
    377 }
    378 /* One-pixel gaps over an ink ground rather than real gutters: the grid
    379    draws its own rules, so the twelve tiles read as one ruled table
    380    instead of as twelve floating objects. */
    381 .plate-tile {
    382   display: flex;
    383   flex-direction: column;
    384   gap: 0.5rem;
    385   padding: 1.15rem 1.2rem 1.25rem;
    386   background: var(--base);
    387   border-top: 2px solid var(--acc);
    388   transition: background-color 240ms ease;
    389 }
    390 .plate-tile:hover { background: var(--wash-strong); }
    391 .plate-tile__head {
    392   display: flex;
    393   align-items: baseline;
    394   justify-content: space-between;
    395   gap: 0.6rem;
    396 }
    397 .plate-tile__tag {
    398   font-family: var(--font-mono); font-size: var(--step-micro);
    399   letter-spacing: var(--track-wide); text-transform: uppercase;
    400   color: var(--acc);
    401 }
    402 .plate-tile__n {
    403   font-family: var(--font-mono); font-size: var(--step-micro);
    404   letter-spacing: var(--track-micro); color: var(--fg-faint);
    405 }
    406 .plate-tile__title {
    407   font-family: var(--font-display);
    408   font-size: 1.32rem;
    409   font-weight: 700;
    410   font-stretch: 84%;
    411   letter-spacing: -0.03em;
    412   line-height: 0.98;
    413   text-transform: uppercase;
    414   color: var(--fg);
    415 }
    416 .plate-tile__blurb { color: var(--fg-dim); font-size: 0.875rem; line-height: 1.5; }
    417 .plate-tile__meta {
    418   margin-top: auto;
    419   padding-top: 0.7rem;
    420   display: flex; align-items: center; justify-content: space-between;
    421   font-family: var(--font-mono); font-size: var(--step-micro);
    422   letter-spacing: var(--track-micro); text-transform: uppercase;
    423   color: var(--fg-faint);
    424 }
    425 .plate-tile__arrow { color: var(--acc); transition: transform 250ms var(--ease); }
    426 .plate-tile:hover .plate-tile__arrow { transform: translateX(4px); }
    427 
    428 /* ---- Record rows --------------------------------------------------------- */
    429 /* The archive's core object: number, kind mark, title, meta — one line,
    430    not a card. A title set as a display heading gives every entry the
    431    weight of a section and makes a list of fifty unscannable; the point of
    432    an index is the shape of the whole list, and that only reads if a row
    433    is a row. */
    434 .index { border-top: 1px solid var(--rule); }
    435 .record {
    436   display: block;
    437   border-bottom: 1px solid var(--rule);
    438   padding: 1.05rem clamp(0.6rem, 1.6vw, 1.3rem);
    439   color: var(--fg);
    440 }
    441 .record:hover { color: var(--base); }
    442 /* Every cell placed explicitly rather than left to auto-flow. Narrow, the
    443    row is two lines — number, title and chevron across the top, kind
    444    underneath — and at md it collapses to the single line the index is
    445    really for. Auto-placement cannot express both: the title has to take
    446    the full middle on mobile, and the moment it does the chevron is pushed
    447    onto a line of its own. */
    448 .record__grid {
    449   display: grid;
    450   grid-template-columns: 30px minmax(0, 1fr) auto;
    451   align-items: center;
    452   column-gap: 1.05rem;
    453   row-gap: 0.35rem;
    454 }
    455 @media (min-width: 768px) {
    456   .record__grid { grid-template-columns: 42px 112px minmax(0, 1fr) 108px 18px; row-gap: 0; }
    457 }
    458 .record__n {
    459   grid-column: 1; grid-row: 1;
    460   font-family: var(--font-mono); font-size: var(--step-micro);
    461   letter-spacing: var(--track-micro); text-transform: uppercase;
    462   color: var(--accent);
    463 }
    464 .record:hover .record__n { color: rgba(250, 244, 237, 0.78); }
    465 .record__kind {
    466   grid-column: 2; grid-row: 2;
    467   display: flex; align-items: center; gap: 0.45rem;
    468   font-family: var(--font-mono); font-size: var(--step-micro);
    469   letter-spacing: var(--track-micro); text-transform: uppercase;
    470   color: var(--acc, var(--fg-faint));
    471 }
    472 @media (min-width: 768px) { .record__kind { grid-row: 1; } }
    473 /* On the hover ink the dawn accents sit at roughly 1:1, so every one of
    474    them takes a cream weight rather than keeping a hue nobody could read.
    475    /78 is the floor for the meta layer — cream on ink clears AA at /74 and
    476    these are 10px, so they get a little over the line rather than sitting
    477    on it. */
    478 .record:hover .record__kind,
    479 .record:hover .record__meta { color: rgba(250, 244, 237, 0.78); }
    480 .record__kind svg { width: 12px; height: 12px; }
    481 .record__title {
    482   grid-column: 2; grid-row: 1;
    483   font-family: var(--font-display);
    484   font-size: 1.02rem;
    485   font-weight: 600;
    486   letter-spacing: -0.015em;
    487   min-width: 0;
    488   overflow: hidden;
    489   text-overflow: ellipsis;
    490   white-space: nowrap;
    491 }
    492 @media (min-width: 768px) { .record__title { grid-column: 3; } }
    493 .record__meta {
    494   display: none;
    495   font-family: var(--font-mono); font-size: var(--step-micro);
    496   letter-spacing: var(--track-micro); text-transform: uppercase;
    497   color: var(--fg-faint);
    498 }
    499 @media (min-width: 768px) { .record__meta { display: block; grid-column: 4; grid-row: 1; } }
    500 .record__arrow {
    501   grid-column: 3; grid-row: 1;
    502   color: var(--fg-faint);
    503   transition: transform 250ms var(--ease);
    504 }
    505 @media (min-width: 768px) { .record__arrow { grid-column: 5; } }
    506 .record:hover .record__arrow { color: rgba(250, 244, 237, 0.78); transform: translateX(3px); }
    507 .record__arrow svg { width: 15px; height: 15px; }
    508 
    509 /* ---- Hero ---------------------------------------------------------------- */
    510 /* The lockup over the ledger. Full-bleed cream with an ink rule under it,
    511    which is the page's hardest value break and the reason the marquee
    512    below needs no top edge of its own. */
    513 .hero {
    514   position: relative;
    515   overflow: hidden;
    516   border-bottom: 1px solid var(--fg);
    517   background: var(--surface);
    518 }
    519 .hero__inner { padding-top: clamp(1.6rem, 3.4vw, 2.6rem); }
    520 /* Sized in `cqw` against the text column rather than in `vw` against the
    521    window, so the lockup fills the space it actually has. In `vw` it is
    522    sized against a viewport the column stops tracking at `--maxw`, and past
    523    that width the type and the rule under it drift out of line with the
    524    rest of the page. */
    525 .hero__lockup { container-type: inline-size; }
    526 .hero__line { height: 18.1cqw; }
    527 .hero__word {
    528   font-family: var(--font-lockup);
    529   font-size: 21cqw;
    530   line-height: 0.86;
    531   font-weight: 600;
    532   letter-spacing: 0.012em;
    533   color: var(--fg);
    534 }
    535 @media (prefers-reduced-motion: no-preference) {
    536   /* The line keeps settling after the letters have landed: the glyphs
    537      finish at 780ms, the tracking at 1500ms. */
    538   .hero__word { animation: dmn-track-in 1500ms var(--ease-in) both; }
    539   .hero__glyph { animation: dmn-up 780ms var(--ease-in) both; }
    540 }
    541 .hero__glyph {
    542   display: inline-block;
    543   transition: color 220ms ease;
    544 }
    545 /* Six letters, six accents, taken under the pointer rather than worn at
    546    rest — the lockup teaches the key the rest of the site is coloured by,
    547    but only to someone who goes looking. */
    548 .hero__glyph:nth-child(1):hover { color: var(--pine); }
    549 .hero__glyph:nth-child(2):hover { color: var(--iris); }
    550 .hero__glyph:nth-child(3):hover { color: var(--love); }
    551 .hero__glyph:nth-child(4):hover { color: var(--rose); }
    552 .hero__glyph:nth-child(5):hover { color: var(--gold); }
    553 .hero__glyph:nth-child(6):hover { color: var(--foam); }
    554 
    555 /* Ink rule. Timed to land as the last glyph settles — six glyphs at 54ms
    556    plus the tail of the 780ms keyframe. Width in `cqw` for the same reason
    557    the lockup is: a viewport-relative rule outgrows the column. */
    558 .hero__rule {
    559   display: block;
    560   height: 3px;
    561   width: 64cqw;
    562   margin-top: 0.6cqw;
    563   transform-origin: 0 50%;
    564   background: var(--pine);
    565 }
    566 @media (prefers-reduced-motion: no-preference) {
    567   .hero__rule { animation: dmn-draw 720ms var(--ease-in) 780ms both; }
    568 }
    569 .hero__subline { height: 7cqw; margin-top: 0.9cqw; }
    570 .hero__sub {
    571   font-family: var(--font-display);
    572   font-size: 5.5cqw;
    573   line-height: 0.82;
    574   font-weight: 700;
    575   font-stretch: 62%;
    576   letter-spacing: 0.045em;
    577   text-transform: uppercase;
    578   color: var(--fg);
    579   transform: scaleY(1.5);
    580 }
    581 /* One gesture rather than letter by letter: it is a subtitle, and two
    582    per-glyph runs in a row read as an effect rather than as an entrance. */
    583 .hero__sub > span { display: inline-block; }
    584 @media (prefers-reduced-motion: no-preference) {
    585   .hero__sub > span { animation: dmn-wipe-in 860ms cubic-bezier(0.22, 0.61, 0.36, 1) 380ms both; }
    586 }
    587 
    588 .hero__ledger {
    589   margin-top: clamp(1.4rem, 3vw, 2.4rem);
    590   padding-bottom: clamp(2rem, 4vw, 3rem);
    591 }
    592 .hero__card {
    593   display: grid;
    594   gap: 1.6rem 2.6rem;
    595   border: 1px solid var(--fg);
    596   background: var(--base);
    597   padding: clamp(1.3rem, 2.4vw, 1.9rem);
    598 }
    599 @media (min-width: 720px) { .hero__card { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); } }
    600 .hero__eyebrow {
    601   font-family: var(--font-mono); font-size: 10.5px;
    602   letter-spacing: 0.2em; text-transform: uppercase; color: var(--pine);
    603 }
    604 .hero__h2 {
    605   margin-top: 0.85rem;
    606   font-family: var(--font-display);
    607   font-size: clamp(1.6rem, 2.5vw, 2.4rem);
    608   line-height: 0.94;
    609   font-weight: 700;
    610   font-stretch: 84%;
    611   letter-spacing: -0.03em;
    612   text-transform: uppercase;
    613 }
    614 .hero__blurb { margin-top: 0.9rem; max-width: 46ch; color: var(--fg); font-size: 0.95rem; line-height: 1.5; }
    615 .hero__actions { margin-top: 1.2rem; display: flex; flex-wrap: wrap; gap: 0.6rem; }
    616 
    617 .hero__stats {
    618   display: grid;
    619   grid-template-columns: repeat(auto-fit, minmax(94px, 1fr));
    620   gap: 1rem 1.3rem;
    621   align-content: start;
    622   border-top: 1px solid var(--fg);
    623   padding-top: 1.1rem;
    624   font-family: var(--font-mono);
    625   font-size: 11px;
    626   line-height: 1.6;
    627   letter-spacing: 0.14em;
    628   text-transform: uppercase;
    629 }
    630 .hero__stat b { font-weight: 400; }
    631 .hero__stat--iris b { color: var(--iris); }
    632 .hero__stat--love b { color: var(--love); }
    633 .hero__stat--gold b { color: var(--gold); }
    634 
    635 /* The shell readout beside the card. Borderless on purpose: the card is
    636    the printed spec, this is the terminal output next to it, so the two
    637    read as different registers rather than as two boxes. */
    638 .whoami { font-family: var(--font-mono); font-size: 12.5px; line-height: 1.6; max-width: 58ch; }
    639 .whoami__cmd { display: flex; align-items: center; gap: 0.5rem; color: var(--fg-faint); flex-wrap: wrap; }
    640 .whoami__cmd .u { color: var(--pine); }
    641 .whoami__cmd .c { color: var(--fg); }
    642 .whoami__caret {
    643   display: inline-block; width: 0.5em; height: 1.05em;
    644   background: color-mix(in oklab, var(--fg) 80%, transparent);
    645   transform: translateY(0.12em);
    646 }
    647 @media (prefers-reduced-motion: no-preference) {
    648   .whoami__caret { animation: dmn-blink 1.1s steps(1, end) infinite; }
    649 }
    650 .whoami__rows {
    651   margin-top: 0.9rem;
    652   padding-top: 0.9rem;
    653   border-top: 1px solid var(--rule-faint);
    654   display: grid;
    655   grid-template-columns: 4.2rem 1fr;
    656   gap: 0.45rem 1.2rem;
    657   color: var(--fg-dim);
    658 }
    659 .whoami__rows dt { color: var(--fg-faint); }
    660 .whoami__rows dd { display: flex; align-items: center; gap: 0.5rem; }
    661 
    662 /* ---- Marquee band -------------------------------------------------------- */
    663 /* The index grammar run sideways. The band is a ground, not an accent —
    664    see the `--band` note in tokens.css.
    665 
    666    The bottom edge is the one at risk: it meets the page's next section at
    667    too close a value to carry itself, which is why the ink rule below is
    668    load-bearing rather than decorative. Do not drop it. */
    669 .marquee {
    670   position: relative;
    671   overflow: hidden;
    672   background: var(--band);
    673   /* Ink, written out rather than taken from `--fg`: the band pins the
    674      night palette for its contents, which would flip this rule to a light
    675      one. The edge below meets the cream page and the black tabs meet the
    676      section under them, and in both cases it is a *dark* rule that keeps
    677      the seam from bleeding. */
    678   border-bottom: 1px solid #575279;
    679   --marquee-fg: #faf4ed;
    680   /* /78, not /62. Cream on this band clears AA at /74, and these are the
    681      smallest words on the page *and* the only ones in motion, so they get
    682      a little over the line rather than sitting on it. At /62 the index
    683      numbers and sheet counts were legible in a still frame and mush while
    684      the band was moving. */
    685   --marquee-dim: rgba(250, 244, 237, 0.78);
    686 
    687   /* Pine, lifted for this band only.
    688      Every other night accent clears its floor on `--band`: iris 4.75,
    689      foam 5.83, gold 6.06, rose 5.88, love 3.42 — all at or above the 3:1
    690      large-text floor the display titles need. Published night pine
    691      (#3e8fb0) lands at 2.73 and is the one that fails, because it is the
    692      accent nearest the ground in both hue and value.
    693      #6fb3d0 is the same hue walked up until it clears with headroom
    694      (4.28:1) while staying visibly pine rather than sliding into foam,
    695      which sits beside it in the rotation and would collapse two domains
    696      onto one colour. */
    697   --pine: #6fb3d0;
    698 }
    699 .marquee__track {
    700   display: flex;
    701   width: max-content;
    702   will-change: transform;
    703 }
    704 /* Two identical runs translated as one unit to -50%, which lands the
    705    second exactly where the first started — the reset is invisible. */
    706 .marquee__run {
    707   display: flex;
    708   flex: none;
    709   align-items: center;
    710 }
    711 .marquee__item {
    712   display: flex;
    713   align-items: baseline;
    714   gap: 0.7rem;
    715   padding: 0.95rem clamp(1.1rem, 2.4vw, 2rem);
    716   white-space: nowrap;
    717   font-family: var(--font-mono);
    718   /* 11px, not the 10px `--step-micro` every other label on the site uses.
    719      This is the one place the furniture type is in motion, and a word
    720      sliding past at 70px/s needs the extra pixel that a static eyebrow
    721      does not. */
    722   font-size: 11px;
    723   letter-spacing: var(--track-micro);
    724   text-transform: uppercase;
    725   color: var(--marquee-fg);
    726 }
    727 .marquee__n { color: var(--marquee-dim); }
    728 /* The kind *word* stays cream and only its icon takes the hue. Colouring
    729    both put three competing colours in every entry, which is what made the
    730    band read as noise rather than as an index. */
    731 .marquee__kind { color: var(--marquee-fg); display: inline-flex; align-items: center; gap: 0.4rem; }
    732 .marquee__kind svg { width: 11px; height: 11px; color: var(--acc); }
    733 /* The credential in display type, the qualifier trailing in the date slot
    734    — record-row grammar, horizontally. */
    735 .marquee__title {
    736   font-family: var(--font-display);
    737   font-size: 1.06rem;
    738   font-weight: 700;
    739   font-stretch: 84%;
    740   letter-spacing: -0.02em;
    741   text-transform: uppercase;
    742   color: var(--acc);
    743 }
    744 .marquee__qual { color: var(--marquee-dim); }
    745 @media (max-width: 720px) { .marquee__qual { display: none; } }
    746 
    747 /* The band's own tab — `^:` survives in exactly one place. It is the
    748    banner's self-naming mark, and the band names itself once; repeated on
    749    every record it was furniture. The tab counts its records, because every
    750    index on this site counts itself. */
    751 .marquee__tab {
    752   position: absolute;
    753   top: 0; bottom: 0;
    754   z-index: 2;
    755   display: flex;
    756   align-items: center;
    757   gap: 0.5rem;
    758   padding-inline: clamp(0.9rem, 2.2vw, 1.5rem);
    759   /* The tabs are their own ground: Rosé Pine's black, which is the page's
    760      furniture ink rather than another colour competing with the band. The
    761      band pins the night palette (`.plate`), so `--base` is that black in
    762      both modes. */
    763   background: var(--base);
    764   color: #faf4ed;
    765   font-family: var(--font-mono);
    766   font-size: var(--step-micro);
    767   letter-spacing: var(--track-wide);
    768   text-transform: uppercase;
    769 }
    770 .marquee__tab--l { left: 0; }
    771 .marquee__tab--r { right: 0; }
    772 @media (max-width: 640px) { .marquee__tab--r { display: none; } }
    773 .marquee__tab .dot { color: #9ccfd8; }
    774 
    775 /* ---- Section banner ------------------------------------------------------ */
    776 /* Dark plate, live noise field, left scrim. Legibility on the plate is
    777    carried by two layers, not one: the canvas erases its own top and bottom
    778    bands, AND this gradient sits over the noise, under the content. */
    779 .banner {
    780   position: relative;
    781   overflow: hidden;
    782   background: var(--plate);
    783   border-bottom: 1px solid var(--fg);
    784 }
    785 .banner__field { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
    786 .banner__scrim {
    787   position: absolute;
    788   top: 0; bottom: 0; left: 0;
    789   width: 68%;
    790   pointer-events: none;
    791   background: linear-gradient(
    792     90deg,
    793     rgb(var(--plate-scrim) / 0.95) 0%,
    794     rgb(var(--plate-scrim) / 0.86) 52%,
    795     rgb(var(--plate-scrim) / 0) 100%
    796   );
    797 }
    798 .banner__body {
    799   position: relative;
    800   z-index: 2;
    801   display: grid;
    802   gap: 1.8rem 2.6rem;
    803   align-items: end;
    804   padding-block: clamp(1.5rem, 3.2vw, 2.2rem);
    805 }
    806 @media (min-width: 900px) { .banner__body { grid-template-columns: minmax(0, 1fr) 280px; } }
    807 .banner__title {
    808   margin-top: 0.85rem;
    809   font-family: var(--font-display);
    810   font-size: clamp(1.9rem, 4.2vw, 3.2rem);
    811   line-height: 0.92;
    812   font-weight: 700;
    813   font-stretch: 82%;
    814   letter-spacing: -0.035em;
    815   text-transform: uppercase;
    816   color: var(--fg);
    817 }
    818 .banner__blurb { margin-top: 0.85rem; max-width: 58ch; color: var(--fg-dim); font-size: 0.95rem; line-height: 1.55; }
    819 .banner__ledger {
    820   display: grid;
    821   gap: 0.55rem;
    822   font-family: var(--font-mono);
    823   font-size: 11px;
    824   letter-spacing: var(--track-micro);
    825   text-transform: uppercase;
    826 }
    827 .banner__row {
    828   display: flex; align-items: baseline; justify-content: space-between;
    829   gap: 1rem;
    830   padding-bottom: 0.5rem;
    831   border-bottom: 1px solid var(--rule);
    832   color: var(--fg-faint);
    833 }
    834 .banner__row b { font-weight: 400; color: var(--fg); }
    835 .banner__row--acc b { color: var(--acc, var(--accent)); }
    836 
    837 /* ---- Section headers ----------------------------------------------------- */
    838 .section { margin-top: clamp(2.4rem, 5.5vw, 4rem); }
    839 .section:last-child { padding-bottom: clamp(2.5rem, 6vw, 4.5rem); }
    840 .section-head {
    841   display: flex; align-items: flex-end; justify-content: space-between;
    842   gap: 1rem; margin-bottom: 1.2rem;
    843 }
    844 .section-head h2 {
    845   margin-top: 0.6rem;
    846   font-family: var(--font-display);
    847   font-size: clamp(1.5rem, 2.8vw, 2.2rem);
    848   font-weight: 700;
    849   font-stretch: 84%;
    850   letter-spacing: -0.03em;
    851   line-height: 0.94;
    852   text-transform: uppercase;
    853 }
    854 
    855 /* ---- Payloads call-to-action band ---------------------------------------- */
    856 .cta-band {
    857   --cta-ground: var(--band-ledger);
    858   display: block;
    859   background: var(--cta-ground);
    860   border-block: 1px solid var(--fg);
    861   color: #faf4ed;
    862 }
    863 .cta-band--internal { --cta-ground: var(--band-internal); }
    864 .cta-band__body {
    865   display: flex; align-items: center; justify-content: space-between;
    866   gap: 1.6rem; flex-wrap: wrap;
    867   padding-block: clamp(1.5rem, 3.2vw, 2.3rem);
    868 }
    869 .cta-band__eyebrow {
    870   font-family: var(--font-mono); font-size: var(--step-micro);
    871   letter-spacing: var(--track-wide); text-transform: uppercase;
    872   color: rgba(250, 244, 237, 0.72);
    873 }
    874 .cta-band h2 {
    875   margin-top: 0.6rem;
    876   font-family: var(--font-display);
    877   font-size: clamp(1.7rem, 3.4vw, 2.6rem);
    878   font-weight: 700; font-stretch: 82%;
    879   letter-spacing: -0.035em; line-height: 0.94;
    880   text-transform: uppercase;
    881   color: #faf4ed;
    882 }
    883 .cta-band p { margin-top: 0.7rem; max-width: 54ch; color: rgba(250, 244, 237, 0.85); font-size: 0.93rem; line-height: 1.55; }
    884 .cta-band .btn {
    885   background: transparent; color: #faf4ed;
    886   border-color: rgba(250, 244, 237, 0.5);
    887   flex: none;
    888 }
    889 .cta-band:hover .btn { background: #faf4ed; color: var(--cta-ground); border-color: #faf4ed; }
    890 
    891 /* ---- Sheet page ---------------------------------------------------------- */
    892 /* Same measure as the body below it, so the title, the rule under the
    893    eyebrow and the prose all start and end on the same two lines. */
    894 .sheet-head { margin-top: 1.6rem; max-width: 1080px; }
    895 .breadcrumb {
    896   font-family: var(--font-mono); font-size: var(--step-micro);
    897   letter-spacing: var(--track-micro); text-transform: uppercase;
    898   color: var(--fg-faint);
    899   display: flex; gap: 0.45rem; flex-wrap: wrap; align-items: center;
    900 }
    901 .breadcrumb a:hover { color: var(--accent); }
    902 .breadcrumb .sep { opacity: 0.45; }
    903 .sheet-title {
    904   margin-top: 0.9rem;
    905   font-family: var(--font-display);
    906   font-size: clamp(1.9rem, 4vw, 3rem);
    907   font-weight: 700; font-stretch: 84%;
    908   letter-spacing: -0.035em; line-height: 0.94;
    909   text-transform: uppercase;
    910 }
    911 .sheet-meta { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin-top: 1rem; }
    912 .sheet-desc { color: var(--fg-dim); font-size: var(--step-1); margin-top: 1rem; max-width: 70ch; }
    913 
    914 /* The reading measure, not the archive measure. `--maxw` is 1440px because
    915    the index pages are built out of full-bleed bands and ruled tables; a
    916    reading column laid out on it leaves a dead third between the prose and
    917    the contents rail. The sheet pages get their own, narrower one. */
    918 .sheet-layout {
    919   display: grid; grid-template-columns: 1fr;
    920   gap: 2.5rem; margin-top: 2.2rem;
    921   max-width: 1080px;
    922 }
    923 @media (min-width: 1040px) { .sheet-layout { grid-template-columns: minmax(0, 1fr) 220px; } }
    924 .toc { position: sticky; top: 78px; align-self: start; max-height: calc(100vh - 100px); overflow-y: auto; display: none; }
    925 @media (min-width: 1040px) { .toc { display: block; } }
    926 .toc h4 {
    927   font-family: var(--font-mono); font-size: var(--step-micro);
    928   letter-spacing: var(--track-wide); text-transform: uppercase;
    929   color: var(--fg-faint); margin-bottom: 0.7rem;
    930   padding-bottom: 0.5rem; border-bottom: 1px solid var(--rule);
    931 }
    932 .toc a {
    933   display: block; padding: 0.26rem 0 0.26rem 0.7rem;
    934   border-left: 2px solid var(--rule);
    935   color: var(--fg-faint); font-size: 0.82rem; line-height: 1.4;
    936   transition: color 180ms ease, border-color 180ms ease;
    937 }
    938 .toc a:hover, .toc a.active { color: var(--accent); border-left-color: var(--accent); }
    939 .toc a.lvl-3 { padding-left: 1.3rem; font-size: 0.78rem; }
    940 
    941 .prevnext { display: grid; gap: 1px; margin-top: 3rem; background: var(--rule); border: 1px solid var(--rule); }
    942 @media (min-width: 620px) { .prevnext { grid-template-columns: 1fr 1fr; } }
    943 .prevnext a { padding: 1.1rem 1.2rem; background: var(--base); transition: background-color 240ms ease; }
    944 .prevnext a:hover { background: var(--wash-strong); }
    945 .prevnext .dir {
    946   font-family: var(--font-mono); font-size: var(--step-micro);
    947   color: var(--fg-faint); letter-spacing: var(--track-wide); text-transform: uppercase;
    948 }
    949 .prevnext .nx { text-align: right; }
    950 .prevnext strong {
    951   display: block; margin-top: 0.4rem; color: var(--fg);
    952   font-family: var(--font-display); font-weight: 600; letter-spacing: -0.015em;
    953 }
    954 
    955 /* ---- Footer -------------------------------------------------------------- */
    956 /* A dark band closing the cream page — the same object as the marquee and
    957    the banners, in its last position. */
    958 .site-footer {
    959   position: relative;
    960   overflow: hidden;
    961   margin-top: clamp(3rem, 7vw, 5.5rem);
    962   border-top: 1px solid var(--fg);
    963   padding-block: clamp(2rem, 4vw, 3rem);
    964   background: var(--base);
    965   color: var(--fg-dim);
    966 }
    967 .site-footer .wrap {
    968   display: grid; gap: 1.6rem 2.5rem;
    969   align-items: start;
    970 }
    971 @media (min-width: 760px) { .site-footer .wrap { grid-template-columns: minmax(0, 1fr) auto; } }
    972 .site-footer a:hover { color: var(--accent); }
    973 .footer-note { font-size: 0.86rem; max-width: 56ch; line-height: 1.55; margin-top: 0.8rem; }
    974 .footer-mono {
    975   display: block;
    976   font-family: var(--font-mono); font-size: var(--step-micro);
    977   letter-spacing: var(--track-micro); text-transform: uppercase;
    978   line-height: 1.9;
    979 }
    980 @media (min-width: 760px) { .footer-col { text-align: right; } }
    981 
    982 /* ---- PDF embed ----------------------------------------------------------- */
    983 .pdf-frame { width: 100%; height: min(80vh, 900px); border: 1px solid var(--rule); background: var(--code-bg); }
    984 
    985 /* ---- Empty / utility ----------------------------------------------------- */
    986 .muted { color: var(--fg-faint); }
    987 .center { text-align: center; }
    988 .mono { font-family: var(--font-mono); }
    989 .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
    990 
    991 /* Skip link — the first tab stop, hidden until focused, then it drops in over
    992    the masthead so keyboard users can jump the nav straight to the content. */
    993 .skip-link {
    994   position: fixed;
    995   top: 0;
    996   left: 0;
    997   z-index: 200;
    998   transform: translateY(-130%);
    999   padding: 0.6rem 1rem;
   1000   background: var(--fg);
   1001   color: var(--base);
   1002   font-family: var(--font-mono);
   1003   font-size: 11px;
   1004   letter-spacing: var(--track-micro);
   1005   text-transform: uppercase;
   1006   transition: transform 160ms var(--ease-in);
   1007 }
   1008 .skip-link:focus-visible { transform: translateY(0); outline: 2px solid var(--accent); outline-offset: 2px; }
   1009 
   1010 /* ---- Category chips ------------------------------------------------------ */
   1011 .cat-nav { display: flex; flex-wrap: wrap; gap: 1px; background: var(--rule); border: 1px solid var(--rule); }
   1012 .cat-chip {
   1013   display: inline-flex; align-items: center; gap: 0.45rem;
   1014   padding: 0.5rem 0.8rem;
   1015   background: var(--base);
   1016   font-family: var(--font-mono); font-size: var(--step-micro);
   1017   letter-spacing: var(--track-micro); text-transform: uppercase;
   1018   color: var(--fg-dim);
   1019   transition: background-color 220ms ease, color 200ms ease;
   1020 }
   1021 .cat-chip:hover { background: var(--wash-strong); color: var(--fg); }
   1022 .cat-chip__tag { color: var(--acc); }
   1023 .cat-chip[aria-current='page'] { background: var(--acc); color: var(--base); }
   1024 .cat-chip[aria-current='page'] .cat-chip__tag { color: var(--base); }
   1025 
   1026 /* ---- PayloadsAllTheThings attribution ------------------------------------ */
   1027 .patt-credit {
   1028   display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap;
   1029   margin: 0.2rem 0 1.8rem;
   1030   padding: 0.7rem 0.9rem;
   1031   border: 1px solid var(--rule);
   1032   border-left: 2px solid var(--iris);
   1033   background: var(--wash);
   1034   font-size: 0.86rem;
   1035 }
   1036 .patt-badge {
   1037   font-family: var(--font-mono); font-size: var(--step-micro);
   1038   letter-spacing: var(--track-micro); text-transform: uppercase;
   1039   color: var(--iris);
   1040   border: 1px solid color-mix(in oklab, var(--iris) 50%, transparent);
   1041   padding: 0.14rem 0.4rem;
   1042   flex: none;
   1043 }
   1044 .patt-credit__text { color: var(--fg-dim); line-height: 1.5; }
   1045 .patt-credit__text a { color: var(--iris); border-bottom: 1px solid color-mix(in oklab, var(--iris) 40%, transparent); }
   1046 .patt-credit__text a:hover { border-color: var(--iris); }
   1047 .patt-lic { display: inline-block; margin-left: 0.5rem; color: var(--fg-faint); font-family: var(--font-mono); font-size: 0.78rem; }
   1048 .patt-lic code { background: none; color: var(--fg-faint); }
   1049 .patt-src {
   1050   margin-left: auto;
   1051   display: inline-flex; align-items: center; gap: 0.35rem;
   1052   font-family: var(--font-mono); font-size: var(--step-micro);
   1053   letter-spacing: var(--track-micro); text-transform: uppercase;
   1054   color: var(--fg-dim);
   1055   padding: 0.32rem 0.6rem;
   1056   border: 1px solid var(--rule);
   1057   transition: color 180ms ease, border-color 180ms ease;
   1058 }
   1059 .patt-src:hover { color: var(--iris); border-color: color-mix(in oklab, var(--iris) 50%, transparent); }
   1060 
   1061 /* ---- Theme toggle: circular-reveal wipe ---------------------------------- */
   1062 /* Scoped to .theme-vt so it outranks the page iris below, which runs on
   1063    every navigation. The toggle sets --vt-x/--vt-y (origin) and --vt-r
   1064    (end radius) in JS. */
   1065 :root.theme-vt::view-transition-old(root),
   1066 :root.theme-vt::view-transition-new(root) {
   1067   animation: none;               /* cancel the default fade */
   1068   mix-blend-mode: normal;
   1069 }
   1070 :root.theme-vt::view-transition-old(root) { z-index: 0; }   /* previous theme underneath */
   1071 :root.theme-vt::view-transition-new(root) { z-index: 1; }   /* new theme wipes in on top */
   1072 
   1073 @media (prefers-reduced-motion: no-preference) {
   1074   :root.theme-vt::view-transition-new(root) {
   1075     animation: theme-reveal 0.5s cubic-bezier(0.4, 0, 0.2, 1) both;
   1076   }
   1077   @keyframes theme-reveal {
   1078     from { clip-path: circle(0 at var(--vt-x, 50%) var(--vt-y, 0)); }
   1079     to   { clip-path: circle(var(--vt-r, 150vmax) at var(--vt-x, 50%) var(--vt-y, 0)); }
   1080   }
   1081 }
   1082 
   1083 /* ---- Page iris: circular reveal on navigation -------------------------- */
   1084 /* Every internal navigation (Astro's ClientRouter) opens the next page as a
   1085    circle from the link that was pressed, the theme wipe's gesture at page
   1086    pace. The inline script in Base.astro sets --vt-x/--vt-y and the exact
   1087    farthest-corner radius --vt-r. The theme wipe's rules above are scoped to
   1088    .theme-vt, which outranks these, so each keeps its own timing. */
   1089 ::view-transition-old(root),
   1090 ::view-transition-new(root) {
   1091   animation: none;               /* replace Astro's default crossfade */
   1092   mix-blend-mode: normal;
   1093 }
   1094 
   1095 @media (prefers-reduced-motion: no-preference) {
   1096   ::view-transition-new(root) {
   1097     animation: page-iris 960ms cubic-bezier(0.4, 0, 0.2, 1) both;
   1098   }
   1099   @keyframes page-iris {
   1100     from { clip-path: circle(0 at var(--vt-x, 50%) var(--vt-y, 50%)); }
   1101     to   { clip-path: circle(var(--vt-r, 150vmax) at var(--vt-x, 50%) var(--vt-y, 50%)); }
   1102   }
   1103 }
   1104 
   1105 /* The transition layer sits over the whole page and, left alone, swallows
   1106    every click until it finishes: at 960ms, a page that ignores the reader
   1107    for a second. The live DOM underneath is already the new page, so clicks
   1108    go straight through to it. */
   1109 ::view-transition {
   1110   pointer-events: none;
   1111 }
   1112 
   1113 /* ============================================================================
   1114    Cyberdeck integration — additive hooks that wire the shared chrome objects
   1115    (`.corners`, `.scanlines`, `.plate-dawn`) into the page furniture. Kept in
   1116    one block at the foot of the file so the restyle stays reviewable.
   1117    ========================================================================== */
   1118 
   1119 /* ---- Hover-reveal corner brackets ---------------------------------------- */
   1120 /* Domain tiles and index records carry `.corners`, but a grid of always-on
   1121    brackets reads as clutter — so they stay clipped until the row is under the
   1122    pointer, arriving with the ink wipe. */
   1123 .plate-tile.corners::before, .plate-tile.corners::after,
   1124 .record.corners::before, .record.corners::after {
   1125   opacity: 0;
   1126   transition: opacity 180ms ease;
   1127 }
   1128 .plate-tile.corners:hover::before, .plate-tile.corners:hover::after,
   1129 .record.corners:hover::before, .record.corners:hover::after {
   1130   opacity: 1;
   1131 }
   1132 
   1133 /* ---- Footer CRT film ----------------------------------------------------- */
   1134 /* The footer is a `.plate`; the scanline sheet rides at z-index 1 over it, so
   1135    the content column is lifted above the film and the brackets frame it. */
   1136 .site-footer .wrap { position: relative; z-index: 2; }
   1137 .site-footer > .scanlines { opacity: 0.6; }
   1138 
   1139 /* ---- 'On this page' rail as a dawn plate --------------------------------- */
   1140 /* `.corners` would force `position: relative` and un-stick the rail, so sticky
   1141    is re-asserted at higher specificity. The panel treatment and its brackets
   1142    only appear on the night page — in light mode `.plate-dawn` is a no-op and
   1143    the rail keeps its bare, borderless look. */
   1144 .toc.corners { position: sticky; }
   1145 .toc.corners::before, .toc.corners::after { display: none; }
   1146 :root[data-theme='dark'] .toc.plate-dawn {
   1147   background: var(--surface);
   1148   border: 1px solid var(--rule);
   1149   padding: 1rem 1.05rem 1.1rem;
   1150 }
   1151 :root[data-theme='dark'] .toc.plate-dawn.corners::before,
   1152 :root[data-theme='dark'] .toc.plate-dawn.corners::after { display: block; }