commit 6813e11af34363dbd14065d5815797b09d1d3c80
parent 5b8ba9d7477d1bcc155873493cff96d361b277d0
Author: DAEMON <zer0sec.xp@icloud.com>
Date: Thu, 3 Sep 2026 01:11:07 +0100
Redesign the vault homepage to match daemon-sec.xyz
Re-skin the cheatsheet site to the main site's design language (the dmn
system): DM Mono type, the fixed dawn/night/signal Rosé Pine palette, and a
1560px measure. The home route is recomposed from the main site's own
components, all bound to the real vault data — nothing is removed.
- Hero loop: a 1:1 vanilla port of the site's hero-loop (one 21.4s rAF clock,
1920x1080 canvas scaled at >=1280px, fluid flow below, glyph decode,
pointer/focus takeover, theme-aware plate inversion, reduced-motion holds
T=12). Rows are real links to the category routes.
- Manifest band: dual-lane counter-scrolling marquee with an inverse cap.
- Operator band: dark plate, terminal readout, search rail that opens Pagefind.
- Index: SectionHeader + filter pills + paper-wash record rows (titles stay
legible on hover) + show-more, bound to recent sheets across shelves.
- Shelves grid: the full taxonomy ("pick your vector").
- Mirrors band: the swisskyrepo PayloadsAllTheThings + InternalAllTheThings
mirrors, credited with live tree/file counts.
- Masthead + footer re-plated in the main site's grammar; Callout and
SectionHeader ported; RecordRow reused site-wide.
- Add self-hosted DM Mono (300/400/500 + italic); point the site mono at it.
Design and CSS only: content, frontmatter, taxonomy, search indexing and
deploy are untouched. Verified light/dark and 1560/1024/390 widths.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5eBM16vjWscUMJJTanpwk
Diffstat:
18 files changed, 1369 insertions(+), 331 deletions(-)
diff --git a/src/components/Callout.astro b/src/components/Callout.astro
@@ -0,0 +1,26 @@
+---
+import Icon from './Icon.astro';
+
+interface Props {
+ tone?: 'note' | 'finding' | 'warning' | 'danger';
+ /** Override the tone's default kind label. */
+ label?: string;
+}
+const { tone = 'note', label } = Astro.props;
+
+/**
+ * Flat plate, a 2px accent rule, mono eyebrow — the archive's callout, ported
+ * from the main site's Callout. Four tones, each with its own kind mark.
+ */
+const TONES = {
+ note: { color: 'var(--pine)', icon: 'info', label: 'Note' },
+ finding: { color: 'var(--foam)', icon: 'check', label: 'Finding' },
+ warning: { color: 'var(--gold)', icon: 'warn', label: 'Warning' },
+ danger: { color: 'var(--love)', icon: 'close', label: 'Danger' },
+} as const;
+const t = TONES[tone];
+---
+<div class="dcallout" style={`--tone: ${t.color};`}>
+ <div class="dcallout__label"><Icon name={t.icon} /> {label ?? t.label}</div>
+ <div class="dcallout__body"><slot /></div>
+</div>
diff --git a/src/components/Footer.astro b/src/components/Footer.astro
@@ -1,32 +1,57 @@
---
import { url } from '../lib/url';
+
const GITHUB = 'https://github.com/DAEMON-404/daemon-sec-cheatsheet';
const SITE = 'https://daemon-404.github.io';
const year = 2026;
+
+/**
+ * The footer, re-plated on the signal field per the main site's Footer: a
+ * dark closing band with the wordmark in its reversed setting, the motto,
+ * the numbered nav (each index keeps its section hue), and the status block.
+ * `.dfooter` pins the signal palette, so it stays dark in both modes.
+ */
+const NAV = [
+ { n: '01', label: 'Vault', href: url(''), acc: 'love' },
+ { n: '02', label: 'Payloads', href: url('payloads'), acc: 'iris' },
+ { n: '03', label: 'Internal', href: url('internal'), acc: 'pine' },
+ { n: '04', label: 'Tags', href: url('tags'), acc: 'foam' },
+ { n: '05', label: 'Credits', href: url('credits'), acc: 'gold' },
+];
---
-<!-- A dark band closing the cream page: the same object as the marquee and
- the section banners, in its last position. `.plate` pins the night
- palette for the whole subtree, so this stays dark in both modes. -->
-<footer class="site-footer plate">
- <span class="grain grain--local" aria-hidden="true"></span>
+<footer class="dfooter site-footer">
+ <span class="dfooter__scrim" aria-hidden="true"></span>
<span class="scanlines" aria-hidden="true"></span>
- <div class="wrap corners">
- <div>
- <a class="brand" href={url('')}>DÆMON<span class="slash">//</span>SEC</a>
- <p class="footer-note">
- A curated vault of IT & security cheatsheets. For authorized testing,
- CTFs, and education only. Know your scope; get permission first.
- </p>
- </div>
- <div class="footer-col">
- <a class="footer-mono" href={SITE} rel="noopener">daemon-404.github.io ↗</a>
- <a class="footer-mono" href={GITHUB} target="_blank" rel="noopener">github / daemon-sec-cheatsheet ↗</a>
- <span class="footer-mono muted">
- Payloads mirrored from <a href="https://github.com/swisskyrepo/PayloadsAllTheThings" target="_blank" rel="noopener">PayloadsAllTheThings</a>
- (MIT) · <a href={url('credits')}>credits</a>
- </span>
- <span class="footer-mono muted">© {year} DÆMON//SEC · MIT</span>
- <span class="footer-mono muted">Rosé Pine · Astro · Pagefind</span>
+ <div class="archive dfooter__inner">
+ <div class="dfooter__grid">
+ <div>
+ <a href={url('')} class="dfooter__wordmark">DΛΣMӨП<span class="hy">-</span>SΣC</a>
+ <p class="dfooter__motto">
+ Terse, tested, kept current. A curated vault of IT & security cheatsheets — for
+ authorised testing, CTFs and education only. Know your scope; get permission first.
+ </p>
+ </div>
+
+ <nav class="dfooter__nav" aria-label="Footer">
+ {NAV.map((item) => (
+ <a href={item.href} style={`--acc: var(--color-night-${item.acc});`}>
+ <span class="n">{item.n}</span>{item.label}
+ </a>
+ ))}
+ <a href={SITE} rel="noopener" style="--acc: var(--color-night-foam);"><span class="n">06</span>Main site ↗</a>
+ <a href={GITHUB} target="_blank" rel="noopener" style="--acc: var(--color-night-gold);"><span class="n">07</span>GitHub ↗</a>
+ </nav>
+
+ <div class="dfooter__meta">
+ <div class="live">
+ <span class="dmn-dot" style="--dot: var(--color-night-love);" aria-hidden="true"><i class="dmn-anim-pulse"></i><u class="dmn-anim-ring"></u></span>
+ All systems operational
+ </div>
+ <div class="row">Payloads & Internal mirrored from
+ <a href="https://github.com/swisskyrepo/PayloadsAllTheThings" target="_blank" rel="noopener">swisskyrepo</a> · MIT</div>
+ <div class="row">Rosé Pine · Astro · Pagefind</div>
+ <div class="row">© {year} DÆMON//SEC · <a href={url('credits')}>credits</a> · MIT</div>
+ </div>
</div>
</div>
</footer>
diff --git a/src/components/Header.astro b/src/components/Header.astro
@@ -6,17 +6,14 @@ const GITHUB = 'https://github.com/DAEMON-404/daemon-sec-cheatsheet';
const SITE = 'https://daemon-404.github.io';
/**
- * The masthead, in the main site's grammar.
+ * The masthead, in the main site's grammar (daemon-sec.xyz `Masthead`): a
+ * wordmark chip on the left, numbered mono nav in the centre, and the action
+ * cluster on the right. Each nav entry owns a hue and its index wears it, so
+ * the bar reads as the colour legend the rest of the site keys back to.
*
- * Each entry owns a hue and its index wears it, so the bar reads as the
- * colour legend the rest of the site keys back to rather than as five grey
- * words. The index keeps its space at rest rather than appearing on hover
- * — revealing it would reflow the whole bar every time the pointer crossed
- * an item.
- *
- * The accents are written out as full custom-property names rather than
- * built up from the slug, because the value is read straight into `--acc`
- * and a half-built token silently resolves to nothing.
+ * The nav entries and the search / theme / mobile wiring are the cheat-sheet
+ * site's own — `[data-search-open]`, `[data-theme-toggle]` and
+ * `[data-mobile-nav-toggle]` are bound in scripts/app.ts and SearchModal.
*/
const NAV = [
{ n: '01', label: 'Vault', href: url(''), acc: 'love', exact: true },
@@ -33,78 +30,73 @@ const isActive = (href: string, exact?: boolean) => {
const target = href.replace(/\/+$/, '') || '/';
return exact ? here === target : here === target || here.startsWith(target + '/');
};
+
+/* The Orthodox cross, drawn rather than typed — none of the site's faces
+ carry U+2626, so it would fall back per-character. Ported from the main
+ site's OrthodoxCross. */
+const CROSS = `<line x1="9" y1="1.3" x2="9" y2="22.7"/><line x1="6.1" y1="6.2" x2="11.9" y2="6.2"/><line x1="1.3" y1="10.7" x2="16.7" y2="10.7"/><line x1="3.7" y1="15.9" x2="14.3" y2="18.7"/>`;
---
-<!-- The masthead as a tmux-style segbar: the brand is the accent-filled
- left segment, the numbered nav entries and the action cluster are
- hairline-divided segments running the full height of the bar. The
- `.segbar` / `.seg` / `.seg--accent` objects are defined once in
- chrome.css; the scoped block below carries the header-specific half. -->
-<header class="site-header segbar">
- <div class="wrap">
- <a class="brand seg seg--accent glitch" href={url('')} aria-label="DÆMON//SEC home" data-text="DÆMON//SEC">
- DÆMON<span class="slash">//</span>SEC<span class="blink">_</span>
+<header class="mast site-header">
+ <div class="archive mast__row">
+ <a class="mast__brand" href={url('')} aria-label="DÆMON//SEC — home">
+ <span class="mast__chip">DΛΣMӨП<svg viewBox="0 0 18 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="butt" aria-hidden="true" set:html={CROSS} /></span>
</a>
- <nav class="mast-nav" aria-label="Primary">
+ <nav class="mast__nav" aria-label="Sections">
{NAV.map((item) => (
<a
- class="seg"
+ class="mast__navitem"
href={item.href}
style={`--acc: var(--${item.acc});`}
aria-current={isActive(item.href, item.exact) ? 'page' : undefined}
>
<span class="n">{item.n}</span><span class="lbl">{item.label}</span>
+ <span class="rule" aria-hidden="true"></span>
</a>
))}
</nav>
- <div class="mast-actions">
- <button class="search-trigger seg" data-search-open type="button" aria-label="Search cheatsheets">
- <Icon name="search" />
- <span class="st-label">Search</span>
- <kbd>/</kbd>
+ <div class="mast__actions">
+ <span class="mast__status" aria-hidden="true">
+ <span class="dmn-dot" style="--dot: var(--love);"><i class="dmn-anim-pulse"></i><u class="dmn-anim-ring"></u></span>
+ <span>Offline · Pagefind</span>
+ </span>
+ <button class="mast__icon" data-search-open type="button" aria-label="Search the vault">
+ <Icon name="search" /><kbd>/</kbd>
</button>
- <button class="icon-btn seg" data-theme-toggle type="button" aria-label="Toggle theme">
+ <button class="mast__icon mast__mode" data-theme-toggle type="button" aria-label="Dark mode">
<span class="icon-sun"><Icon name="sun" /></span>
<span class="icon-moon"><Icon name="moon" /></span>
</button>
- <a class="icon-btn seg" href={SITE} rel="noopener" aria-label="DÆMON main site">
- <Icon name="external" />
- </a>
- <a class="icon-btn seg" href={GITHUB} target="_blank" rel="noopener" aria-label="GitHub repository">
- <Icon name="github" />
- </a>
+ <a class="mast__icon" href={SITE} rel="noopener" aria-label="DÆMON main site"><Icon name="external" /></a>
+ <a class="mast__icon" href={GITHUB} target="_blank" rel="noopener" aria-label="GitHub repository"><Icon name="github" /></a>
<button
- class="icon-btn seg mobile-nav-trigger"
+ class="mast__burger"
data-mobile-nav-toggle
type="button"
aria-label="Open navigation"
aria-controls="mobile-primary-nav"
aria-expanded="false"
>
- <span class="mobile-nav-glyph" aria-hidden="true"><i></i><i></i><i></i></span>
+ <i></i><i></i><i></i>
</button>
</div>
</div>
- <nav id="mobile-primary-nav" class="mobile-nav" data-mobile-nav aria-label="Primary mobile" hidden>
+ <nav id="mobile-primary-nav" class="mast__mobile" data-mobile-nav aria-label="Primary mobile" hidden>
{NAV.map((item) => (
<a
href={item.href}
style={`--acc: var(--${item.acc});`}
aria-current={isActive(item.href, item.exact) ? 'page' : undefined}
>
- <span class="n">{item.n}</span><span class="lbl">{item.label}</span>
+ <span class="n">{item.n}</span>{item.label}
+ <span class="arrow" aria-hidden="true">→</span>
</a>
))}
+ <a href={SITE} rel="noopener">
+ <span class="n" style="--acc: var(--foam);">↗</span>Main site
+ <span class="arrow" aria-hidden="true" style="--acc: var(--foam);">↗</span>
+ </a>
</nav>
</header>
-
-<style>
- /* Show the correct theme glyph without a flash */
- .icon-sun { display: inline-flex; }
- .icon-moon { display: none; }
- :global(:root[data-theme='dark']) .icon-sun { display: none; }
- :global(:root[data-theme='dark']) .icon-moon { display: inline-flex; }
- .search-trigger svg { width: 15px; height: 15px; }
-</style>
diff --git a/src/components/HeroLoop.astro b/src/components/HeroLoop.astro
@@ -0,0 +1,384 @@
+---
+interface Shelf { n: string; label: string; path: string; accent: string; desc: string }
+interface Props {
+ shelves: Shelf[];
+ name?: string;
+ tagline?: string;
+}
+const { shelves, name = 'DΛΣMӨП-RΣF', tagline = 'DÆMON-REF · Cheatsheet vault' } = Astro.props;
+const data = JSON.stringify({ shelves, name, tagline });
+---
+<section class="hero-loop bleed" data-hero-loop data-hero={data} aria-label="Shelves">
+ <noscript>
+ <div style="padding:56px 44px;max-width:1560px;margin:0 auto;">
+ <p style="font-family:var(--font-wordmark);font-weight:800;font-size:clamp(40px,9vw,120px);font-stretch:88%;letter-spacing:-0.02em;line-height:1;color:var(--text);text-transform:uppercase;">{name}</p>
+ <p style="margin-top:14px;font-family:var(--font-plex);font-size:13px;letter-spacing:0.1em;text-transform:uppercase;color:var(--muted);">Six shelves. One vault.</p>
+ <ul style="margin-top:28px;list-style:none;padding:0;border-top:1px solid var(--line);">
+ {shelves.map((s) => (
+ <li><a href={s.path} style={`display:flex;justify-content:space-between;gap:16px;padding:16px 0;border-bottom:1px solid var(--line);font-family:var(--font-archivo);font-weight:600;font-size:24px;text-transform:uppercase;color:var(--${s.accent});`}>{s.label}<span style="font-family:var(--font-plex);font-size:13px;color:var(--subtle);">{s.path}</span></a></li>
+ ))}
+ </ul>
+ </div>
+ </noscript>
+</section>
+
+<script>
+ // Hero loop — a 1:1 vanilla port of the handoff's hero-loop.js (itself the
+ // main site's hero-loop.tsx): one 21.4s rAF clock, an authored 1920×1080
+ // canvas scaled to fit at >=1280px, a fluid flow shape below, pointer/focus
+ // takeover with an eased `mix`, glyph decode until +0.55s, and the plate as
+ // the inverse of the page (theme-aware P/Q). Pauses when hidden or scrolled
+ // out; prefers-reduced-motion holds T=12.0 with no scramble.
+
+ const W = 1920, H = 1080, PLATE_W = 784, PLATE_H = 780, MIN_HEIGHT = 560, MARQUEE = 92;
+ const CANVAS_MIN = 1280, FLOW_PLATE_MIN = 860;
+ const BOOT = 0, CLOSE = 18.8, AUTHORED = 21.4, HELD_FRAME = 12.0;
+ const STARTS = [3.2, 5.8, 8.4, 11.0, 13.6, 16.2];
+ const ENDS = STARTS.map((s, i) => (i < 5 ? STARTS[i + 1] : CLOSE));
+ const GLYPH_POOL = 'ΛΣӨПΔΞΨΩ01#/\\_';
+
+ const DAWN = { base: 'var(--color-dawn-base)', surface: 'var(--color-dawn-surface)', ink: 'var(--color-dawn-ink)', subtle: 'var(--color-dawn-subtle)', muted: 'var(--color-dawn-muted)', line: 'color-mix(in srgb, var(--color-dawn-ink) 16%, transparent)', pine: 'var(--color-dawn-pine)', foam: 'var(--color-dawn-foam)', gold: 'var(--color-dawn-gold)', love: 'var(--color-dawn-love)', iris: 'var(--color-dawn-iris)', rose: 'var(--color-dawn-rose)' };
+ const NIGHT = { base: 'var(--color-night-base)', surface: 'var(--color-night-surface)', ink: 'var(--color-night-text)', subtle: 'var(--color-night-subtle)', muted: 'var(--color-night-muted)', line: 'color-mix(in srgb, var(--color-night-text) 16%, transparent)', pine: 'var(--color-night-pine)', foam: 'var(--color-night-foam)', gold: 'var(--color-night-gold)', love: 'var(--color-night-love)', iris: 'var(--color-night-iris)', rose: 'var(--color-night-rose)' };
+ const MONO = 'var(--font-plex)', ARCHIVO = 'var(--font-archivo)', WORDMARK_FACE = 'var(--font-wordmark)';
+
+ const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));
+ const easeOutCubic = (p: number) => 1 - Math.pow(1 - p, 3);
+ const easeInOutCubic = (p: number) => (p < 0.5 ? 4 * p * p * p : 1 - Math.pow(-2 * p + 2, 3) / 2);
+ const enter = (T: number, start: number, dur = 0.6) => { const o = easeOutCubic(clamp((T - start) / dur, 0, 1)); return { o, y: (1 - o) * 24 }; };
+ const draw = (T: number, start: number, dur = 0.8) => easeInOutCubic(clamp((T - start) / dur, 0, 1));
+ const hold = (T: number, a: number, b: number, ramp = 0.35) => Math.min(easeOutCubic(clamp((T - a) / ramp, 0, 1)), easeOutCubic(clamp((b - T) / ramp, 0, 1)));
+ const hash = (i: number, k: number) => { const s = Math.sin(i * 12.9898 + k * 78.233) * 43758.5453; return s - Math.floor(s); };
+ const activeAt = (T: number) => { for (let i = 0; i < 6; i++) if (T >= STARTS[i] && T < ENDS[i]) return i; return -1; };
+ const timecode = (T: number) => `T+${String(Math.floor(T / 60)).padStart(2, '0')}:${String(Math.floor(T % 60)).padStart(2, '0')}.${String(Math.floor((T % 1) * 24)).padStart(2, '0')}`;
+
+ type Shelf = { n: string; label: string; path: string; accent: string; desc: string };
+
+ function shelfState(T: number, mix: number, held: number | null, shelves: Shelf[]) {
+ const act: number[] = [], plateAct: number[] = [], plateY: number[] = [], plateLine: number[] = [];
+ for (let i = 0; i < shelves.length; i++) {
+ const hv = i === held ? 1 : 0;
+ act.push(hold(T, STARTS[i], ENDS[i]) * (1 - mix) + hv * mix);
+ plateAct.push(hold(T, STARTS[i], ENDS[i], 0.4) * (1 - mix) + hv * mix);
+ plateY.push((1 - enter(T, STARTS[i], 0.5).o) * 40 * (1 - mix));
+ const line = draw(T, STARTS[i] + 0.15, 0.7) * (1 - draw(T, ENDS[i] - 0.4, 0.4));
+ plateLine.push(line * (1 - mix) + hv * mix);
+ }
+ const fromClock = activeAt(T);
+ const active = mix > 0.5 ? (held ?? fromClock) : fromClock;
+ const standing = Math.max(hold(T, BOOT + 1.6, STARTS[0]), hold(T, CLOSE + 0.35, AUTHORED + 1)) * (1 - mix);
+ return { act, plateAct, plateY, plateLine, standing, active, accent: active >= 0 ? shelves[active].accent : 'love' };
+ }
+
+ // Tiny DOM helper. Numeric style values need no unit here — every length is
+ // passed as an explicit px string.
+ function el(tag: string, style?: Record<string, string>, kids?: any): HTMLElement {
+ const n = document.createElement(tag);
+ if (style) Object.assign(n.style, style);
+ if (kids != null) (Array.isArray(kids) ? kids : [kids]).forEach((k) => { if (k == null || k === false) return; n.append(k.nodeType ? k : document.createTextNode(String(k))); });
+ return n;
+ }
+
+ function build(hostW: number, mode: 'canvas' | 'flow', withPlate: boolean, shelves: Shelf[], name: string, tagline: string, P: any, Q: any, onHold: (i: number) => void, onRelease: () => void) {
+ // Row metrics differ by shape.
+ const M = mode === 'canvas'
+ ? { height: '76px', columns: '64px 1fr auto', padLeft: '14px', chipSize: '14px', chipPad: '3px 7px', labelSize: '30px', pathSize: '15px', pathPad: '16px', shift: 10, arrow: true }
+ : { height: '56px', columns: '44px 1fr auto', padLeft: '8px', chipSize: '11px', chipPad: '3px 6px', labelSize: 'clamp(17px, 2.2vw, 26px)', pathSize: 'clamp(10px, 1.1vw, 13px)', pathPad: '0px', shift: 8, arrow: false };
+
+ // ── Shelf row ──
+ const rows = shelves.map((s) => {
+ const acc = P[s.accent];
+ const wash = el('span', { position: 'absolute', inset: '0', background: P.surface, opacity: '0', borderLeft: `3px solid ${acc}`, transformOrigin: 'left', transform: 'scaleX(0)' });
+ const chip = el('span', { fontFamily: MONO, fontSize: M.chipSize, letterSpacing: '0.06em', padding: M.chipPad, color: P.muted, background: 'transparent', boxShadow: `inset 0 0 0 1px ${P.line}` }, s.n);
+ const chipWrap = el('span', { position: 'relative', display: 'flex', alignItems: 'center', paddingLeft: M.padLeft }, chip);
+ const label = el('span', { position: 'relative', fontFamily: ARCHIVO, fontWeight: '600', fontSize: M.labelSize, letterSpacing: '0.04em', textTransform: 'uppercase', color: P.ink, opacity: '1', transform: 'translateX(0)' }, s.label);
+ const arrow = M.arrow ? el('span', { display: 'inline-block', marginLeft: '12px', opacity: '0', transform: 'translateX(-8px)' }, '→') : null;
+ const path = el('span', { position: 'relative', fontFamily: MONO, fontSize: M.pathSize, letterSpacing: '0.06em', color: P.subtle, paddingRight: M.pathPad, opacity: '0.6' }, [s.path, arrow]);
+ const a = el('a', { position: 'relative', display: 'grid', gridTemplateColumns: M.columns, alignItems: 'center', height: M.height, borderTop: `1px solid ${P.line}`, opacity: '1', transform: 'translateY(0)', textDecoration: 'none', color: P.ink }, [wash, chipWrap, label, path]) as HTMLAnchorElement;
+ a.href = s.path;
+ const idx = shelves.indexOf(s);
+ a.addEventListener('mouseenter', () => onHold(idx));
+ a.addEventListener('mouseleave', onRelease);
+ a.addEventListener('focus', () => onHold(idx));
+ a.addEventListener('blur', onRelease);
+ return { a, wash, chip, label, path, arrow };
+ });
+ const nav = el('nav', mode === 'canvas'
+ ? { position: 'absolute', left: '72px', top: '424px', width: '916px' }
+ : { marginTop: '32px' }, [...rows.map((r) => r.a), el('div', { borderTop: `1px solid ${P.line}` })]);
+ nav.setAttribute('aria-label', 'Shelves');
+
+ // ── Plate ──
+ const dots: HTMLElement[] = [];
+ const cols = 24, rowsN = 24, cw = PLATE_W / cols, chh = PLATE_H / rowsN;
+ const dotField = el('div', { position: 'absolute', left: '0', top: '0', width: PLATE_W + 'px', height: PLATE_H + 'px' });
+ for (let r = 0; r < rowsN; r++) for (let c = 0; c < cols; c++) {
+ const d = el('div', { position: 'absolute', left: (c * cw + cw / 2 - 2) + 'px', top: (r * chh + chh / 2 - 2) + 'px', width: '4px', height: '4px', borderRadius: '2px', background: Q.ink, opacity: '0.08' });
+ dots.push(d); dotField.append(d);
+ }
+ const plateMeta = { fontFamily: MONO, fontSize: '14px', letterSpacing: '0.10em', textTransform: 'uppercase', color: Q.subtle };
+ const chromeRight = el('span', { color: P.love }, '— / 06');
+ const chrome = el('div', { position: 'absolute', left: '40px', right: '40px', top: '36px', display: 'flex', justifyContent: 'space-between', whiteSpace: 'nowrap', ...plateMeta }, [el('span', undefined, 'Plate · index'), chromeRight]);
+ const standing = el('div', { position: 'absolute', left: '40px', right: '40px', bottom: '48px', opacity: '0', transform: 'translateY(16px)' }, [
+ el('div', { ...plateMeta, marginBottom: '14px' }, 'Standing by'),
+ el('div', { fontFamily: ARCHIVO, fontWeight: '700', fontSize: '64px', lineHeight: '0.98', letterSpacing: '-0.02em', color: Q.ink }, ['Security lives', el('br'), 'in the details.']),
+ el('div', { marginTop: '22px', fontFamily: MONO, fontSize: '15px', letterSpacing: '0.04em', color: Q.muted }, '06 shelves · select an entry'),
+ ]);
+ const sections = shelves.map((s) => {
+ const acc = Q[s.accent];
+ const rule = el('div', { height: '2px', background: acc, marginTop: '26px', transformOrigin: 'left', transform: 'scaleX(0)' });
+ const block = el('div', { position: 'absolute', left: '40px', right: '40px', bottom: '48px', opacity: '0', transform: 'translateY(40px)' }, [
+ el('div', { display: 'flex', alignItems: 'center', gap: '14px', marginBottom: '20px' }, [
+ el('span', { fontFamily: MONO, fontSize: '14px', letterSpacing: '0.08em', padding: '4px 8px', background: acc, color: Q.base }, s.n),
+ el('span', { fontFamily: MONO, fontSize: '15px', letterSpacing: '0.08em', color: acc }, s.path),
+ ]),
+ el('div', { fontFamily: ARCHIVO, fontWeight: '800', fontSize: (s.label.length > 5 ? 118 : 148) + 'px', lineHeight: '0.92', letterSpacing: '-0.03em', textTransform: 'uppercase', color: Q.ink }, s.label),
+ rule,
+ el('div', { marginTop: '22px', fontFamily: ARCHIVO, fontSize: '27px', lineHeight: '1.3', color: Q.subtle, maxWidth: '620px' }, s.desc),
+ el('div', { marginTop: '28px', fontFamily: MONO, fontSize: '15px', letterSpacing: '0.10em', textTransform: 'uppercase', color: Q.muted, display: 'flex', gap: '12px', alignItems: 'center' }, [el('span', { color: acc }, '▸'), ' Enter shelf']),
+ ]);
+ return { block, rule };
+ });
+ const plateSurface = el('div', { position: 'absolute', left: '0', top: '0', width: PLATE_W + 'px', height: PLATE_H + 'px', background: Q.base, color: Q.ink, overflow: 'hidden', clipPath: 'inset(0 100% 0 0)' }, [
+ el('div', { position: 'absolute', inset: '0', background: `radial-gradient(120% 80% at 90% 100%, ${Q.surface} 0%, ${Q.base} 60%)` }),
+ dotField, chrome,
+ el('div', { position: 'absolute', left: '40px', right: '40px', top: '66px', height: '1px', background: Q.line }),
+ standing, ...sections.map((s) => s.block),
+ ]);
+ plateSurface.setAttribute('aria-hidden', 'true');
+
+ // Plate host — absolute box on canvas, aspect-ratio fitted box on flow.
+ let plateHost: HTMLElement, plateScaleK = 1;
+ if (mode === 'canvas') {
+ plateHost = el('div', { position: 'absolute', left: '1064px', top: '150px', width: PLATE_W + 'px', height: PLATE_H + 'px' }, plateSurface);
+ } else {
+ const inner = el('div', { position: 'absolute', left: '0', top: '0', width: PLATE_W + 'px', height: PLATE_H + 'px', transformOrigin: '0 0' }, plateSurface);
+ plateHost = el('div', { position: 'relative', width: '100%', aspectRatio: `${PLATE_W} / ${PLATE_H}`, overflow: 'hidden' }, inner);
+ plateHost.setAttribute('aria-hidden', 'true');
+ (plateHost as any)._inner = inner;
+ }
+
+ // ── Wordmark ──
+ const wm = name.split('').map((g) => el('span', { display: 'inline-block', opacity: '0', transform: 'translateY(24px)', color: g === '-' ? P.love : P.muted, fontFamily: MONO, fontWeight: '500' }, g));
+ const wordmark = el('div', { fontFamily: WORDMARK_FACE, fontWeight: '700', lineHeight: '1', letterSpacing: '-0.02em', color: P.ink, display: 'flex', whiteSpace: 'pre' }, wm);
+
+ // ── Meta rows + subline ──
+ const metaBase = (x?: Record<string, string>) => ({ fontFamily: MONO, fontSize: '15px', letterSpacing: '0.10em', textTransform: 'uppercase', color: P.subtle, whiteSpace: 'nowrap', ...(x || {}) });
+ const recDot = el('div', { width: '8px', height: '8px', borderRadius: '4px', background: P.love, opacity: '1' });
+ const timecodeEl = el('div', metaBase({ fontVariantNumeric: 'tabular-nums' }), timecode(0));
+ const sublinePath = el('span', { color: P[shelves[0]?.accent] || P.love }, '/');
+ let topMeta: HTMLElement, subline: HTMLElement, hairline: HTMLElement, bottomMeta: HTMLElement, root: HTMLElement;
+
+ if (mode === 'canvas') {
+ hairline = el('div', { position: 'absolute', left: '72px', right: '72px', top: '96px', height: '1px', background: P.ink, transformOrigin: 'left', transform: 'scaleX(0)', opacity: '0.9' });
+ topMeta = el('div', { position: 'absolute', left: '72px', right: '72px', top: '56px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', opacity: '0' }, [
+ el('div', metaBase(), tagline),
+ el('div', { display: 'flex', alignItems: 'center', gap: '10px' }, [recDot, el('div', metaBase({ color: P.love }), 'REC')]),
+ timecodeEl,
+ ]);
+ wordmark.style.fontSize = '138px';
+ subline = el('div', metaBase({ marginTop: '18px', marginLeft: '4px', color: P.muted, opacity: '0', transform: 'translateY(24px)' }), ['Six shelves. One vault. ', sublinePath]);
+ const wmBlock = el('div', { position: 'absolute', left: '68px', top: '178px' }, [wordmark, subline]);
+ bottomMeta = el('div', { position: 'absolute', left: '72px', right: '72px', bottom: '56px', display: 'flex', justifyContent: 'space-between', opacity: '0' }, [
+ el('div', metaBase({ color: P.muted }), 'Rosé Pine · Dawn · plates inverted'),
+ el('div', metaBase({ color: P.muted }), 'Scroll ↓'),
+ ]);
+ root = el('div', { position: 'absolute', inset: '0', background: P.base, color: P.ink, opacity: '0', overflow: 'hidden', fontFamily: ARCHIVO }, [hairline, topMeta, wmBlock, nav, plateHost, bottomMeta]);
+ } else {
+ wordmark.style.fontSize = withPlate ? 'clamp(36px, 6.4vw, 88px)' : 'clamp(28px, 10vw, 80px)';
+ hairline = el('div', { height: '1px', marginTop: '14px', background: P.ink, transformOrigin: 'left', transform: 'scaleX(0)', opacity: '0.9' });
+ topMeta = el('div', { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '12px', opacity: '0' }, [
+ el('div', metaBase({ fontSize: 'clamp(10px, 1.05vw, 13px)' }), tagline),
+ el('div', { display: 'flex', alignItems: 'center', gap: '8px' }, [recDot, timecodeEl]),
+ ]);
+ subline = el('div', metaBase({ fontSize: 'clamp(10px, 1.05vw, 13px)', marginTop: '14px', color: P.muted, whiteSpace: 'normal', opacity: '0', transform: 'translateY(24px)' }), ['Six shelves. One vault. ', sublinePath]);
+ const left = el('div', undefined, [wordmark, subline, nav]);
+ const center = el('div', { display: 'grid', gridTemplateColumns: withPlate ? 'minmax(0,1fr) clamp(300px, 44%, 560px)' : 'minmax(0,1fr)', gap: 'clamp(28px, 3.4vw, 56px)', alignItems: 'center' }, withPlate ? [left, plateHost] : [left]);
+ const centerWrap = el('div', { flex: '1', display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: 'clamp(28px, 4vw, 56px) 0' }, center);
+ bottomMeta = el('div', { paddingTop: '32px', display: 'flex', justifyContent: 'space-between', gap: '12px', opacity: '0' }, [
+ el('div', metaBase({ fontSize: 'clamp(10px, 1.05vw, 13px)', color: P.muted }), 'Rosé Pine · Dawn'),
+ el('div', metaBase({ fontSize: 'clamp(10px, 1.05vw, 13px)', color: P.muted }), 'Scroll ↓'),
+ ]);
+ root = el('div', { display: 'flex', flexDirection: 'column', minHeight: `min(calc(100svh - ${MARQUEE}px), 620px)`, padding: 'clamp(28px, 4vw, 56px) clamp(20px, 3.8vw, 72px)', background: P.base, color: P.ink, opacity: '0', fontFamily: ARCHIVO }, [topMeta, hairline, centerWrap, bottomMeta]);
+ }
+
+ let lastStep = -1, lastAccent = '';
+ function paintPlate(T: number, st: any, reveal: number) {
+ plateSurface.style.clipPath = `inset(0 ${(1 - reveal) * 100}% 0 0)`;
+ const accentName = st.active >= 0 ? shelves[st.active].accent : 'love';
+ const accentCol = Q[accentName];
+ chromeRight.textContent = st.active >= 0 ? `${shelves[st.active].n} / 06` : '— / 06';
+ chromeRight.style.color = accentCol;
+ const step = Math.floor(T * 6);
+ if (step !== lastStep || accentName !== lastAccent) {
+ lastStep = step; lastAccent = accentName;
+ for (let i = 0; i < dots.length; i++) {
+ const v = hash(i, step), v2 = hash(i + 991, step), on = v > 0.72;
+ const d = dots[i];
+ d.style.background = on && v2 > 0.6 ? accentCol : Q.ink;
+ d.style.opacity = String(on ? 0.35 + v2 * 0.55 : 0.08);
+ }
+ }
+ standing.style.opacity = String(st.standing);
+ standing.style.transform = `translateY(${(1 - st.standing) * 16}px)`;
+ for (let i = 0; i < sections.length; i++) {
+ sections[i].block.style.opacity = String(st.plateAct[i]);
+ sections[i].block.style.transform = `translateY(${st.plateY[i]}px)`;
+ sections[i].rule.style.transform = `scaleX(${st.plateLine[i]})`;
+ }
+ }
+
+ function paint(T: number, st: any) {
+ const master = Math.min(enter(T, BOOT, 0.5).o, easeOutCubic(clamp((AUTHORED - T) / 0.5, 0, 1)));
+ const reveal = draw(T, BOOT + 0.9, 1.0) * (1 - draw(T, CLOSE + 1.2, 0.8));
+ const rule = draw(T, BOOT + 0.25, 0.9), pulse = 0.55 + 0.45 * Math.sin(T * 5), sub = enter(T, BOOT + 2.1);
+ root.style.opacity = String(master);
+ hairline.style.transform = `scaleX(${rule})`;
+ topMeta.style.opacity = String(enter(T, BOOT + 0.2).o);
+ recDot.style.opacity = String(pulse);
+ timecodeEl.textContent = timecode(T);
+ bottomMeta.style.opacity = String(enter(T, BOOT + 2.4).o);
+ // Wordmark decode
+ const roll = Math.floor(T * 18);
+ for (let i = 0; i < wm.length; i++) {
+ const g = name[i], at = BOOT + 0.35 + i * 0.16, revealed = T >= at + 0.55, e = enter(T, at, 0.5);
+ const ch = revealed ? g : GLYPH_POOL[Math.floor(hash(i, roll) * GLYPH_POOL.length)];
+ const node = wm[i];
+ if (node.textContent !== ch) node.textContent = ch;
+ node.style.opacity = String(e.o);
+ node.style.transform = `translateY(${e.y}px)`;
+ node.style.color = g === '-' ? P.love : revealed ? P.ink : P.muted;
+ node.style.fontFamily = revealed ? WORDMARK_FACE : MONO;
+ node.style.fontWeight = revealed ? '700' : '500';
+ }
+ // Subline
+ subline.style.opacity = String(sub.o);
+ subline.style.transform = `translateY(${sub.y}px)`;
+ sublinePath.textContent = st.active >= 0 ? shelves[st.active].path : '/';
+ sublinePath.style.color = P[st.accent];
+ // Rows
+ for (let i = 0; i < rows.length; i++) {
+ const a = st.act[i], s = shelves[i], acc = P[s.accent], lit = a > 0.5;
+ const en = enter(T, BOOT + 1.3 + i * 0.13, 0.55);
+ const R = rows[i];
+ R.a.style.opacity = String(en.o);
+ R.a.style.transform = `translateY(${en.y}px)`;
+ R.wash.style.opacity = String(a);
+ R.wash.style.transform = `scaleX(${a})`;
+ R.chip.style.color = lit ? P.base : P.muted;
+ R.chip.style.background = lit ? acc : 'transparent';
+ R.chip.style.boxShadow = `inset 0 0 0 1px ${lit ? acc : P.line}`;
+ R.label.style.color = a > 0.01 ? acc : P.ink;
+ R.label.style.opacity = String(0.55 + 0.45 * Math.max(a, st.active >= 0 ? 0 : 1));
+ R.label.style.transform = `translateX(${a * M.shift}px)`;
+ R.path.style.opacity = String(0.6 + 0.4 * a);
+ if (R.arrow) { R.arrow.style.opacity = String(a); R.arrow.style.transform = `translateX(${(1 - a) * -8}px)`; }
+ }
+ paintPlate(T, st, reveal);
+ if (mode === 'flow' && withPlate) {
+ const k = plateHost.clientWidth / PLATE_W;
+ if (k > 0 && Math.abs(k - plateScaleK) > 0.001) { plateScaleK = k; (plateHost as any)._inner.style.transform = `scale(${k})`; }
+ }
+ }
+ return { root, paint };
+ }
+
+ function initHero() {
+ const host = document.querySelector('[data-hero-loop]') as HTMLElement | null;
+ if (!host || host.dataset.bound) return;
+ host.dataset.bound = '1';
+ let cfg: { shelves: Shelf[]; name: string; tagline: string };
+ try { cfg = JSON.parse(host.dataset.hero || '{}'); } catch { return; }
+ const shelves = cfg.shelves || [];
+ if (!shelves.length) return;
+
+ const reduced = matchMedia('(prefers-reduced-motion: reduce)');
+ let hover: { idx: number | null; last: number | null } = { idx: null, last: null };
+ const onHold = (i: number) => { hover = { idx: i, last: i }; };
+ const onRelease = () => { hover = { idx: null, last: hover.last }; };
+
+ let current: { root: HTMLElement; paint: (T: number, st: any) => void } | null = null;
+ let mode: 'canvas' | 'flow' | null = null, withPlate = false, dark = document.documentElement.getAttribute('data-theme') === 'dark';
+ let carriedT = 0, carriedMix = 0, raf = 0, prev = 0, visible = !document.hidden, inView = true;
+
+ function rebuild() {
+ const w = host.clientWidth || window.innerWidth;
+ const canvas = window.innerWidth >= CANVAS_MIN;
+ mode = canvas ? 'canvas' : 'flow';
+ withPlate = !canvas && window.innerWidth >= FLOW_PLATE_MIN;
+ dark = document.documentElement.getAttribute('data-theme') === 'dark';
+ const P = dark ? NIGHT : DAWN, Q = dark ? DAWN : NIGHT;
+ host.textContent = '';
+ if (canvas) {
+ const stage = el('div', { position: 'absolute', left: '0', top: '0', transformOrigin: '0 0' });
+ host.style.height = `max(${MIN_HEIGHT}px, min(calc(100vh - ${MARQUEE}px), ${(H / W) * 100}vw))`;
+ const built = build(w, 'canvas', false, shelves, cfg.name, cfg.tagline, P, Q, onHold, onRelease);
+ stage.append(built.root);
+ host.append(stage);
+ const fit = () => {
+ const bw = host.clientWidth, bh = host.clientHeight, scale = bw > 0 && bh > 0 ? Math.min(bw / W, bh / H) : 0;
+ stage.style.width = W + 'px'; stage.style.height = H + 'px';
+ stage.style.left = ((bw - W * scale) / 2) + 'px'; stage.style.top = ((bh - H * scale) / 2) + 'px';
+ stage.style.transform = `scale(${scale})`;
+ };
+ fit();
+ current = built;
+ (host as any)._fit = fit;
+ } else {
+ host.style.height = '';
+ const built = build(w, 'flow', withPlate, shelves, cfg.name, cfg.tagline, P, Q, onHold, onRelease);
+ host.append(built.root);
+ current = built;
+ (host as any)._fit = null;
+ }
+ // Paint one frame immediately so a paused/first render is correct.
+ paintFrame();
+ }
+
+ function paintFrame() {
+ if (!current) return;
+ const T = reduced.matches ? HELD_FRAME : carriedT;
+ const st = shelfState(T, carriedMix, hover.last, shelves);
+ current.paint(T, st);
+ }
+
+ const running = () => !reduced.matches && visible && inView;
+ function tick(now: number) {
+ if (!running()) { raf = 0; return; }
+ const dt = Math.min(0.05, (now - prev) / 1000); prev = now;
+ carriedT = (carriedT + dt) % AUTHORED;
+ const target = hover.idx !== null ? 1 : 0;
+ carriedMix += (target - carriedMix) * Math.min(1, dt * 14);
+ paintFrame();
+ raf = requestAnimationFrame(tick);
+ }
+ function start() { if (raf || !running()) { if (!running()) paintFrame(); return; } prev = performance.now(); raf = requestAnimationFrame(tick); }
+ function stop() { if (raf) cancelAnimationFrame(raf); raf = 0; }
+
+ rebuild();
+ start();
+
+ // Reactivity: breakpoint + theme rebuild, visibility + in-view pause.
+ const canvasMq = matchMedia(`(min-width: ${CANVAS_MIN}px)`), plateMq = matchMedia(`(min-width: ${FLOW_PLATE_MIN}px)`);
+ const onBp = () => { stop(); rebuild(); start(); };
+ canvasMq.addEventListener('change', onBp);
+ plateMq.addEventListener('change', onBp);
+ reduced.addEventListener('change', () => { stop(); paintFrame(); start(); });
+ window.addEventListener('daemonmodechange', () => { stop(); rebuild(); start(); });
+ const ro = new ResizeObserver(() => { const f = (host as any)._fit; if (f) f(); });
+ ro.observe(host);
+ const onVis = () => { visible = !document.hidden; visible ? start() : stop(); };
+ document.addEventListener('visibilitychange', onVis);
+ const io = new IntersectionObserver(([e]) => { inView = e.isIntersecting; inView ? start() : stop(); });
+ io.observe(host);
+
+ document.addEventListener('astro:before-swap', () => {
+ stop(); ro.disconnect(); io.disconnect();
+ document.removeEventListener('visibilitychange', onVis);
+ canvasMq.removeEventListener('change', onBp); plateMq.removeEventListener('change', onBp);
+ }, { once: true });
+ }
+
+ document.addEventListener('astro:page-load', initHero);
+ if (document.readyState !== 'loading') initHero();
+ else document.addEventListener('DOMContentLoaded', initHero);
+</script>
diff --git a/src/components/Icon.astro b/src/components/Icon.astro
@@ -19,6 +19,9 @@ const paths: Record<string, string> = {
is a document; a payload is a fragment of one. */
doc: '<path d="M14 3H6a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V8z"/><path d="M14 3v5h5M9 13h6M9 17h4"/>',
layers: '<path d="m12 3 9 5-9 5-9-5 9-5z"/><path d="m3 13 9 5 9-5"/>',
+ warn: '<path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/><path d="M12 9v4M12 17h.01"/>',
+ info: '<circle cx="12" cy="12" r="9"/><path d="M12 16v-4M12 8h.01"/>',
+ lock: '<rect x="5" y="11" width="14" height="10"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/>',
};
const d = paths[name] ?? '';
---
diff --git a/src/components/Marquee.astro b/src/components/Marquee.astro
@@ -1,157 +1,46 @@
---
-import Icon from './Icon.astro';
-import type { CategoryDef } from '../lib/taxonomy';
-
+interface LaneTitle { n: string; title: string; accent: string }
+interface LaneMeta { tag: string; text: string; accent: string }
interface Props {
- items: { cat: CategoryDef; count: number }[];
+ titles: LaneTitle[];
+ meta: LaneMeta[];
+ /** The manifest count shown in the fixed cap. */
+ manifest: string;
}
-const { items } = Astro.props;
+const { titles, meta, manifest } = Astro.props;
/**
- * The domain band — the archive's index grammar, run sideways.
- *
- * ── Grammar ──
- * The band could have opened each entry with the section eyebrow, but that
- * makes it read as twelve small banners sliding past. The site's core
- * object is the record row: number, kind mark, title, meta, in that order,
- * 10px mono with an 11px kind icon. Each entry carries exactly that line
- * horizontally — the index, the kind, the domain in display type, the
- * sheet count trailing in the meta slot — so the band reads as the live
- * index in motion rather than as decoration. Like the row, the trailing
- * count drops on narrow viewports, and like the index, every entry sets at
- * the same weight: a row is a row.
- *
- * The `^:` mark survives in exactly one place — the band's own tab. It is
- * the site's self-naming mark, and the band names itself once; repeated on
- * every entry it was furniture. The tab counts its entries too, because
- * every index on this site counts itself, and a count derived from the
- * data cannot go stale.
- *
- * ── Colour ──
- * The ground is `--band`: iris walked down to a weight that carries text
- * (see tokens.css). The accent carries the domain title and its kind icon
- * while every small word stays cream — record-row grammar, not a contrast
- * ruling.
- *
- * Two identical runs, translated as one unit, so the reset is invisible.
+ * The manifest band — two counter-scrolling lanes with a fixed inverse-plate
+ * cap, ported from the main site's dual-lane marquee. The title lane scrolls
+ * left, the meta lane scrolls right; each list is duplicated so the −50% loop
+ * is seamless. Both pause on hover; static under reduced motion.
*/
-
-/* Rendered twice. The second run is `aria-hidden` — the band is one index,
- not two, and a screen reader should not read twelve domains and then
- read them again. */
-const RUN = items;
+const titleRun = [...titles, ...titles];
+const metaRun = [...meta, ...meta];
---
-<!-- `.plate` pins the night palette for the whole band. Without it `--acc`
- resolves to the *dawn* accents, which are reading weights drawn for
- cream — on this ground pine and rose land near 1:1 and the kind marks
- disappear. The band is dark in both modes, so it takes the dark
- palette in both modes. -->
-<section class="marquee plate bleed" aria-label="Domains" data-marquee>
- <span class="marquee__tab marquee__tab--l">
- <span class="dot" aria-hidden="true"><i></i><u></u></span>
- ^: Index
- </span>
-
- <div class="marquee__track" data-marquee-track>
- {[0, 1].map((run) => (
- <div class="marquee__run" aria-hidden={run === 1 ? 'true' : undefined}>
- {RUN.map((item, i) => (
- <span class="marquee__item" style={`--acc: var(--${item.cat.accent});`}>
- <span class="marquee__n">{String(i + 1).padStart(2, '0')}</span>
- <span class="marquee__kind"><Icon name="doc" /> {item.cat.tag}</span>
- <span class="marquee__title">{item.cat.title}</span>
- <span class="marquee__qual">{item.count} {item.count === 1 ? 'sheet' : 'sheets'}</span>
- </span>
+<section class="vault-lanes bleed" aria-label="Manifest" data-marquee>
+ <div class="vault-lanes__body">
+ <div class="vault-lane-wrap vault-lane-wrap--top">
+ <div class="vault-lane">
+ {titleRun.map((t, i) => (
+ <div class="vault-title" style={`--acc: var(--${t.accent});`} aria-hidden={i >= titles.length ? 'true' : undefined}>
+ <span class="n">{t.n}</span><span class="t">{t.title}</span>
+ </div>
))}
</div>
- ))}
+ </div>
+ <div class="vault-lane-wrap">
+ <div class="vault-lane-rev">
+ {metaRun.map((m, i) => (
+ <div class="vault-meta" style={`--acc: var(--${m.accent});`} aria-hidden={i >= meta.length ? 'true' : undefined}>
+ <span class="tag">{m.tag}</span><span class="tx">{m.text}</span><span class="sep">/</span>
+ </div>
+ ))}
+ </div>
+ </div>
+ </div>
+ <div class="vault-cap" aria-hidden="true">
+ <span>Manifest ^: {manifest}</span>
+ <span class="verified"><i></i>Verified · MMXXVI</span>
</div>
-
- <span class="marquee__tab marquee__tab--r">{String(items.length).padStart(2, '0')} domains</span>
-
- <!-- CRT film over the band. z-index 1 puts it over the track and under
- the tabs (z-index 2); events pass straight through. -->
- <span class="scanlines" aria-hidden="true"></span>
</section>
-
-<style>
- /* ---- Cyberdeck chrome for the band -------------------------------------
- Terminal refinements over the global `.marquee` block: JetBrains Mono
- for the furniture type, hairline dividers where the tabs meet the band,
- and a subdued scanline film. Scoped selectors out-rank global.css; only
- the theme/palette reads stay global (they come from `.plate`). */
- .marquee__item,
- .marquee__tab {
- font-family: var(--font-term, 'JetBrains Mono', 'IBM Plex Mono', ui-monospace, monospace);
- }
- /* The tabs sit on their own black ground; a hairline on the seam keeps
- the join from reading as a value smear against the band. */
- .marquee__tab--l { border-inline-end: 1px solid var(--rule-hi); }
- .marquee__tab--r { border-inline-start: 1px solid var(--rule-hi); }
- /* Subtler than the code panes' film — this band is in motion already, so
- the CRT texture stays well under the type. */
- .marquee > .scanlines { opacity: 0.5; }
-</style>
-
-<script>
- /**
- * Driven from rAF rather than from a CSS keyframe, which is the whole
- * reason this is script at all: a CSS animation cannot ease toward a
- * fraction of its speed under the pointer, and a hard `paused` reads as
- * a broken animation rather than as a deliberate slow-down.
- */
- const SPEED = 70; // px/s
- const HOVER = 0.22; // multiplier under the pointer — never zero
- const EASE = 0.08; // how fast the current speed chases the target
-
- function initMarquee(): void {
- const host = document.querySelector<HTMLElement>('[data-marquee]');
- const track = document.querySelector<HTMLElement>('[data-marquee-track]');
- if (!host || !track || host.dataset.bound) return;
- host.dataset.bound = '1';
-
- // Static under reduced motion: the first run is already the complete
- // index, so it stays readable with nothing moving.
- if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
-
- const run = track.firstElementChild as HTMLElement | null;
- if (!run) return;
-
- let offset = 0;
- let speed = SPEED;
- let target = SPEED;
- let last = 0;
- let raf = 0;
-
- host.addEventListener('pointerenter', () => { target = SPEED * HOVER; });
- host.addEventListener('pointerleave', () => { target = SPEED; });
-
- const frame = (now: number) => {
- const dt = last ? Math.min(0.05, (now - last) / 1000) : 0;
- last = now;
- speed += (target - speed) * EASE;
- offset += speed * dt;
-
- // One run's width is the wrap point: the second run lands exactly
- // where the first started, so resetting there is invisible. Measured
- // every frame rather than cached — the band reflows on resize and on
- // font load, and a stale width shows up as a visible jump.
- const width = run.offsetWidth;
- if (width > 0 && offset >= width) offset -= width;
-
- track.style.transform = `translate3d(${-offset.toFixed(2)}px, 0, 0)`;
- raf = requestAnimationFrame(frame);
- };
-
- raf = requestAnimationFrame(frame);
-
- // Astro's view transitions swap the document without unloading it, so
- // an un-cancelled rAF would keep running against a detached node and
- // stack a second loop on the next page that has a band.
- document.addEventListener('astro:before-swap', () => cancelAnimationFrame(raf), { once: true });
- }
-
- document.addEventListener('astro:page-load', initMarquee);
- if (document.readyState !== 'loading') initMarquee();
- else document.addEventListener('DOMContentLoaded', initMarquee);
-</script>
diff --git a/src/components/Operator.astro b/src/components/Operator.astro
@@ -0,0 +1,71 @@
+---
+interface Props {
+ sheets: number;
+ domains: number;
+ mirrors: number;
+ updated: string;
+}
+const { sheets, domains, mirrors, updated } = Astro.props;
+
+/**
+ * The operator band — a dark plate with a grid texture, capability chips, a
+ * terminal `--status` readout and an action rail, ported from the main site's
+ * operator section. The rail's search field opens the Pagefind modal
+ * (`[data-search-open]`, bound in SearchModal); the stats and CTAs bind the
+ * real vault counts.
+ */
+const pad2 = (n: number) => String(n).padStart(2, '0');
+---
+<section class="operator bleed" aria-labelledby="op-title">
+ <div class="operator__grid" aria-hidden="true"></div>
+ <div class="archive operator__inner">
+ <div class="sec-head">
+ <span class="sec-head__eyebrow" style="--tone: var(--color-night-iris);">Index / operator</span>
+ <h2 id="op-title" class="sec-head__title" style="--tone: var(--color-night-iris);">Start here.</h2>
+ <p class="sec-head__intro" style="--intro: var(--color-signal-meta);">
+ {sheets} sheets across {domains} shelves, plus two full upstream mirrors. The shelves are
+ below; the index itself runs under them.
+ </p>
+ </div>
+
+ <div class="operator__cols">
+ <div>
+ <div class="op-chips">
+ <span class="op-chip" style="--chip: var(--color-night-iris);"><span class="pip dmn-anim-pulse"></span>Offline search</span>
+ <span class="op-chip" style="--chip: var(--color-night-foam);">Copy-ready</span>
+ <span class="op-chip" style="--chip: var(--color-night-love);">Zero fluff</span>
+ </div>
+ <div class="op-readout">
+ <div class="op-readout__cmd">
+ <span class="u">daemon@ref</span><span>~</span><span>%</span><span class="c">vault --status</span><span class="dmn-blink"></span>
+ </div>
+ <dl>
+ <dt>sheets</dt><dd>{sheets} · curated</dd>
+ <dt>shelves</dt><dd>{domains} · domains</dd>
+ <dt>search</dt><dd><span class="on">offline</span> · fuzzy · no tracking</dd>
+ <dt>updated</dt><dd>{updated}</dd>
+ </dl>
+ </div>
+ </div>
+
+ <div class="op-rail">
+ <div class="op-rail__eyebrow">Quick search / 2026</div>
+ <button class="op-search" data-search-open type="button" aria-label="Search the vault">
+ <span class="slash">/</span>
+ <span class="ph">search the vault</span>
+ <kbd>⌘K</kbd>
+ </button>
+ <dl class="op-stats">
+ <dd><span style="color: var(--color-night-iris);">{pad2(domains)}</span> shelves</dd>
+ <dd><span style="color: var(--color-night-love);">{sheets}</span> sheets</dd>
+ <dd><span style="color: var(--color-night-gold);">{pad2(mirrors)}</span> mirrors</dd>
+ </dl>
+ <div class="op-cta-list">
+ <a class="op-cta op-cta--pine" href="#index"><span>Open the index</span><span class="count">{sheets} sheets</span><span>↓</span></a>
+ <a class="op-cta op-cta--text" href="#shelves"><span>Pick a shelf</span><span class="count">{pad2(domains)} shelves</span><span>↓</span></a>
+ <a class="op-cta op-cta--love" href="#mirrors"><span>Mirrored trees</span><span class="count">{pad2(mirrors)} upstream</span><span>↓</span></a>
+ </div>
+ </div>
+ </div>
+ </div>
+</section>
diff --git a/src/components/RecordRow.astro b/src/components/RecordRow.astro
@@ -11,32 +11,38 @@ interface Props {
kind: string;
kindIcon?: string;
accent?: string;
- /** The trailing slot: difficulty, date, page count. Dropped below md,
- * where the row has no room for a fifth column. */
+ /** Trailing slot: date / page count. Dropped below md. */
meta?: string;
+ /** Disclosure detail, revealed on hover/focus. */
+ excerpt?: string;
+ tagline?: string;
+ /** Client-side filter bucket (index page). */
+ bucket?: string;
}
-const { href, n, title, kind, kindIcon = 'doc', accent = 'foam', meta } = Astro.props;
+const { href, n, title, kind, kindIcon = 'doc', accent = 'foam', meta, excerpt, tagline, bucket } = Astro.props;
/**
- * One record in an index — the site's core object.
+ * One record in an index — the site's core object, ported from the main
+ * site's RecordRow. One line, not a card: the title is NOT a display heading,
+ * so a list of fifty stays scannable.
*
- * One line, not a card. The title is deliberately NOT set as a display
- * heading: at heading weight every entry carries the weight of a section
- * and a list of fifty becomes unscannable. The point of an index is the
- * shape of the whole list, and that only reads if a row is a row.
- *
- * Hovering wipes ink across the row (`.wipe` in chrome.css). On that ink
- * the dawn accents sit at roughly 1:1, so every coloured cell takes a
- * cream weight instead of keeping a hue nobody could read — see the
- * `.record:hover` rules in global.css.
+ * Rule #6 of the redesign handoff: the hover is a PAPER wash (`--overlay`),
+ * not the solid ink wipe — the title must stay ≥4.5:1 on hover — so nothing
+ * here re-colours the text on hover. See `.rec` in daemon.css.
*/
---
-<a class="record wipe dmn-t corners" href={href} style={`--acc: var(--${accent});`}>
- <span class="record__grid">
- <span class="record__n">{n}</span>
- <span class="record__kind"><Icon name={kindIcon} /> {kind}</span>
- <span class="record__title">{title}</span>
- {meta ? <span class="record__meta">{meta}</span> : <span class="record__meta" />}
- <span class="record__arrow"><Icon name="chevron" /></span>
+<a class="rec" href={href} style={`--acc: var(--${accent});`} data-bucket={bucket}>
+ <span class="rec__grid">
+ <span class="rec__n">{n}</span>
+ <span class="rec__kind"><Icon name={kindIcon} /> {kind}</span>
+ <span class="rec__title">{title}</span>
+ <span class="rec__date">{meta}</span>
+ <span class="rec__arrow"><Icon name="chevron" /></span>
+ {(excerpt || tagline) && (
+ <span class="rec__reveal"><span>
+ {excerpt && <span class="rec__excerpt">{excerpt}</span>}
+ {tagline && <span class="rec__tagline">{tagline}</span>}
+ </span></span>
+ )}
</span>
</a>
diff --git a/src/components/SectionHeader.astro b/src/components/SectionHeader.astro
@@ -0,0 +1,26 @@
+---
+interface Props {
+ eyebrow: string;
+ title: string;
+ intro?: string;
+ /** Heading + eyebrow colour — each section is keyed to a different accent. */
+ tone?: 'ink' | 'pine' | 'iris' | 'love' | 'gold' | 'foam' | 'rose';
+ class?: string;
+}
+const { eyebrow, title, intro, tone = 'ink', class: cls } = Astro.props;
+
+/**
+ * The three-column header every archive section opens with — mono eyebrow,
+ * oversized display heading, short intro — ported from the main site's
+ * SectionHeader. The eyebrow takes the section's own accent rather than a
+ * neutral; the heading tone is keyed to the same. Stacks below the lg break.
+ */
+// The eyebrow always uses an accent (never bare ink); the title follows tone.
+const eyebrowVar = tone === 'ink' ? 'var(--pine)' : `var(--${tone})`;
+const titleVar = tone === 'ink' ? 'var(--text)' : `var(--${tone})`;
+---
+<div class:list={['sec-head', cls]}>
+ <span class="sec-head__eyebrow" style={`--tone: ${eyebrowVar};`}>{eyebrow}</span>
+ <h2 class="sec-head__title" style={`--tone: ${titleVar};`}>{title}</h2>
+ {intro && <p class="sec-head__intro">{intro}</p>}
+</div>
diff --git a/src/fonts/dm-mono-300.woff2 b/src/fonts/dm-mono-300.woff2
Binary files differ.
diff --git a/src/fonts/dm-mono-400-italic.woff2 b/src/fonts/dm-mono-400-italic.woff2
Binary files differ.
diff --git a/src/fonts/dm-mono-400.woff2 b/src/fonts/dm-mono-400.woff2
Binary files differ.
diff --git a/src/fonts/dm-mono-500.woff2 b/src/fonts/dm-mono-500.woff2
Binary files differ.
diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro
@@ -6,10 +6,10 @@ import SearchModal from '../components/SearchModal.astro';
import { url } from '../lib/url';
import '../styles/app.css';
// Preload the two faces that paint above the fold — the wordmark in the
-// masthead and the mono the segbar/labels use — so the header does not flash
+// masthead and the DM Mono the masthead/labels use — so the header does not flash
// its fallback on first paint. The rest load on demand via @font-face.
import notoWordmark from '../fonts/noto-sans-display-var-latin.woff2';
-import jetbrainsMono from '../fonts/jetbrains-mono-400-latin.woff2';
+import dmMono from '../fonts/dm-mono-400.woff2';
interface Props {
title?: string;
@@ -32,7 +32,7 @@ const canonical = new URL(Astro.url.pathname, Astro.site).href;
<link rel="canonical" href={canonical} />
<link rel="icon" href={url('favicon.svg')} type="image/svg+xml" />
<link rel="preload" href={notoWordmark} as="font" type="font/woff2" crossorigin />
- <link rel="preload" href={jetbrainsMono} as="font" type="font/woff2" crossorigin />
+ <link rel="preload" href={dmMono} as="font" type="font/woff2" crossorigin />
<meta name="theme-color" content="#191724" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#faf4ed" media="(prefers-color-scheme: light)" />
<meta property="og:type" content="website" />
diff --git a/src/pages/index.astro b/src/pages/index.astro
@@ -1,10 +1,11 @@
---
import Base from '../layouts/Base.astro';
-import Hero from '../components/Hero.astro';
+import HeroLoop from '../components/HeroLoop.astro';
import Marquee from '../components/Marquee.astro';
-import DomainPlate from '../components/DomainPlate.astro';
+import Operator from '../components/Operator.astro';
+import SectionHeader from '../components/SectionHeader.astro';
import RecordRow from '../components/RecordRow.astro';
-import Icon from '../components/Icon.astro';
+import Callout from '../components/Callout.astro';
import { CATEGORIES, categoryOf } from '../lib/taxonomy';
import { allSheets, categoryCounts, sheetHref } from '../lib/sheets';
import { payloadsByTopic } from '../lib/payloads';
@@ -23,105 +24,248 @@ const internalSections = await internalBySection();
const internalRoot = await internalRootPages();
const internalPages = internalSections.reduce((n, g) => n + g.count, 0) + internalRoot.length;
-const marqueeItems = CATEGORIES.map((cat) => ({ cat, count: counts[cat.slug] ?? 0 }));
+// Latest sheet date, for the operator readout.
+const latest = sheets
+ .map((s) => s.data.updated)
+ .filter((d): d is string => !!d)
+ .sort()
+ .at(-1);
+const updatedLabel = latest ? `${latest.slice(0, 7).replace('-', '.')} · latest` : '2026 · current';
-const featured = [...sheets]
- .sort((a, b) => (b.data.updated ?? '').localeCompare(a.data.updated ?? ''))
- .slice(0, 8);
+// ── Hero: six flagship shelves, one accent each (a designed showcase; the
+// full taxonomy is in the shelves grid below). Real routes + counts. ──
+const HERO = [
+ { slug: 'active-directory', accent: 'pine' },
+ { slug: 'enumeration', accent: 'love' },
+ { slug: 'exploitation', accent: 'gold' },
+ { slug: 'privilege-escalation', accent: 'foam' },
+ { slug: 'web', accent: 'iris' },
+ { slug: 'dfir', accent: 'rose' },
+];
+const heroShelves = HERO.map((h, i) => {
+ const c = categoryOf(h.slug);
+ return { n: String(i + 1).padStart(2, '0'), label: c.tag, path: url(h.slug), accent: h.accent, desc: c.blurb };
+});
+
+// ── Manifest band lanes ──
+const laneTitles = [
+ { n: '01', title: 'DΛΣMӨП', accent: 'iris' },
+ { n: '02', title: 'Cheatsheets', accent: 'foam' },
+ { n: '03', title: 'Offline search', accent: 'gold' },
+ { n: '04', title: 'Copy-ready', accent: 'pine' },
+ { n: '05', title: `${CATEGORIES.length} shelves`, accent: 'love' },
+ { n: '06', title: 'Zero fluff', accent: 'rose' },
+];
+const laneMeta = [
+ { tag: 'Sys', text: 'Open notebook', accent: 'iris' },
+ { tag: 'Search', text: 'Pagefind · offline', accent: 'love' },
+ { tag: 'Sheets', text: `${total} · curated`, accent: 'foam' },
+ { tag: 'Theme', text: 'Rosé Pine Dawn', accent: 'gold' },
+ { tag: 'Field', text: 'AD · web · privesc', accent: 'pine' },
+ { tag: 'Mirror', text: 'Two upstream trees', accent: 'love' },
+ { tag: 'Doctrine', text: 'Terse, not thin', accent: 'rose' },
+];
+
+// ── Index rows: recent sheets, bucketed for the client-side filter ──
+const byDate = [...sheets].sort((a, b) => (b.data.updated ?? '').localeCompare(a.data.updated ?? ''));
+// Recent, but spread across shelves: cap 3 per category so one busy shelf
+// (the CPTS workflow, freshest by date) does not fill the whole index and the
+// Sheets / Workflow filters both have rows.
+const perCat: Record<string, number> = {};
+const recentRaw: typeof byDate = [];
+for (const entry of byDate) {
+ const c = entry.data.category;
+ perCat[c] = (perCat[c] ?? 0) + 1;
+ if (perCat[c] > 3) continue;
+ recentRaw.push(entry);
+ if (recentRaw.length >= 24) break;
+}
+const recent = recentRaw
+ .map((entry, i) => {
+ const cat = categoryOf(entry.data.category);
+ const bucket = entry.data.category === 'pentest-workflow' ? 'workflow' : 'sheets';
+ const metaVal = entry.data.updated ? entry.data.updated.replace(/-/g, '.') : entry.data.difficulty.toUpperCase();
+ const tags = entry.data.tags.slice(0, 2).join(' · ');
+ return {
+ href: url(sheetHref(entry)),
+ n: String(i + 1).padStart(2, '0'),
+ title: entry.data.title,
+ kind: cat.tag,
+ accent: cat.accent,
+ meta: metaVal,
+ excerpt: entry.data.description || `${cat.title} — terse, tested, kept current.`,
+ tagline: [cat.title, tags].filter(Boolean).join(' · '),
+ bucket,
+ };
+ });
+
+// ── Shelves grid: the full taxonomy ──
+const shelfCards = CATEGORIES.map((cat, i) => ({
+ n: String(i + 1).padStart(2, '0'),
+ slug: cat.slug,
+ label: cat.title,
+ accent: cat.accent,
+ count: counts[cat.slug] ?? 0,
+}));
+
+// ── Mirrors manifest: the two swisskyrepo trees + their largest sub-trees ──
+const topPayloads = [...payloadTopics].sort((a, b) => b.count - a.count).slice(0, 2);
+const topInternal = [...internalSections].sort((a, b) => b.count - a.count).slice(0, 2);
+const mirrorRows = [
+ { n: '01', path: 'payloads/', count: payloadPages },
+ { n: '02', path: 'internal/', count: internalPages },
+ { n: '03', path: `payloads/${topPayloads[0]?.slug ?? ''}/`, count: topPayloads[0]?.count ?? 0 },
+ { n: '04', path: `internal/${topInternal[0]?.slug ?? ''}/`, count: topInternal[0]?.count ?? 0 },
+ { n: '05', path: `payloads/${topPayloads[1]?.slug ?? ''}/`, count: topPayloads[1]?.count ?? 0 },
+ { n: '06', path: `internal/${topInternal[1]?.slug ?? ''}/`, count: topInternal[1]?.count ?? 0 },
+];
---
<Base>
- <!-- The page's rhythm: paper hero, band, paper index, band, paper index.
- The bands break the measure and run edge to edge; their contents
- return to it. -->
- <Hero sheets={total} domains={domains} payloads={payloadPages} />
-
- <Marquee items={marqueeItems} />
-
- <div class="wrap">
- <section class="section" id="domains">
- <div class="section-head">
- <div>
- <p class="eyebrow" style="--acc: var(--foam);">
- <span class="eyebrow__n">01</span>
- <span class="eyebrow__mark">^:</span>
- <span>{CATEGORIES.length} domains</span>
- <span class="eyebrow__rule" aria-hidden="true"></span>
- </p>
- <h2>Pick your vector</h2>
- </div>
- </div>
- <div class="grid-plates">
- {CATEGORIES.map((cat, i) => (
- <DomainPlate cat={cat} count={counts[cat.slug] ?? 0} n={String(i + 1).padStart(2, '0')} />
- ))}
- </div>
- </section>
- </div>
+ <!-- 1 · Hero loop -->
+ <HeroLoop shelves={heroShelves} name="DΛΣMӨП-RΣF" tagline="DÆMON//SEC · Cheatsheet vault" />
- <!-- The ledger band. Far enough down the page from the marquee that the
- two never share a sightline, which is why they are free to differ —
- and being separate tokens, they do so on purpose. -->
- <a class="cta-band bleed section" href={url('payloads')}>
- <div class="wrap cta-band__body corners">
- <div>
- <p class="cta-band__eyebrow">02 ^: Full mirror · MIT</p>
- <h2>PayloadsAllTheThings</h2>
- <p>
- The complete {payloadTopics.length}-topic, {payloadPages}-page payload and bypass
- reference by <strong>swisskyrepo</strong> — mirrored on-site, fully searchable,
- every page linked back to source.
- </p>
- </div>
- <span class="btn">Browse payloads <Icon name="arrow" /></span>
+ <!-- 2 · Manifest band -->
+ <Marquee titles={laneTitles} meta={laneMeta} manifest={String(CATEGORIES.length).padStart(2, '0')} />
+
+ <!-- 3 · Operator band -->
+ <Operator sheets={total} domains={domains} mirrors={2} updated={updatedLabel} />
+
+ <!-- 4 · Index -->
+ <section id="index" class="dsection" data-index>
+ <SectionHeader
+ eyebrow="Reference transmissions"
+ title="Terse, tested, kept current."
+ intro={`${total} sheets across ${domains} shelves, each stripped to the commands that matter.`}
+ tone="iris"
+ />
+ <div class="filters">
+ <button type="button" class="pill" data-filter="all" aria-pressed="true">All</button>
+ <button type="button" class="pill" data-filter="sheets" aria-pressed="false">Sheets</button>
+ <button type="button" class="pill" data-filter="workflow" aria-pressed="false">Workflow</button>
+ <a class="pill" href={url('payloads')}>Payloads ↗</a>
+ <a class="pill" href={url('internal')}>Internal ↗</a>
+ <span class="filters__count" data-shown>10 shown</span>
</div>
- </a>
+ <div class="index-list">
+ {recent.map((r) => (
+ <RecordRow
+ href={r.href}
+ n={r.n}
+ title={r.title}
+ kind={r.kind}
+ accent={r.accent}
+ meta={r.meta}
+ excerpt={r.excerpt}
+ tagline={r.tagline}
+ bucket={r.bucket}
+ />
+ ))}
+ </div>
+ <button type="button" class="show-more" data-more hidden>
+ <span data-more-label>Show more</span><span>↓</span>
+ </button>
+ <div class="index-foot">
+ <span data-foot>Showing 10 of {total} sheets</span>
+ <a href={url('tags')}>All {CATEGORIES.length} shelves ↗</a>
+ </div>
+ </section>
+
+ <!-- 5 · Shelves grid (the full taxonomy — "pick your vector") -->
+ <section id="shelves" class="dsection">
+ <SectionHeader
+ eyebrow="Shelves / taxonomy"
+ title="Pick your vector."
+ intro={`${CATEGORIES.length} shelves, grouped by how you actually reach for them.`}
+ tone="pine"
+ />
+ <div class="shelves-grid">
+ {shelfCards.map((s) => (
+ <a class="shelf" href={url(s.slug)} style={`--acc: var(--${s.accent});`}>
+ <span class="shelf__top"><span>{s.n}</span><span>{s.count} {s.count === 1 ? 'sheet' : 'sheets'}</span></span>
+ <span class="shelf__title">{s.label}</span>
+ </a>
+ ))}
+ </div>
+ </section>
- <a class="cta-band cta-band--internal bleed section" href={url('internal')}>
- <div class="wrap cta-band__body corners">
+ <!-- 6 · Mirrors band (the upstreamed repos — swisskyrepo) -->
+ <section id="mirrors" class="mirrors bleed" aria-labelledby="mirrors-title">
+ <span class="mirrors__scrim" aria-hidden="true"></span>
+ <div class="archive mirrors__inner">
<div>
- <p class="cta-band__eyebrow">03 ^: Full mirror · credit upstream</p>
- <h2>InternalAllTheThings</h2>
- <p>
- The complete {internalSections.length}-section, {internalPages}-page internal-network
- reference by <strong>swisskyrepo</strong> — Active Directory, cloud, containers and
- red-team tradecraft, mirrored on-site and searchable offline.
+ <span class="mirrors__eyebrow">Third party / mirrored</span>
+ <h2 id="mirrors-title" class="mirrors__title">Some trees are borrowed.</h2>
+ <p class="mirrors__body">
+ Two directories are generated mirrors of <strong>swisskyrepo</strong>'s
+ PayloadsAllTheThings and InternalAllTheThings — regenerated on a schedule and never
+ hand-edited, searchable offline, every page linked back to source. Attribution and
+ licences live on the credits page.
</p>
+ <dl class="mirrors__dl">
+ <div><dt>Trees</dt><dd>02</dd></div>
+ <div><dt>Licence</dt><dd><span class="mirrors__chip">Upstream</span></dd></div>
+ <div><dt>Sync</dt><dd>Scheduled</dd></div>
+ <div><dt>Edits</dt><dd>None</dd></div>
+ </dl>
+ <a class="mirrors__cta" href={url('credits')}>Read the credits →</a>
+ </div>
+ <div class="mirror-panel">
+ <div class="mirror-panel__head"><span>Mirror manifest // upstream</span><span class="lic">swisskyrepo</span></div>
+ {mirrorRows.map((m) => (
+ <div class="mirror-row"><span class="n">{m.n}</span><span class="path">{m.path}</span><span class="count">{m.count} files</span></div>
+ ))}
</div>
- <span class="btn">Browse internal <Icon name="arrow" /></span>
- </div>
- </a>
-
- {featured.length > 0 && (
- <div class="wrap">
- <section class="section" id="featured">
- <div class="section-head">
- <div>
- <p class="eyebrow" style="--acc: var(--gold);">
- <span class="eyebrow__n">04</span>
- <span class="eyebrow__mark">^:</span>
- <span>Freshly modernized</span>
- <span class="eyebrow__rule" aria-hidden="true"></span>
- </p>
- <h2>Recently updated</h2>
- </div>
- <a class="btn btn--ghost" href={url('tags')}>All tags <Icon name="arrow" /></a>
- </div>
- <div class="index">
- {featured.map((entry, i) => {
- const cat = categoryOf(entry.data.category);
- return (
- <RecordRow
- href={url(sheetHref(entry))}
- n={String(i + 1).padStart(2, '0')}
- title={entry.data.title}
- kind={cat.tag}
- accent={cat.accent}
- meta={entry.data.updated ?? entry.data.difficulty}
- />
- );
- })}
- </div>
- </section>
</div>
- )}
+ </section>
+
+ <!-- 7 · Scope callout -->
+ <div class="archive" style="padding-top:80px;">
+ <Callout tone="warning" label="Scope">
+ Authorised testing, CTFs and education only. Know your scope, and get permission first,
+ before you touch a system.
+ </Callout>
+ </div>
+ <div style="height:96px"></div>
</Base>
+
+<script>
+ // Index filter pills — client-side by bucket, with a show-more cap. The
+ // Payloads / Internal pills are real links (they navigate); All / Sheets /
+ // Workflow filter the visible rows. Progressive: rows are all rendered, so
+ // the index is complete with JS off.
+ function initIndexFilters() {
+ const root = document.querySelector('[data-index]') as HTMLElement | null;
+ if (!root || root.dataset.bound) return;
+ root.dataset.bound = '1';
+ const CAP = 10;
+ const rows = [...root.querySelectorAll<HTMLElement>('.rec')];
+ const pills = [...root.querySelectorAll<HTMLButtonElement>('button[data-filter]')];
+ const shown = root.querySelector<HTMLElement>('[data-shown]');
+ const foot = root.querySelector<HTMLElement>('[data-foot]');
+ const more = root.querySelector<HTMLElement>('[data-more]');
+ const moreLabel = root.querySelector<HTMLElement>('[data-more-label]');
+ let filter = 'all', expanded = false;
+
+ const apply = () => {
+ const matching = rows.filter((r) => filter === 'all' || r.dataset.bucket === filter);
+ rows.forEach((r) => { r.hidden = true; });
+ const visible = expanded ? matching : matching.slice(0, CAP);
+ visible.forEach((r) => { r.hidden = false; });
+ if (shown) shown.textContent = `${visible.length} shown`;
+ if (foot) foot.textContent = `Showing ${visible.length} of ${matching.length} ${filter === 'all' ? 'entries' : filter}`;
+ if (more && moreLabel) {
+ const rem = matching.length - visible.length;
+ more.hidden = rem <= 0;
+ moreLabel.textContent = `Show ${Math.min(CAP, rem)} more — ${rem} remaining`;
+ }
+ pills.forEach((p) => p.setAttribute('aria-pressed', String(p.dataset.filter === filter)));
+ };
+
+ pills.forEach((p) => p.addEventListener('click', () => { filter = p.dataset.filter || 'all'; expanded = false; apply(); }));
+ more?.addEventListener('click', () => { expanded = true; apply(); });
+ apply();
+ }
+ document.addEventListener('astro:page-load', initIndexFilters);
+ if (document.readyState !== 'loading') initIndexFilters();
+ else document.addEventListener('DOMContentLoaded', initIndexFilters);
+</script>
diff --git a/src/styles/app.css b/src/styles/app.css
@@ -2,3 +2,4 @@
@import './chrome.css';
@import './global.css';
@import './prose.css';
+@import './daemon.css';
diff --git a/src/styles/daemon.css b/src/styles/daemon.css
@@ -0,0 +1,418 @@
+/* ============================================================================
+ daemon.css — the daemon-sec.xyz form language, ported to the cheatsheet.
+
+ The redesigned home route and the shared chrome (masthead, footer) are
+ re-compositions of the main site's own components: Masthead, SectionHeader,
+ RecordRow, Callout, Footer, the dual-lane marquee, the operator band, the
+ shelves grid and the mirrors band. Every value here is the main site's,
+ translated from its Tailwind classes into plain CSS against the tokens in
+ tokens.css. Nothing hardcodes a colour.
+
+ Measure is 1560px / 44px gutter (`--container-archive`). Paper sections use
+ the theme-relative tokens (--base/--text/--pine…) so they invert with the
+ mode; the dark bands (operator, mirrors, footer, the marquee cap) pin the
+ fixed --color-night-* / --color-signal-* palette and stay dark in both.
+ ========================================================================== */
+
+/* ---- Measure ------------------------------------------------------------- */
+.archive {
+ width: 100%;
+ max-width: var(--container-archive);
+ margin-inline: auto;
+ padding-inline: 44px;
+ box-sizing: border-box;
+}
+@media (max-width: 700px) {
+ .archive { padding-inline: clamp(18px, 5.4vw, 44px); }
+}
+
+/* ---- Shared timing + status animations ----------------------------------- */
+.dmn-tt {
+ transition:
+ background-color 240ms ease, color 200ms ease, border-color 220ms ease,
+ transform 220ms var(--ease-in), padding-left 280ms var(--ease-in), opacity 200ms ease;
+}
+.dmn-anim-pulse { animation: dmn-pulse 2.4s ease-in-out infinite; }
+.dmn-anim-ring { animation: dmn-ring-status 4.2s ease-out infinite; }
+@keyframes dmn-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.25; } }
+@keyframes dmn-ring-status {
+ 0% { transform: scale(1); opacity: 0.7; }
+ 70%,100% { transform: scale(2.6); opacity: 0; }
+}
+/* A hard terminal-cursor blink — a steps() square wave, not a fade. */
+.dmn-blink {
+ display: inline-block; width: 0.5em; height: 1.05em;
+ transform: translateY(0.12em); background: currentColor;
+ animation: dmn-cursor 1.1s steps(1, end) infinite;
+}
+@keyframes dmn-cursor { 0%,49% { opacity: 1; } 50%,100% { opacity: 0; } }
+@media (prefers-reduced-motion: reduce) {
+ .dmn-anim-pulse, .dmn-anim-ring, .dmn-blink { animation: none; }
+}
+
+/* A 7px status dot with a pulse and an expanding ring pushing out of it. */
+.dmn-dot { position: relative; display: block; width: 7px; height: 7px; }
+.dmn-dot > i, .dmn-dot > u {
+ position: absolute; inset: 0; border-radius: 50%; display: block;
+}
+.dmn-dot > i { background: var(--dot, var(--love)); }
+.dmn-dot > u { border: 1px solid var(--dot, var(--love)); }
+
+/* ============================================================================
+ 1 · MASTHEAD — sticky, wordmark chip, numbered mono nav, status + actions.
+ ========================================================================== */
+.mast {
+ position: sticky; top: 0; z-index: 60;
+ border-bottom: 1px solid color-mix(in srgb, var(--text) 22%, transparent);
+ background: color-mix(in srgb, var(--base) 92%, transparent);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+}
+.mast__row {
+ display: flex; align-items: center; justify-content: space-between;
+ gap: 24px; height: 62px;
+}
+.mast__brand { display: inline-flex; align-items: center; }
+.mast__chip {
+ display: inline-flex; align-items: center; gap: 7px;
+ background: var(--text); color: var(--base);
+ padding: 6px 9px 7px;
+ font-family: var(--font-wordmark); font-weight: 800; font-size: 15px;
+ line-height: 1; letter-spacing: -0.02em; font-stretch: 88%;
+}
+.mast__chip svg { height: 0.72em; width: auto; flex: none; transform: translateY(-0.045em); }
+
+.mast__nav {
+ display: none; align-items: center; gap: 22px;
+ font-family: var(--font-plex); font-size: 10.5px; letter-spacing: 0.18em; text-transform: uppercase;
+}
+@media (min-width: 1024px) { .mast__nav { display: flex; } }
+.mast__navitem { position: relative; display: flex; align-items: baseline; gap: 8px; padding: 6px 0; }
+.mast__navitem .n { font-size: 10px; letter-spacing: 0.1em; color: var(--acc, var(--pine)); }
+.mast__navitem .lbl { display: inline-block; color: var(--subtle); transition: color 200ms ease, transform 200ms var(--ease-in); }
+.mast__navitem:hover .lbl { color: var(--text); transform: translateY(-1px); }
+.mast__navitem[aria-current='page'] .lbl { color: var(--text); }
+.mast__navitem .rule {
+ position: absolute; left: 0; bottom: -2px; height: 1px; width: 100%;
+ background: var(--acc, var(--pine)); transform: scaleX(0); transform-origin: left;
+ transition: transform 260ms var(--ease-in);
+}
+.mast__navitem:hover .rule, .mast__navitem[aria-current='page'] .rule { transform: scaleX(1); }
+
+.mast__actions { display: flex; align-items: center; gap: 14px; }
+.mast__status {
+ display: none; align-items: center; gap: 9px;
+ font-family: var(--font-plex); font-size: 10.5px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--subtle);
+}
+@media (min-width: 1180px) { .mast__status { display: flex; } }
+.mast__icon {
+ display: inline-flex; align-items: center; gap: 7px; color: var(--subtle);
+ transition: color 200ms ease;
+}
+.mast__icon:hover { color: var(--text); }
+.mast__icon svg { width: 15px; height: 15px; }
+.mast__icon kbd {
+ font-family: var(--font-plex); font-size: 10px; letter-spacing: 0.06em;
+ border: 1px solid color-mix(in srgb, var(--text) 30%, transparent); padding: 1px 5px; color: var(--muted);
+}
+.mast__mode .icon-sun { display: inline-flex; }
+.mast__mode .icon-moon { display: none; }
+:root[data-theme='dark'] .mast__mode .icon-sun { display: none; }
+:root[data-theme='dark'] .mast__mode .icon-moon { display: inline-flex; }
+
+.mast__burger {
+ display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px;
+ width: 34px; height: 34px; margin-right: -8px;
+}
+@media (min-width: 1024px) { .mast__burger { display: none; } }
+.mast__burger i {
+ display: block; width: 20px; height: 1.5px; background: var(--text);
+ transition: opacity 200ms ease, transform 300ms var(--ease-in);
+}
+.mast__burger[aria-expanded='true'] i:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
+.mast__burger[aria-expanded='true'] i:nth-child(2) { opacity: 0; }
+.mast__burger[aria-expanded='true'] i:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }
+
+.mast__mobile { border-top: 1px solid color-mix(in srgb, var(--text) 14%, transparent); background: var(--base); }
+@media (min-width: 1024px) { .mast__mobile { display: none; } }
+.mast__mobile a {
+ display: flex; align-items: center; gap: 14px;
+ padding: 14px 44px; border-bottom: 1px solid color-mix(in srgb, var(--text) 14%, transparent);
+ font-family: var(--font-plex); font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--subtle);
+ transition: padding-left 260ms var(--ease-in), color 200ms ease;
+}
+.mast__mobile a:hover { padding-left: 50px; color: var(--text); }
+.mast__mobile a .n { font-size: 10px; color: var(--acc, var(--pine)); }
+.mast__mobile a .arrow { margin-left: auto; color: var(--acc, var(--pine)); }
+
+/* ============================================================================
+ 2 · SECTION HEADER — three columns: mono eyebrow, oversized head, intro.
+ ========================================================================== */
+.sec-head {
+ display: grid; gap: 40px; align-items: start;
+ grid-template-columns: minmax(0, 0.6fr) minmax(0, 1.6fr) minmax(0, 1fr);
+}
+@media (max-width: 1023px) { .sec-head { grid-template-columns: 1fr; gap: 16px; } }
+.sec-head__eyebrow {
+ padding-top: 10px; font-family: var(--font-plex); font-size: 10.5px;
+ letter-spacing: 0.18em; text-transform: uppercase; color: var(--tone, var(--pine));
+}
+.sec-head__title {
+ margin: 0; font-family: var(--font-archivo); font-weight: 800;
+ font-size: clamp(46px, 6vw, 92px); line-height: 0.86; letter-spacing: -0.04em;
+ font-stretch: 88%; text-transform: uppercase; color: var(--tone, var(--text)); text-wrap: balance;
+}
+.sec-head__intro {
+ margin: 2px 0 0; max-width: 36ch; font-family: var(--font-archivo);
+ font-size: 15px; line-height: 1.55; color: var(--intro, var(--subtle)); text-wrap: pretty;
+}
+
+/* Section wrapper — centred measure, top rhythm. */
+.dsection { margin-inline: auto; width: 100%; max-width: var(--container-archive); padding: 96px 44px 0; box-sizing: border-box; }
+@media (max-width: 700px) { .dsection { padding: 72px clamp(18px, 5.4vw, 44px) 0; } }
+
+/* ============================================================================
+ 3 · RECORD ROW — one line, paper wash on hover (titles stay ink, ≥4.5:1),
+ excerpt disclosure. Rule #6: paper wash, never the solid ink wipe.
+ ========================================================================== */
+.rec {
+ position: relative; display: block; color: var(--text);
+ border-bottom: 1px solid color-mix(in srgb, var(--text) 22%, transparent);
+ padding: 22px 28px;
+ background-image: linear-gradient(to right, var(--overlay) 50%, transparent 50%);
+ background-size: 200% 100%; background-position: 100% 0; background-repeat: no-repeat;
+ transition: background-position 260ms var(--ease-in);
+}
+.rec:hover, .rec:focus-visible { background-position: 0 0; }
+.rec__grid {
+ display: grid; align-items: center;
+ grid-template-columns: 34px minmax(0, 1fr) auto; gap: 6px 18px;
+}
+@media (min-width: 768px) {
+ .rec__grid { grid-template-columns: 46px 118px minmax(0, 1fr) 112px 20px; gap: 8px 18px; }
+}
+.rec__n { grid-column: 1; grid-row: 1; font-family: var(--font-plex); font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--iris); }
+.rec__kind {
+ grid-column: 2; grid-row: 2; display: flex; align-items: center; gap: 8px;
+ font-family: var(--font-plex); font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--acc, var(--foam));
+}
+.rec__kind svg { width: 11px; height: 11px; }
+@media (min-width: 768px) { .rec__kind { grid-column: 2; grid-row: 1; } }
+.rec__title { grid-column: 2; grid-row: 1; min-width: 0; font-family: var(--font-archivo); font-size: 17px; line-height: 1.3; font-weight: 600; }
+@media (min-width: 768px) { .rec__title { grid-column: 3; } }
+.rec__date { display: none; font-family: var(--font-plex); font-size: 10px; letter-spacing: 0.12em; color: var(--subtle); }
+@media (min-width: 768px) { .rec__date { display: block; grid-column: 4; grid-row: 1; } }
+.rec__arrow { grid-column: 3; grid-row: 1; justify-self: end; font-size: 11px; line-height: 1; color: var(--subtle); }
+.rec__arrow svg { width: 13px; height: 13px; }
+@media (min-width: 768px) { .rec__arrow { grid-column: 5; } }
+.rec__reveal {
+ grid-column: 1 / -1; grid-row: 3; display: grid; grid-template-rows: 0fr; opacity: 0;
+ transition: grid-template-rows 380ms var(--ease-in), opacity 240ms ease;
+}
+@media (min-width: 768px) { .rec__reveal { grid-column: 3 / 4; grid-row: 2; } }
+.rec__reveal > * { min-height: 0; overflow: hidden; }
+.rec:hover .rec__reveal, .rec:focus-within .rec__reveal { grid-template-rows: 1fr; opacity: 1; }
+.rec__reveal > * > * { display: block; }
+.rec__excerpt { display: block; max-width: 62ch; padding-top: 10px; font-family: var(--font-archivo); font-size: 13.5px; line-height: 1.5; color: var(--subtle); text-wrap: pretty; }
+.rec__tagline { display: block; padding-top: 8px; font-family: var(--font-plex); font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); }
+@media (hover: none) { .rec__reveal { grid-template-rows: 1fr; opacity: 1; } }
+@media (prefers-reduced-motion: reduce) { .rec, .rec__reveal { transition: none; } }
+
+/* ============================================================================
+ 4 · MANIFEST BAND — dual-lane counter-scrolling marquee + inverse cap.
+ ========================================================================== */
+@keyframes vaultLane { from { transform: translateX(0); } to { transform: translateX(-50%); } }
+@keyframes vaultLaneRev { from { transform: translateX(-50%); } to { transform: translateX(0); } }
+.vault-lanes {
+ display: grid; grid-template-columns: 1fr auto; overflow: hidden;
+ background: var(--base);
+ border-top: 1px solid var(--text); border-bottom: 1px solid var(--text);
+}
+.vault-lanes__body { display: grid; grid-template-rows: 56px 34px; overflow: hidden; }
+.vault-lane-wrap { position: relative; overflow: hidden; }
+.vault-lane-wrap--top { border-bottom: 1px solid color-mix(in srgb, var(--text) 16%, transparent); }
+.vault-lane { display: flex; align-items: center; height: 56px; width: max-content; animation: vaultLane 50s linear infinite; }
+.vault-lane-rev { display: flex; align-items: center; height: 34px; width: max-content; animation: vaultLaneRev 80s linear infinite; }
+.vault-lanes:hover .vault-lane, .vault-lanes:hover .vault-lane-rev { animation-play-state: paused; }
+.vault-title { display: flex; align-items: center; gap: 16px; height: 56px; padding: 0 34px; white-space: nowrap; }
+.vault-title .n { font-family: var(--font-plex); font-size: 12px; letter-spacing: 0.12em; padding: 3px 7px; background: var(--acc, var(--iris)); color: var(--base); }
+.vault-title .t { font-family: var(--font-archivo); font-weight: 800; font-size: 30px; letter-spacing: -0.02em; line-height: 1; text-transform: uppercase; color: var(--text); }
+.vault-meta { display: flex; align-items: center; gap: 12px; height: 34px; padding: 0 26px; white-space: nowrap; font-family: var(--font-plex); font-size: 12px; letter-spacing: 0.1em; text-transform: uppercase; }
+.vault-meta .tag { color: var(--acc, var(--iris)); letter-spacing: 0.14em; }
+.vault-meta .tx { color: var(--muted); }
+.vault-meta .sep { color: var(--muted); margin-left: 10px; }
+.vault-cap {
+ display: flex; flex-direction: column; justify-content: center; gap: 6px; padding: 0 26px; white-space: nowrap;
+ background: var(--color-night-base); color: var(--color-signal-meta);
+ font-family: var(--font-plex); font-size: 12px; letter-spacing: 0.14em; text-transform: uppercase;
+}
+.vault-cap .verified { display: flex; align-items: center; gap: 8px; color: var(--color-night-subtle); }
+.vault-cap .verified i { width: 7px; height: 7px; border-radius: 4px; background: var(--color-night-pine); display: inline-block; }
+@media (prefers-reduced-motion: reduce) { .vault-lane, .vault-lane-rev { animation: none; } }
+@media (max-width: 640px) { .vault-cap { display: none; } .vault-lanes { grid-template-columns: 1fr; } }
+
+/* ============================================================================
+ 5 · OPERATOR BAND — dark plate: grid texture, chips, terminal readout, rail.
+ ========================================================================== */
+.operator {
+ position: relative; isolation: isolate; overflow: hidden;
+ background: var(--color-night-base); color: var(--color-night-text); padding: 92px 0 84px;
+}
+.operator__grid {
+ position: absolute; inset: 0; pointer-events: none;
+ background-image:
+ linear-gradient(color-mix(in srgb, var(--color-night-text) 5%, transparent) 1px, transparent 1px),
+ linear-gradient(90deg, color-mix(in srgb, var(--color-night-text) 5%, transparent) 1px, transparent 1px);
+ background-size: 64px 64px;
+}
+.operator__inner { position: relative; z-index: 2; }
+.operator__cols { margin-top: 56px; display: grid; grid-template-columns: minmax(0, 1fr) minmax(320px, 420px); gap: 48px; align-items: center; }
+@media (max-width: 900px) { .operator__cols { grid-template-columns: 1fr; gap: 36px; } }
+.op-chips { display: flex; flex-wrap: wrap; gap: 10px; font-family: var(--font-plex); font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; }
+.op-chip { display: flex; align-items: center; gap: 8px; padding: 6px 11px; border: 1px solid color-mix(in srgb, var(--chip, var(--color-night-iris)) 55%, transparent); color: var(--chip, var(--color-night-iris)); }
+.op-chip .pip { width: 6px; height: 6px; border-radius: 3px; background: var(--chip, var(--color-night-iris)); }
+.op-readout { margin-top: 36px; max-width: 56ch; font-family: var(--font-plex); font-size: 12.5px; line-height: 1.6; }
+.op-readout__cmd { display: flex; align-items: center; gap: 8px; color: var(--color-night-muted); }
+.op-readout__cmd .u { color: var(--color-night-pine); }
+.op-readout__cmd .c { color: var(--color-night-text); }
+.op-readout dl { margin: 14px 0 0; display: grid; grid-template-columns: 5rem 1fr; gap: 7px 20px; border-top: 1px solid color-mix(in srgb, var(--color-night-text) 20%, transparent); padding-top: 14px; color: var(--color-night-subtle); }
+.op-readout dt { color: var(--color-night-muted); }
+.op-readout dd { margin: 0; }
+.op-readout .on { color: var(--color-night-gold); }
+.op-rail { border: 1px solid color-mix(in srgb, var(--color-night-text) 30%, transparent); background: var(--color-night-surface); padding: 22px 26px 26px; }
+.op-rail__eyebrow { font-family: var(--font-plex); font-size: 10.5px; letter-spacing: 0.2em; text-transform: uppercase; color: var(--color-night-pine); }
+.op-search { margin-top: 14px; display: flex; align-items: center; gap: 12px; width: 100%; border: 1px solid color-mix(in srgb, var(--color-night-text) 30%, transparent); padding: 12px 14px; font-family: var(--font-plex); text-align: left; }
+.op-search:hover { border-color: color-mix(in srgb, var(--color-night-text) 55%, transparent); }
+.op-search .slash { color: var(--color-night-foam); font-size: 13px; }
+.op-search .ph { flex: 1; color: var(--color-night-subtle); font-size: 13px; }
+.op-search kbd { font-size: 10px; letter-spacing: 0.1em; color: var(--color-night-muted); border: 1px solid color-mix(in srgb, var(--color-night-text) 30%, transparent); padding: 2px 6px; }
+.op-stats { margin: 18px 0 0; display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px 20px; border-top: 1px solid color-mix(in srgb, var(--color-night-text) 30%, transparent); padding-top: 16px; font-family: var(--font-plex); font-size: 11px; line-height: 1.6; letter-spacing: 0.14em; text-transform: uppercase; color: var(--color-night-text); }
+.op-stats dd { margin: 0; }
+.op-cta-list { margin-top: 18px; display: flex; flex-direction: column; gap: 10px; font-family: var(--font-plex); font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; }
+.op-cta { display: flex; align-items: center; gap: 16px; padding: 15px 20px; color: var(--color-night-base); background: var(--cta, var(--color-night-pine)); transition: background-color 220ms ease; }
+.op-cta .count { margin-left: auto; opacity: 0.7; letter-spacing: 0.14em; }
+.op-cta--pine:hover { background: var(--color-night-text); }
+.op-cta--text { background: var(--color-night-text); }
+.op-cta--text:hover { background: var(--color-night-pine); }
+.op-cta--love { background: var(--color-night-love); }
+.op-cta--love:hover { background: var(--color-night-text); }
+
+/* ============================================================================
+ 6 · INDEX — filter pills + record rows + show-more + footer meta.
+ ========================================================================== */
+.filters { margin-top: 62px; display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
+.pill {
+ cursor: pointer; border: 1px solid var(--text); padding: 13px 20px;
+ font-family: var(--font-plex); font-size: 10.5px; letter-spacing: 0.18em; text-transform: uppercase;
+ background: transparent; color: var(--text);
+ transition: transform 200ms var(--ease-in), background-color 200ms ease, color 200ms ease;
+}
+.pill:hover { transform: translateY(-3px); background: var(--text); color: var(--base); }
+.pill[aria-pressed='true'] { background: var(--text); color: var(--base); }
+.filters__count { margin-left: auto; font-family: var(--font-plex); font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--iris); }
+.index-list { margin-top: 26px; border-top: 1px solid var(--text); }
+.rec[hidden] { display: none; }
+.show-more {
+ display: flex; width: 100%; align-items: center; justify-content: space-between;
+ border: 0; border-bottom: 1px solid color-mix(in srgb, var(--text) 22%, transparent); background: transparent;
+ padding: 24px 28px; font-family: var(--font-plex); font-size: 10.5px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--text);
+ transition: background-color 220ms ease, color 200ms ease;
+}
+.show-more:hover { background: var(--text); color: var(--base); }
+.index-foot { display: flex; justify-content: space-between; padding: 26px 28px 0; font-family: var(--font-plex); font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--iris); }
+.index-foot a { color: var(--iris); }
+
+/* ============================================================================
+ 7 · SHELVES GRID — 4-col hairline grid of the taxonomy.
+ ========================================================================== */
+.shelves-grid {
+ margin-top: 62px; display: grid; grid-template-columns: repeat(4, 1fr);
+ border-top: 1px solid var(--text); border-left: 1px solid var(--text);
+}
+@media (max-width: 900px) { .shelves-grid { grid-template-columns: repeat(2, 1fr); } }
+@media (max-width: 520px) { .shelves-grid { grid-template-columns: 1fr; } }
+.shelf {
+ display: flex; flex-direction: column; justify-content: space-between; gap: 36px;
+ min-height: 150px; padding: 18px 20px; box-sizing: border-box; color: var(--text);
+ border-right: 1px solid var(--text); border-bottom: 1px solid var(--text);
+ transition: background-color 200ms ease;
+}
+.shelf:hover { background: var(--panel); }
+.shelf__top { display: flex; justify-content: space-between; font-family: var(--font-plex); font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--acc, var(--pine)); }
+.shelf__title { font-family: var(--font-archivo); font-weight: 900; font-size: 21px; line-height: 1.15; letter-spacing: -0.02em; font-stretch: 86%; text-transform: uppercase; text-wrap: pretty; }
+
+/* ============================================================================
+ 8 · MIRRORS BAND — signal plate: borrowed trees + upstream manifest panel.
+ ========================================================================== */
+.mirrors {
+ position: relative; margin-top: 100px; overflow: hidden;
+ background: var(--color-signal-plate); color: var(--color-night-text); padding: 88px 0;
+}
+.mirrors__scrim {
+ position: absolute; inset: 0; pointer-events: none;
+ background: linear-gradient(180deg,
+ color-mix(in srgb, var(--color-signal-plate) 93%, transparent) 0%,
+ color-mix(in srgb, var(--color-signal-plate) 62%, transparent) 34%,
+ color-mix(in srgb, var(--color-signal-plate) 58%, transparent) 66%,
+ color-mix(in srgb, var(--color-signal-plate) 90%, transparent) 100%);
+}
+.mirrors__inner { position: relative; display: grid; grid-template-columns: 1fr 1fr; gap: 56px 64px; }
+@media (max-width: 900px) { .mirrors__inner { grid-template-columns: 1fr; } }
+.mirrors__eyebrow { font-family: var(--font-plex); font-size: 10.5px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-signal-meta); }
+.mirrors__title { margin: 14px 0 0; font-family: var(--font-archivo); font-weight: 800; font-size: clamp(46px, 6vw, 92px); line-height: 0.86; letter-spacing: -0.04em; font-stretch: 88%; text-transform: uppercase; }
+.mirrors__body { margin: 20px 0 0; max-width: 46ch; font-family: var(--font-archivo); font-size: 15px; line-height: 1.6; color: var(--color-night-text); text-wrap: pretty; }
+.mirrors__dl { margin: 32px 0 0; display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px 40px; font-family: var(--font-plex); font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; }
+.mirrors__dl dt { color: var(--color-signal-meta); }
+.mirrors__dl dd { margin: 8px 0 0; color: var(--color-plate-ink); }
+.mirrors__chip { display: inline-flex; border: 1px solid color-mix(in srgb, var(--color-night-love) 55%, transparent); padding: 3px 8px; color: var(--color-night-love-lift); }
+.mirrors__cta { margin-top: 36px; display: inline-flex; align-items: center; gap: 12px; border: 1px solid var(--color-night-text); padding: 16px 24px; font-family: var(--font-plex); font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase; color: var(--color-night-text); transition: background-color 220ms ease, color 200ms ease; }
+.mirrors__cta:hover { background: var(--color-night-text); color: var(--color-signal-plate); }
+.mirror-panel { border: 1px solid color-mix(in srgb, var(--color-night-love) 34%, transparent); background: color-mix(in srgb, var(--color-signal-plate) 42%, transparent); }
+.mirror-panel__head { display: flex; justify-content: space-between; border-bottom: 1px solid color-mix(in srgb, var(--color-night-love) 28%, transparent); padding: 14px 20px; font-family: var(--font-plex); font-size: 10.5px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--color-night-love); }
+.mirror-panel__head .lic { color: var(--color-signal-dim); }
+.mirror-row { display: grid; grid-template-columns: 34px 1fr 90px; align-items: center; gap: 12px; border-bottom: 1px solid color-mix(in srgb, var(--color-night-love) 14%, transparent); padding: 14px 20px; font-family: var(--font-plex); font-size: 11.5px; }
+.mirror-row .n { color: var(--color-signal-meta); }
+.mirror-row .path { color: var(--color-night-text); }
+.mirror-row .count { color: var(--color-signal-dim); text-align: right; }
+
+/* ============================================================================
+ 9 · CALLOUT — flat plate, 2px accent rule, mono eyebrow.
+ ========================================================================== */
+.dcallout { border-left: 2px solid var(--tone, var(--pine)); background: var(--surface); padding: 18px 20px; }
+.dcallout__label { margin-bottom: 9px; display: flex; align-items: center; gap: 10px; font-family: var(--font-plex); font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--tone, var(--pine)); }
+.dcallout__label svg { width: 13px; height: 13px; }
+.dcallout__body { font-family: var(--font-archivo); font-size: 15px; line-height: 1.6; color: var(--text); text-wrap: pretty; }
+
+/* ============================================================================
+ 10 · HERO — the host the loop mounts into. The loop sets its own inner
+ styles inline (canvas ≥1280px, fluid flow below); this is the frame.
+ ========================================================================== */
+.hero-loop { position: relative; width: 100%; overflow: hidden; background: var(--base); min-height: 560px; }
+.hero-loop a { color: inherit; text-decoration: none; }
+
+/* ============================================================================
+ 11 · FOOTER — the page's closing dark band, on the signal plate.
+ ========================================================================== */
+.dfooter { position: relative; overflow: hidden; background: var(--color-signal-plate); color: var(--color-night-text); }
+.dfooter__scrim {
+ position: absolute; inset: 0; pointer-events: none;
+ background: linear-gradient(180deg,
+ color-mix(in srgb, var(--color-signal-plate) 92%, transparent) 0%,
+ color-mix(in srgb, var(--color-signal-plate) 55%, transparent) 30%,
+ color-mix(in srgb, var(--color-signal-plate) 50%, transparent) 62%,
+ color-mix(in srgb, var(--color-signal-plate) 86%, transparent) 100%);
+}
+.dfooter__inner { position: relative; padding-top: 56px; padding-bottom: 64px; }
+.dfooter__grid { display: grid; gap: 48px 44px; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
+.dfooter__wordmark { font-family: var(--font-wordmark); font-weight: 800; letter-spacing: -0.025em; font-stretch: 80%; line-height: 0.88; font-size: clamp(40px, 5vw, 74px); color: var(--color-plate-ink); white-space: nowrap; }
+.dfooter__wordmark .hy { color: var(--color-night-love); }
+.dfooter__motto { margin-top: 20px; max-width: 32ch; font-family: var(--font-archivo); font-size: 15px; line-height: 1.55; color: var(--color-night-text); text-wrap: pretty; }
+.dfooter__nav { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px 24px; align-self: start; font-family: var(--font-plex); font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; }
+.dfooter__nav a { display: flex; gap: 12px; color: var(--color-plate-ink); transition: padding-left 280ms var(--ease-in), color 200ms ease; }
+.dfooter__nav a:hover { padding-left: 6px; color: var(--color-night-foam); }
+.dfooter__nav a .n { color: var(--acc, var(--color-night-foam)); }
+.dfooter__meta { align-self: start; font-family: var(--font-plex); font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--color-signal-meta); }
+.dfooter__meta .live { display: flex; align-items: center; gap: 10px; color: var(--color-plate-ink); }
+.dfooter__meta .row { margin-top: 10px; }
+.dfooter__meta a { color: var(--color-signal-meta); }
+.dfooter__meta a:hover { color: var(--color-night-foam); }
diff --git a/src/styles/tokens.css b/src/styles/tokens.css
@@ -31,6 +31,12 @@
@font-face { font-family: 'EB Garamond'; font-style: normal; font-weight: 600; font-display: swap; src: url('../fonts/eb-garamond-600.woff2') format('woff2'); }
@font-face { font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url('../fonts/ibm-plex-mono-400.woff2') format('woff2'); }
@font-face { font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 600; font-display: swap; src: url('../fonts/ibm-plex-mono-600.woff2') format('woff2'); }
+/* DM Mono — the site mono, matching daemon-sec.xyz (labels, eyebrows, meta,
+ the redesigned homepage). Self-hosted latin subsets; no external request. */
+@font-face { font-family: 'DM Mono'; font-style: normal; font-weight: 300; font-display: swap; src: url('../fonts/dm-mono-300.woff2') format('woff2'); }
+@font-face { font-family: 'DM Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url('../fonts/dm-mono-400.woff2') format('woff2'); }
+@font-face { font-family: 'DM Mono'; font-style: italic; font-weight: 400; font-display: swap; src: url('../fonts/dm-mono-400-italic.woff2') format('woff2'); }
+@font-face { font-family: 'DM Mono'; font-style: normal; font-weight: 500; font-display: swap; src: url('../fonts/dm-mono-500.woff2') format('woff2'); }
/* Noto Sans Display — variable cut, the wordmark/display face: heavy,
compressed, uppercase. */
@font-face { font-family: 'Noto Sans Display'; font-style: normal; font-weight: 100 900; font-stretch: 62.5% 100%; font-display: swap; src: url('../fonts/noto-sans-display-var-latin.woff2') format('woff2'); }
@@ -338,3 +344,50 @@
--fuzz-fil-3: 144 122 169;
color: var(--fg);
}
+
+
+/* ============================================================================
+ DÆMON main-site palette — the fixed, absolute Rosé Pine token set the
+ redesigned homepage and shared chrome are authored against, extracted
+ verbatim from daemon-sec.xyz's compiled @theme. These do NOT flip with the
+ mode: `dawn` is always the cream family, `night` always the ink family,
+ `signal` the dark plate chrome. The page's *ground* follows the mode through
+ the theme-relative tokens above (--base / --text / --pine …); the tokens
+ below name the two poles the homepage inverts between — see the hero's P/Q
+ and .plate / .plate-dawn.
+ ========================================================================== */
+:root {
+ /* Paper (Rosé Pine Dawn) */
+ --color-dawn-base: #faf4ed; --color-dawn-surface: #fffaf3; --color-dawn-panel: #fdf8f1; --color-dawn-overlay: #f2e9e1;
+ --color-dawn-ink: #575279; --color-dawn-subtle: #635f7c; --color-dawn-muted: #6e6a86;
+ --color-dawn-pine: #017194; --color-dawn-foam: #00747b; --color-dawn-gold: #9b5800;
+ --color-dawn-love: #af416c; --color-dawn-iris: #795aa2; --color-dawn-rose: #b4423d;
+ --color-dawn-band: #4e3969; --color-dawn-band-ledger: #015a77;
+
+ /* Plates (Rosé Pine night) */
+ --color-night-base: #191724; --color-night-surface: #1f1d2e; --color-night-text: #e0def4;
+ --color-night-subtle: #908caa; --color-night-muted: #8b87a3;
+ --color-night-pine: #3e8fb0; --color-night-foam: #9ccfd8; --color-night-gold: #f6c177;
+ --color-night-love: #eb6f92; --color-night-love-lift: #ee85a2;
+ --color-night-iris: #c4a7e7; --color-night-rose: #ebbcba;
+
+ /* Signal / plate chrome */
+ --color-signal-plate: #171322; --color-signal-meta: #c9c3e0; --color-signal-dim: #9a91c4;
+ --color-signal-pine: #31a3c4; --color-plate-ink: #faf4ed;
+
+ /* Type — DM Mono is now the site mono, matching daemon-sec.xyz. `--font-mono`
+ is re-pointed here so every existing eyebrow/label picks it up too. */
+ --font-plex: 'DM Mono', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
+ --font-mono: 'DM Mono', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
+ --font-archivo: 'Archivo', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+
+ /* The redesigned homepage's measure — wider than --maxw for the band grids. */
+ --container-archive: 1560px;
+}
+
+/* Theme-relative surfaces the paper homepage sections use, so they invert with
+ the mode. --line is ink@16% and rides --text automatically. */
+:root, :root[data-theme='light'] { --panel: #fdf8f1; --line: color-mix(in srgb, var(--text) 16%, transparent); }
+:root[data-theme='dark'] { --panel: #1f1d2e; }
+.plate { --panel: #1f1d2e; }
+.plate-dawn { --panel: #fdf8f1; }