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; }