daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

global.css (47348B)


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