    /* ---- SELF-HOSTED FONTS (no third-party request, works offline) ----
       Space Grotesk = body / headings / nav (geometric, techy, very legible).
       IBM Plex Mono  = terminal chrome only: title bars, prompt lines, prices,
       buttons, the 404 heading. Both OFL, served from /fonts, cached one week
       by the /fonts rule in _headers. Space Grotesk ships as ONE variable file
       (300-700 axis); Plex Mono as two static weights. */
    @font-face {
      font-family: 'Space Grotesk';
      font-style: normal;
      font-weight: 300 700;
      font-display: swap;
      src: url('/fonts/SpaceGrotesk-Variable.woff2') format('woff2');
    }
    @font-face {
      font-family: 'IBM Plex Mono';
      font-style: normal;
      font-weight: 400;
      font-display: swap;
      src: url('/fonts/IBMPlexMono-Regular.woff2') format('woff2');
    }
    @font-face {
      font-family: 'IBM Plex Mono';
      font-style: normal;
      font-weight: 600;
      font-display: swap;
      src: url('/fonts/IBMPlexMono-SemiBold.woff2') format('woff2');
    }

    html {
      background-color: #000;
      background-image:
        repeating-linear-gradient(0deg, rgba(255,255,255,.028) 0 1px, rgba(0,0,0,0) 1px 4px),
        repeating-linear-gradient(90deg, rgba(255,255,255,.028) 0 1px, rgba(0,0,0,0) 1px 4px);
      image-rendering: pixelated;
    }
    body {
      margin: 0; color: #e6e6e6;
      font-family: 'Space Grotesk', system-ui, -apple-system, 'Segoe UI', sans-serif;
      font-size: 16px; font-weight: 400; line-height: 1.45; letter-spacing: 0;
      -webkit-font-smoothing: antialiased;
    }
    /* Terminal chrome runs on the mono face. Everything else inherits the
       body's Space Grotesk. Prices (.rbtext / .term-rendered) and buttons live
       here so numbers and CTAs read as terminal type, not body copy. */
    .tbar, .term, .buy, .btn88, .rbtext {
      font-family: 'IBM Plex Mono', 'Courier New', monospace;
    }
    a { color: #fff; text-decoration: underline; }
    a:hover { color: #000; background: #fff; }
    .px   { letter-spacing: .02em; }
    .term { font-weight: 400; }
    .dim { color: #9a9a9a; }
    .faint { color: #6a6a6a; }
    h1,h2,h3,h4 { margin: 0; font-weight: 500; letter-spacing: 0; }

    /* ---- RAINBOW EDGE ----
       Four strips, one per side. Lengths are real pixels measured along the
       perimeter, so a wide box and a tall box get identical rainbow runs.
       Two runs, half a perimeter apart, both starting from the top-left corner
       going clockwise — so every box on the site is in the same phase.
       Slides with scroll position. */
    .rbox { position: relative; padding: 3px; background: #000; border-radius: 0; }
    .rbox > .e { position: absolute; pointer-events: none; }
    .rbox > .et { top: 0; left: 0; height: 3px; }
    .rbox > .eb { bottom: 0; left: 0; height: 3px; }
    .rbox > .el { top: 0; left: 0; width: 3px; }
    .rbox > .er { top: 0; right: 0; width: 3px; }
    .rbox > .win { position: relative; z-index: 1; }

    /* OS window */
    .win { background: #000; border: 1px solid #3a3a3a; }
    .tbar {
      background: #262626; color: #d8d8d8; border-bottom: 1px solid #3a3a3a;
      padding: 5px 5px 5px 9px; display: flex; align-items: center; justify-content: space-between; gap: 8px;
      font-size: 12px; letter-spacing: .04em; font-weight: 600;
    }
    /* Long "NAME // LABEL" titles must not shove the window buttons off the
       bar or overflow on a phone — mono glyphs are wider than the old pixel
       font. Let the label truncate; the buttons keep their slot. */
    .tbar b { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .wbtn { display: flex; gap: 3px; }
    .wbtn i { width: 14px; height: 12px; border: 1px solid #5a5a5a; background: #3a3a3a;
      display: block; position: relative; }
    .wbtn i::before, .wbtn i::after { content: ""; position: absolute; background: #d8d8d8; }
    /* minimise */
    .wbtn i:nth-child(1)::after { left: 3px; right: 3px; bottom: 2px; height: 1px; }
    /* maximise */
    .wbtn i:nth-child(2)::after { left: 3px; right: 3px; top: 2px; bottom: 2px;
      background: none; border: 1px solid #d8d8d8; border-top-width: 2px; }
    /* close */
    .wbtn i:nth-child(3)::before, .wbtn i:nth-child(3)::after {
      left: 2px; right: 2px; top: 50%; height: 1px; margin-top: -1px; }
    .wbtn i:nth-child(3)::before { transform: rotate(45deg); }
    .wbtn i:nth-child(3)::after  { transform: rotate(-45deg); }
    /* a panel with a single button gets a close, not a minimise */
    .wbtn i:only-child::before, .wbtn i:only-child::after {
      left: 2px; right: 2px; top: 50%; bottom: auto; height: 1px; margin-top: -1px;
      background: #d8d8d8; border: 0; }
    .wbtn i:only-child::before { transform: rotate(45deg); }
    .wbtn i:only-child::after  { transform: rotate(-45deg); }
    .pad { padding: 14px 16px; }
    .inset { border: 1px solid #333; background: #060606; }
    /* SM-7: plugin index cards + home deck tiles click through to plugin detail
       (wired by render.js wireCardNav). Show the pointer so they read as links;
       drop it in edit mode, where clicking edits rather than navigates. */
    [data-render="plugins"] .inset[data-slug],
    [data-render="deck"] .inset[data-slug] { cursor: pointer; }
    body.editing [data-render="plugins"] .inset[data-slug],
    body.editing [data-render="deck"] .inset[data-slug] { cursor: default; }
    /* AG-10: these cards are keyboard-reachable now (deck tile = real <a>; plugins
       card = role="link" tabindex="0"). Show a clear focus ring so a keyboard user
       can see which card they're on — same cyan as the edit-mode focus outline. */
    [data-render="plugins"] .inset[data-slug]:focus-visible,
    [data-render="deck"] .inset[data-slug]:focus-visible {
      outline: 2px solid #2fd8ff; outline-offset: 2px; }

    hr.rule { border: 0; height: 5px; margin: 14px 0;
      background: repeating-linear-gradient(90deg, #4a4a4a 0 7px, #000 7px 14px); }
    .hazard { background: repeating-linear-gradient(135deg, #262626 0 9px, #000 9px 18px); }

    .btn88 { width: 88px; height: 31px; border: 1px solid #4a4a4a; background: #0a0a0a;
      display: flex; align-items: center; justify-content: center; font-size: 14px;
      color: #cfcfcf; text-align: center; }
    .buy { display: inline-block; border: 1px solid #fff; background: #fff; color: #000;
      padding: 8px 18px; font-size: 18px; letter-spacing: .05em; }
    .buy.alt { background: #000; color: #fff; }
    .buy.off { background: #0a0a0a; color: #6a6a6a; border-color: #3a3a3a; }

    .rbtext {
      display: inline-block;
      background: linear-gradient(90deg,#ff2d2d,#ff9a1f,#ffe83d,#4dff5a,#2fd8ff,#6a5cff,#ff4dd2);
      -webkit-background-clip: text; background-clip: text; color: transparent;
    }
    .rbline { height: 3px; background: linear-gradient(90deg,
      #000, #ff2d2d, #ff9a1f, #ffe83d, #4dff5a, #2fd8ff, #6a5cff, #ff4dd2, #000); }

    /* progress bars */
    .bar { height: 14px; border: 1px solid #3a3a3a; background: #060606; position: relative; }
    .bar > span { display: block; height: 100%;
      background: repeating-linear-gradient(90deg, #e8e8e8 0 4px, #9a9a9a 4px 7px); }
    .barrow { display: flex; align-items: center; gap: 10px; margin-bottom: 9px; }
    .barrow .nm { width: 120px; font-size: 18px; color: #fff; }
    .barrow .bar { flex-grow: 1; }
    .barrow .pc { width: 50px; text-align: right; color: #9a9a9a; font-size: 18px; }

    .logo { color: #fff; display: block; }
    .logo svg { display: block; width: 100%; height: auto; }

    /* Centre the whole page column in the viewport at every width above the
       breakpoint. The outer frame is the inline 1100px div; it sets width +
       padding inline but NOT margin, so auto side-margins apply with no
       !important. Ben's first look: the column hugged the left edge. Every
       page (index, plugins, plugin, merch) uses the same 1100px frame, so
       this one rule centres them all. */
    [style*="width:1100px"] { margin-left: auto; margin-right: auto; }

    /* ---- EDIT MODE (owner only) ---- */
    .editbar { display: none; position: fixed; left: 0; right: 0; bottom: 0; z-index: 99;
      background: #f0f0f0; color: #000; padding: 10px 16px; align-items: center; gap: 14px;
      border-top: 2px solid #000; font-size: 17px; }
    body.editing .editbar { display: flex; }
    body.editing [data-edit] { outline: 1px dashed #6a5cff; outline-offset: 2px; cursor: text; }
    body.editing [data-edit]:focus { outline: 2px solid #2fd8ff; background: #0d0d14; }
    body.editing .barrow input { display: block; }
    .barrow input { display: none; width: 100%; margin-top: 4px; accent-color: #fff; }
    .editbar .ebtn { border: 1px solid #000; background: #000; color: #fff; padding: 6px 14px; cursor: pointer; }
    .editflag { display: none; }
    body.editing .editflag { display: inline-block; color: #2fd8ff; }

    /* story 1-11 — array controls, unsaved-draft flag, draft badge (owner only,
       injected by edit.js only while editing, so a visitor never sees them) */
    .lw-actrl { position: absolute; top: 2px; right: 2px; z-index: 50;
      display: flex; gap: 2px; }
    /* SM-8: on cards whose .inset opens with a .tbar (plugin / merch / project
       cards), drop the ↑↓✕ controls below the bar so they don't stack on the
       decorative .wbtn window-icon cluster anchored top-right. The .tbar-topped
       .inset is ~24px to its bottom border; 26px clears it by the same 2px
       inset. Deck tiles have no .tbar so they keep top: 2px, unchanged. */
    .inset > .tbar ~ .lw-actrl { top: 26px; }
    .lw-actrl button { font: 13px/1 monospace; padding: 3px 6px; cursor: pointer;
      border: 1px solid #000; background: #f0f0f0; color: #000; }
    .lw-actrl button:hover { background: #2fd8ff; }
    /* SM-16: reorder lever for date-sorted containers (projects). The ↑↓ move
       buttons are a no-op there (the grid always re-sorts by date), so edit.js
       hides them and injects this month picker instead — sits top-left, clear of
       the top-right ↑↓✕ cluster. Owner-only, edit mode. */
    .lw-datectrl { position: absolute; top: 2px; left: 2px; z-index: 50;
      display: flex; align-items: center; gap: 4px; background: #f0f0f0;
      border: 1px solid #000; padding: 2px 4px; font: 11px/1 monospace; color: #000; }
    .inset > .tbar ~ .lw-datectrl { top: 26px; }
    .lw-datectrl input { font: 12px/1 monospace; border: 1px solid #888; padding: 1px 2px; }
    .lw-add { display: block; width: 100%; margin: 10px 0 0; padding: 8px;
      font: 15px/1 inherit; cursor: pointer; color: #000; background: #f0f0f0;
      border: 2px dashed #6a5cff; }
    .lw-add:hover { background: #2fd8ff; }
    .editbar .lw-flag { display: none; color: #c00; font-weight: bold; }
    body.lw-dirty .editbar .lw-flag { display: inline-block; }
    /* SM-18: calm "saved, now deploy" confirmation shown after a SAVE, replacing
       the red UNSAVED alarm so a save reads as taken. Dark green for contrast on
       the light editbar. */
    .editbar .lw-saved-flag { display: none; color: #0a7d3a; font-weight: bold; }
    body.lw-saved .editbar .lw-saved-flag { display: inline-block; }
    .lw-draft-badge { display: none; position: fixed; top: 8px; right: 8px; z-index: 99;
      background: #c00; color: #fff; padding: 6px 12px; font-size: 15px; cursor: pointer;
      border: 1px solid #000; }
    body.lw-dirty .lw-draft-badge { display: block; }
    body.editing .lw-draft-badge { display: none; }

    ::-webkit-scrollbar { width: 15px; }
    ::-webkit-scrollbar-track { background: #000; border: 1px solid #3a3a3a; }
    ::-webkit-scrollbar-thumb { background: #3a3a3a; border: 1px solid #5a5a5a; }

    /* ================================================================
       RESPONSIVE — one breakpoint (story 1-3)
       Ben's traffic is Instagram → phone. Above 720px NOTHING here
       applies; the desktop look is the mockup, untouched. These
       selectors match the mockup's own inline layout values, so every
       page built from that markup inherits mobile behaviour for free.
       ================================================================ */
    @media (max-width: 720px) {
      /* Fixed-width containers become fluid. max-width caps the inline
         px width without needing !important. Widths enumerated are the
         mockup's real column widths (frame, content, home split,
         detail-page split). */
      [style*="width:1100px"], [style*="width:1000px"],
      [style*="width:676px"],  [style*="width:600px"],
      [style*="width:384px"],  [style*="width:308px"],
      [style*="width:300px"] { max-width: 100%; }

      /* The page frame's inline padding is "22px 0 34px" (no side gutter),
         which lets content touch the phone's edge. Force a side gutter and
         border-box so the padding fits inside the fluid width. */
      [style*="width:1100px"] {
        box-sizing: border-box !important;
        padding-left: 14px !important;
        padding-right: 14px !important;
      }

      /* Two-column layouts stack. The mockup's splits are flex rows
         aligned to the top (home: 676/308; detail pages: 600/384).
         flex-direction is not set inline, so no !important needed. */
      [style*="align-items:flex-start"] { flex-direction: column; }

      /* Deck / card grids drop to 2-across. grid-template-columns IS
         set inline, so this override needs !important. */
      [style*="grid-template-columns:repeat(4"],
      [style*="grid-template-columns:repeat(3"] {
        grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
      }

      /* Nav / header rows wrap instead of overflowing the viewport. */
      nav, [style*="justify-content:space-between"] { flex-wrap: wrap; }

      /* Nothing exceeds the viewport width. border-box keeps a box's
         padding + border inside its (grid/flex) track so tiles don't bleed. */
      .win, .inset { box-sizing: border-box; max-width: 100%; }
      .logo { max-width: 100%; }
      img, svg { max-width: 100%; height: auto; }

      /* Flex children default to min-width:auto, which refuses to shrink below
         their content — that is what pushes the banner's fixed-width logo lockup
         and the nav's fixed cells past the phone edge. Let flex children shrink
         so the max-width caps above actually take effect, cap the one remaining
         fixed inline width (the 380px lockup), and let the nav row wrap instead
         of overflow. (Story 1-6; scoped to ≤720px, desktop is untouched.) */
      [style*="display:flex"] > * { min-width: 0; }
      [style*="width:380px"] { max-width: 100%; }
      .win[style*="display:flex"] { flex-wrap: wrap; }

      /* Home banner: the small dim sigil (56px, #4a4a4a) sat awkwardly under
         the tagline once the banner flex wrapped on a phone. The wordmark
         lockup alone carries the header at ≤720px, so hide the secondary
         sigil here — the wordmark already caps to the box via the rules
         above. (Ben's first look, item 2.) */
      .sigil-mark { display: none; }
    }

    /* ---- LEMON SQUEEZY overlay (moved from the hello page, story 1-3) ----
       Their checkout overlay defaults to a white backdrop with a purple
       pulse. Force a black backdrop with a white card to match the site. */
    .lemonsqueezy-loader { background: rgba(0,0,0,.92) !important; }
    .lemonsqueezy-loader main > div { background-color: #fff !important; }
