/* Astryke Hub.
   Chrome uses the Night/Gold/Paper brand palette, matching astryke.com. It used
   to be the white/blue one, which meant the chrome fought the cork board it
   wrapped; now they are the same warm family and the board belongs to the app
   instead of sitting in it.

   Contrast ratios were computed, not eyeballed, against --page #2B241F:
     --ink   Paper  #F7F1E8  13.60:1
     --body         #DCD2C4  ~10:1
     --muted        #A8998A   ~5.2:1   clears AA for small print
     --accent Gold  #D9B267   7.65:1
     #2B241F on --accent      7.65:1   button label, see .btn below

   Ember #C85037 is 3.39:1 here and is therefore NOT a text colour. It stays in
   the mark and in large display type only. */

:root {
    --page: #2B241F;
    --band: #241E1A;
    --sunk: #1B1714;
    --rule: #4A3F37;
    --ink: #F7F1E8;
    --body: #DCD2C4;
    --muted: #A8998A;
    --accent: #D9B267;
    --accent-hover: #E8C987;
    --accent-soft: #3A302A;
    --border: #6E5F53;
    --good: #7FB98A;
    --warn: #E0A94A;
    --bad: #E0705A;

    /* Two-tone. The page is Night; the content panel in the middle is paper,
       the same relationship the board has (light cork on a dark page) and the
       same one the demo video shows. A .card used to be background:var(--page)
       on var(--page), so only its 1px border separated it from the page and it
       effectively vanished.

       Measured on paper #F7F1E8:
         panel vs page          13.60:1   unmistakably a separate surface
         --panel-ink  #332a20   12.53:1
         --panel-mute #6b5c4c    5.74:1   clears AA for hints and small print
       The chrome's own --muted #A8998A is 2.46:1 here and must NOT be used
       inside a panel, which is why these exist as separate tokens. */
    --panel: #F7F1E8;
    --panel-ink: #332a20;
    --panel-mute: #6b5c4c;
    --panel-rule: #d9cdb8;
    --panel-field: #FFFDF7;

    /* CHIP INK. The small light chips - .ok, .err, .gap, .pill, .trialtag - are
       paper surfaces that happen to sit on the Night page, so their text has to
       come from the paper side of the palette too. They were left on
       --good/--warn/--bad/--body when the theme flipped, which is how a chip
       ended up with 2:1 text inside it. Measured on their own backgrounds:

         --ink-warn #8a5a1e on #fdf6e7   5.48:1   (was --warn, 1.96:1)
         --ink-good #1f6b40 on #f0f9f5   6.05:1   (was --good, 2.13:1)
         --ink-bad  #a32a1c on #fdf2f2   6.59:1   (was --bad,  2.89:1)
         --ink-body #4a3f33 on #fdf6e7   9.52:1   (was --body, 1.39:1)

       --good/--warn/--bad stay valid as BORDERS on the dark page, and as text
       ON the dark page. They are not valid as text inside a light chip. */
    --ink-warn: #8a5a1e;
    --ink-good: #1f6b40;
    --ink-bad:  #a32a1c;
    --ink-body: #4a3f33;

    /* Board palette, taken verbatim from board-demo.html - the mockup SD
       signed off on AND the file the demo video was recorded from. Sampling the
       poster gives cork #958877 and cards #F7F3E8, which is this palette.

       It was briefly darkened to #3A302A on 2026-08-01 to match the new Night
       chrome. That was wrong: it made the product stop matching the video a
       visitor watches thirty seconds earlier. The cork is a LIGHT surface
       sitting on a dark page, deliberately. Do not darken it without
       re-recording the demo first.

       (original note follows)
       Board palette, taken verbatim from board-demo.html - the mockup SD
       signed off on. Cork pulled off orange toward a warm neutral, and notes
       at roughly HALF the saturation of the first cork board: they should read
       as paper on a wall, not as highlighter. */
    --cork: #9d8f7e; --cork-dk: #8a7c6b; --cork-lt: #ab9d8c;
    --ink-2: #5f5347; --ink-3: #867a6c;
    --n-active: #f0e3ba; --n-active-2: #e7d7a6;
    --n-wait: #eed4ae;   --n-wait-2: #e4c294;
    --n-paused: #dee2e7; --n-paused-2: #ccd3da;
    --n-done: #d7e5cd;   --n-done-2: #c3d8b6;
    --card: #efe6d3; --card-2: #e5d9c1; --card-edge: #d3c5a9;
    --paper: #fbf7ee;
    --alert: #b0472f; --alert-dk: #8f3722; --good-b: #3f6f47;
    --pin: #a8493a; --pin-hi: #c9705f;
    --sh-1: 0 1px 2px rgba(48,34,20,.13);
    --sh-2: 0 1px 2px rgba(48,34,20,.13), 0 5px 12px rgba(48,34,20,.14);
    --sh-3: 0 2px 4px rgba(48,34,20,.14), 0 12px 24px rgba(48,34,20,.19);
    --board-ink: #332a20;
}

* { box-sizing: border-box; }

body {
    margin: 0;
    background: var(--page);
    color: var(--body);
    font: 16px/1.55 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); }
a:hover { color: var(--accent-hover); }

/* Focus ring: 3.4:1 against white, above the 3:1 non-text minimum. */
a:focus-visible, button:focus-visible, input:focus-visible {
    outline: none;
    box-shadow: 0 0 0 2px var(--page), 0 0 0 4px var(--accent);
    border-radius: 4px;
}

/* ---- chrome ---- */
/* The constellation field. z-index rather than a negative one: a -1 child of
   body would sit behind body's own background and vanish, so the sky takes
   z-index 0 and the content layers take 1. height:min(100%,760px) for the same
   reason as the site - taller and the slice scaling blows the figures off
   screen on a long page. */
.sky {
    position: absolute; left: 0; right: 0; top: 0;
    height: min(100%, 760px); overflow: hidden;
    z-index: 0; pointer-events: none;
}
.sky svg { display: block; width: 100%; height: 100%; }
body { position: relative; }
.top, .wrap, .foot { position: relative; z-index: 1; }

.top {
    display: flex; align-items: center; gap: 20px;
    /* Content centred at 1280 to match astryke.com, while the bar and its
       border still span the viewport. Padding rather than an inner wrapper
       because .top's children are direct flex items and adding a div would
       mean touching every page that renders the header. */
    padding: 14px max(26px, calc((100% - 1280px) / 2 + 32px));
    border-bottom: 1px solid var(--rule);
    background: var(--page);
}
/* The wordmark plus HUB. Was letter-spaced text and a link to "/", which is
   the page you are already on. */
.brand { display: inline-flex; align-items: center; gap: 9px; }
.brand img { display: block; height: 28px; width: auto; }
.brand span {
    color: var(--accent); font-weight: 700; font-size: 15px;
    letter-spacing: .18em; text-transform: uppercase;
}
.top nav { margin-left: auto; display: flex; gap: 20px; align-items: center; }
.top nav a { text-decoration: none; font-size: 14px; color: var(--body); }
.top nav a.on { color: var(--accent); font-weight: 600; }
.who { font-size: 13px; color: var(--muted); }

.wrap { max-width: 1344px; margin: 0 auto; padding: 32px 58px 64px; }
.narrow { max-width: 440px; }

h1 { font-size: 26px; color: var(--ink); margin: 0 0 6px; letter-spacing: -.01em; }
h2 { font-size: 17px; color: var(--ink); margin: 0 0 10px; }
.sub { color: var(--muted); margin: 0 0 26px; font-size: 15px; }

/* ---- forms ---- */
label { display: block; font-size: 13px; font-weight: 600; color: var(--ink); margin: 16px 0 6px; }
input[type=text], input[type=email], input[type=password] {
    width: 100%; padding: 10px 12px; font-size: 15px;
    border: 1px solid var(--border); border-radius: 6px; background: var(--page); color: var(--ink);
}
.hint { font-size: 12.5px; color: var(--muted); margin-top: 5px; }

.btn {
    display: inline-block; margin-top: 22px; padding: 11px 20px;
    background: var(--accent); color: #2B241F; border: 0; border-radius: 6px;
    font-size: 15px; font-weight: 600; cursor: pointer; text-decoration: none;
}
.btn:hover { background: var(--accent-hover); color: #2B241F; }
.btn.ghost { background: transparent; color: var(--accent); border: 1px solid var(--border); }

.card { border: 1px solid var(--panel-rule); border-radius: 10px; padding: 26px; background: var(--panel); color: var(--panel-ink); }

/* Everything inside a panel is on paper, so it needs the panel's own ink.
   Inheriting the chrome colours here is how you get 2.46:1 hint text. */
.card h1, .card h2, .card h3, .card b, .card strong { color: var(--panel-ink); }
.card label { color: var(--panel-ink); }
.card p, .card li, .card td, .card div { color: var(--panel-ink); }
.card .hint, .card .sub { color: var(--panel-mute); }
.card a { color: #8a5a1e; }
.card input[type=text], .card input[type=email], .card input[type=password],
.card textarea, .card select {
    background: var(--panel-field); border-color: #cbbda4; color: var(--panel-ink);
}
.card input::placeholder, .card textarea::placeholder { color: #9b8b76; }

/* A gold fill on paper is 1.78:1 - the button would have no edge at all, which
   WCAG 1.4.11 wants at 3:1. The Night border supplies it (13.60:1) without
   giving up the brand colour. */
.card .btn { border: 1px solid #2B241F; }
.card .btn.ghost { background: transparent; color: #6b4a12; border-color: #b09a7c; }

/* The recommended path. It used to be a pale blue tint set inline, left over
   from the white/blue chrome; on the dark theme those inline rules painted the
   card with --accent-soft (#3A302A) while its text stayed panel ink, which is
   dark on dark and was genuinely unreadable. It stays a PAPER panel and is
   marked instead by a gold edge. */
.card.featured {
    background: #FCF8F0;
    border-color: #d9b267;
    box-shadow: inset 3px 0 0 #d9b267;
}
.alt { text-align: center; margin-top: 22px; font-size: 14px; color: var(--muted); }

.err { background: #fdf2f2; border: 1px solid #f3c7c3; color: var(--ink-bad); padding: 11px 14px; border-radius: 6px; font-size: 14px; margin-bottom: 6px; }
.ok  { background: #f0f9f5; border: 1px solid #b7e0cd; color: var(--ink-good); padding: 11px 14px; border-radius: 6px; font-size: 14px; }

/* ---- stats ---- */
.stats { display: flex; gap: 26px; margin: 0 0 26px; padding: 16px 20px; background: var(--band); border-radius: 8px; }
.stat b { display: block; font-size: 21px; color: var(--ink); line-height: 1.2; }
.stat span { font-size: 12.5px; color: var(--muted); }

/* ---- an uncaptured session ----
   Warn-coloured, not error-coloured. Nothing is broken; a record is missing,
   and the honest thing is to say which one rather than leave a gap the board
   presents as continuity. */
.gap {
    background: #fdf6e7; border: 1px solid #ecd9ae; border-left: 3px solid var(--warn);
    border-radius: 8px; padding: 14px 18px; margin: 0 0 20px;
}
.gap b { display: block; color: var(--ink-warn); font-size: 14.5px; margin-bottom: 4px; }
.gap p { margin: 0 0 8px; font-size: 13.5px; color: var(--ink-body); }
.gap ul { margin: 0; padding-left: 18px; font-size: 13px; color: var(--ink-body); }
.gap li { margin: 3px 0; }
.gap .q { color: var(--panel-mute); }

/* ---- THE BOARD ----
   Ported from board-demo.html, the mockup. The old hub split its palette the
   same way and so do we: the BOARD is cork and paper; sign-in and the project
   editor keep the white/blue chrome. Neither has to compromise for the other. */
body.cork {
    color: var(--board-ink);
    min-height: 100vh;
    background:
      radial-gradient(circle at 20% 24%, rgba(255,255,255,.030) 0 1.5px, transparent 2.5px),
      radial-gradient(circle at 64% 70%, rgba(0,0,0,.028) 0 1.5px, transparent 2.5px),
      radial-gradient(circle at 86% 36%, rgba(255,255,255,.024) 0 1px, transparent 2px),
      linear-gradient(158deg, var(--cork-lt), var(--cork) 48%, var(--cork-dk));
    background-size: 41px 41px, 57px 57px, 33px 33px, 100% 100%;
}
.frame {
    max-width: 1380px; margin: 0 auto;
    padding: 16px 22px 34px;
    min-height: 100vh;
    box-shadow: inset 0 0 140px rgba(60,42,24,.16);
}

/* ---- the dark bar: brand, stats, and the way out ---- */
.bar {
    display: flex; flex-wrap: wrap; gap: 12px; align-items: center;
    background: linear-gradient(180deg, #3a3129, #2b241e);
    border: 1px solid #4b4038; border-radius: 9px;
    padding: 9px 16px; margin-bottom: 16px;
    box-shadow: var(--sh-2);
}
.bar .brandwrap { display: flex; align-items: center; gap: 12px; margin-right: 4px; }
.bar .mark img { display: block; height: 24px; width: auto; }
/* The wordmark on the board bar. Was letter-spaced text linking to "/", which
   is the board you are already looking at. */
.bar .mark {
    display: inline-flex; align-items: center;
    font-size: 11px; font-weight: 700; letter-spacing: .22em; text-transform: uppercase;
    color: #c9a76a; text-decoration: none;
}
.bar h1 {
    margin: 0; font-size: 14.5px; font-weight: 600; letter-spacing: .02em; color: #f2e9d9;
    white-space: nowrap;
}
.bar .stats {
    display: flex; gap: 0; margin-left: auto; flex-wrap: wrap;
    background: none; padding: 0; margin-bottom: 0; border-radius: 0;
}
.bar .stat { padding: 0 15px; border-left: 1px solid #4b4038; min-width: 76px; }
.bar .stat:first-child { border-left: 0; }
.bar .stat b {
    display: block; font-size: 17px; font-weight: 600; line-height: 1.15; color: #f0dcb4;
    font-variant-numeric: tabular-nums;
}
.bar .stat b.hot { color: #e08a72; }
.bar .stat span {
    display: block; font-size: 9px; letter-spacing: .13em; text-transform: uppercase;
    color: #94867a; margin-top: 1px;
}
.barbtn {
    cursor: pointer; font-family: inherit; font-size: 11.5px; font-weight: 500;
    padding: 7px 14px; border-radius: 6px; margin-left: 12px; text-decoration: none;
    background: #463b32; color: #efe3d0; border: 1px solid #5b4e43;
    transition: background .15s ease;
}
.barbtn:hover { background: #544740; color: #efe3d0; }

/* ---- section label ---- */
.sect {
    font-size: 9.5px; letter-spacing: .24em; text-transform: uppercase; font-weight: 700;
    color: #5a4936; text-shadow: 0 1px 0 rgba(255,255,255,.14);
    margin: 16px 0 9px; display: flex; align-items: center; gap: 12px;
}
.sect:first-of-type { margin-top: 0; }
.sect .count {
    background: rgba(70,54,34,.20); color: #4d3d2b; border-radius: 20px;
    padding: 1px 8px; font-size: 9px; letter-spacing: .08em;
}
.sect::after { content: ""; flex: 1; height: 1px; background: rgba(74,56,32,.22); }
.sect a { color: #4d3d2b; }

/* ---- index cards: what is waiting on the human ---- */
/* auto-FIT, not auto-fill. auto-fill keeps the empty 300px tracks it made room
   for, so a wall holding one card left it stranded in the first column with two
   thirds of the board blank to the right of it. auto-fit collapses the tracks
   nothing landed in, and the cards that exist share the width. */
.qwall { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 13px; }
.qcard {
    position: relative; padding: 14px 15px 12px;
    background: linear-gradient(172deg, #fdf9f4, #f7ede3);
    border: 1px solid #e0cfc0; border-left: 4px solid var(--alert);
    border-radius: 4px; box-shadow: var(--sh-2);
    transform: rotate(-.25deg);
    transition: transform .16s ease, box-shadow .16s ease;
}
.qcard:nth-child(even) { transform: rotate(.3deg); }
.qcard:hover { transform: rotate(0) translateY(-3px); box-shadow: var(--sh-3); }
.qcard .who {
    font-size: 9px; letter-spacing: .15em; text-transform: uppercase;
    color: var(--alert-dk); font-weight: 700; margin-bottom: 6px;
}
.qcard p { margin: 0 0 6px; font-size: 13.2px; line-height: 1.42; font-weight: 500; color: var(--board-ink); }
.qcard .ctx { margin: 0 0 4px; font-size: 11.5px; line-height: 1.42; color: var(--ink-2); }
.qcard ul { margin: 6px 0 0; padding-left: 17px; font-size: 11.5px; color: var(--ink-2); }
.qcard li { margin: 2px 0; }

/* ---- the unreported-session card ----
   A card that is a LIST, not a note. In the 300px column every row wrapped to
   three lines, so four sessions ate half a screen while two thirds of the board
   sat empty to the right. It spans the wall instead, and the tilt comes off:
   a full-width pinned note reads as a mistake, not as charm.

   It also drops the alert-red edge. It shared that edge with "Needs you",
   which says PROBLEM before a word of it is read, and most of what lands here
   is not a problem: a tab opened and closed again is the common case. The
   card's job is to say the board might be stale, not to raise an alarm, so it
   gets a quiet edge and lets its own words carry the weight. */
   #665a4a for the edge, measured: 6.41:1 against the card it borders and
   2.13:1 against the cork behind it, so it reads as a deliberate edge from
   both sides. --ink-3 was tried first and is 1.33:1 on cork, which is a stripe
   you cannot see. */
.qcard.gapcard {
    grid-column: 1 / -1;
    transform: none;
    border-left-color: #665a4a;
}
.qcard.gapcard:hover { transform: translateY(-2px); }
.qcard.gapcard .who { color: var(--ink-2); }

/* One grid for the whole list, so the columns line up down the card and each
   session is a single line. display:contents on the row lets the li's children
   BE the grid cells. */
.qcard .gaps {
    display: grid;
    grid-template-columns: max-content max-content max-content max-content 1fr;
    gap: 4px 18px; align-items: baseline;
    margin: 8px 0 0; padding: 0; list-style: none;
    font-size: 11.8px; color: var(--ink-2);
}
.qcard .gaps > li { display: contents; }
.qcard .gaps b { color: var(--board-ink); font-size: 12.4px; }
/* NOT --ink-3, which is 4.00:1 on this card and fails AA at 11.8px. #6f6252 is
   5.65:1 and still clearly secondary to the project name beside it: the
   hierarchy here comes from weight and size, not from making text too faint to
   read. */
.qcard .gaps .det { color: #6f6252; }
@media (max-width: 720px) {
    /* Grid columns cannot survive a phone. Back to stacked rows. */
    .qcard .gaps { display: block; }
    .qcard .gaps > li { display: block; margin: 0 0 7px; }
    .qcard .gaps .det { margin-right: 8px; }
}

/* Footnotes left, the off switch right, on the same line. */
.qcard .gapfoot {
    display: flex; align-items: flex-end; justify-content: space-between;
    gap: 20px; margin-top: 12px; flex-wrap: wrap;
}
.qcard .gapfoot .ctx { margin: 0 0 2px; }
.qcard .gapdismiss { flex: 0 0 auto; }

/* The client's session id on an unreported-session row. Monospace and set in a
   chip because it is an identifier to be matched against something else, not
   prose: it is read character by character, or copied. */
.qcard .sid {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 10.5px; letter-spacing: .02em; white-space: nowrap;
    padding: 1px 5px; border-radius: 3px;
    background: rgba(70,54,34,.09); color: var(--ink-2);
}

/* ---- answer form on an index card ---- */
.qform { display: flex; gap: 7px; align-items: flex-end; margin-top: 8px; }
.qform textarea {
    flex: 1; padding: 7px 9px; font: inherit; font-size: 12.5px; line-height: 1.4;
    border: 1px solid #ddcbbc; border-radius: 5px; background: #fffdfa;
    color: var(--board-ink); resize: vertical;
}
.qform textarea:focus { outline: none; border-color: var(--alert); }
.qbtn {
    cursor: pointer; font-family: inherit; font-size: 11px; font-weight: 500;
    padding: 7px 11px; border-radius: 5px; white-space: nowrap;
    border: 1px solid #cdbcae; background: #fbf6f0; color: #5b4a3c;
    transition: background .15s ease;
}
.qbtn:hover { background: #f1e7dc; }
.qbtn.primary { background: var(--alert); border-color: var(--alert-dk); color: #fff; font-weight: 600; }
.qbtn.primary:hover { background: var(--alert-dk); }

/* ---- capture pad ---- */
.padwrap { display: grid; grid-template-columns: minmax(300px, 1.05fr) minmax(260px, 1fr); gap: 14px; align-items: start; }
/* The pinned column is only rendered when something is pinned, so on a board
   with nothing pinned the pad was holding open a second column for content
   that did not exist and sitting at half width for no reason. */
.padwrap .pad:only-child { grid-column: 1 / -1; }
.pad {
    position: relative; padding: 15px 16px 12px;
    background:
      repeating-linear-gradient(180deg, transparent 0 25px, rgba(92,120,158,.10) 25px 26px),
      linear-gradient(172deg, var(--paper), #f5efe2);
    border: 1px solid #ded2ba; border-radius: 5px;
    box-shadow: var(--sh-2); transform: rotate(-.2deg);
}
.pad h4 {
    margin: 0 0 9px; font-size: 10px; letter-spacing: .18em; text-transform: uppercase;
    color: #8a6f52; font-weight: 700;
}
.pad textarea {
    width: 100%; padding: 6px 8px; font: inherit; font-size: 13px; line-height: 1.45;
    border: 1px solid transparent; border-radius: 4px; background: transparent;
    color: var(--board-ink); resize: vertical;
}
.pad textarea:focus { outline: none; border-color: #d6c8ae; background: #fffdf7; }
.pad textarea::placeholder { color: #a89b86; }
.padrow { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.padbtn {
    cursor: pointer; font-family: inherit; font-size: 11.5px; font-weight: 600;
    padding: 6px 14px; border-radius: 5px;
    background: linear-gradient(180deg, #e8c979, #d9b45c);
    border: 1px solid #c19f47; color: #4a3a1c;
}
.padbtn:hover { filter: brightness(1.05); }
.padhint { font-size: 10.5px; color: var(--ink-3); }

.pinned { display: flex; flex-direction: column; gap: 9px; }
.pnote {
    display: flex; gap: 10px; align-items: flex-start;
    padding: 10px 12px; border-radius: 4px;
    background: linear-gradient(172deg, #fdf9f4, #f6efe4);
    border: 1px solid #e2d5c4; box-shadow: var(--sh-1);
}
.pnote p { margin: 0; flex: 1; font-size: 12.5px; line-height: 1.42; color: var(--ink-2); }
.pnote .linkbtn { color: var(--ink-3); white-space: nowrap; }
.pnote .linkbtn:hover { color: var(--alert-dk); }

@media (max-width: 720px) {
    .padwrap { grid-template-columns: 1fr; }
}

/* ---- projects: sticky notes ---- */
.wall { display: grid; grid-template-columns: repeat(auto-fill, minmax(198px, 1fr)); gap: 13px; }
.note {
    /* Top padding clears the pin. The pin occupies 8px to 20px, so a 17px top
       padding put the title two pixels INTO it. Widening the padding rather
       than nudging the pin upward keeps the pin visually pushed through the
       paper instead of perched on its edge. */
    position: relative; display: block; padding: 27px 13px 24px;
    cursor: pointer; border: 0; text-align: left; font-family: inherit;
    color: var(--board-ink); text-decoration: none;
    background: linear-gradient(166deg, var(--n-active), var(--n-active-2));
    box-shadow: var(--sh-2);
    clip-path: polygon(0 0, 100% 0, 100% 92%, 92% 100%, 0 100%);
    transition: transform .16s ease, box-shadow .16s ease;
    transform: rotate(-.25deg);
}
.note:nth-child(3n+2) { transform: rotate(.28deg); }
.note:nth-child(3n+3) { transform: rotate(-.12deg); }
.note.s-waiting   { background: linear-gradient(166deg, var(--n-wait), var(--n-wait-2)); }
.note.s-paused    { background: linear-gradient(166deg, var(--n-paused), var(--n-paused-2)); }
.note.s-completed { background: linear-gradient(166deg, var(--n-done), var(--n-done-2)); }
.note.s-archived  { background: linear-gradient(166deg, #ddd8cf, #cbc5ba); }
.note:hover { transform: rotate(0) translateY(-3px); box-shadow: var(--sh-3); z-index: 5; color: var(--board-ink); }
.note:focus-visible { outline: 2px solid #2b241e; outline-offset: 3px; }
/* The pin sat at top:-6px, i.e. six pixels ABOVE the note's own box, while
   .note carries a clip-path for the folded corner. A clip-path clips
   everything outside the polygon, and the polygon starts at 0 0, so the top
   half of every pin on the board was cut off. It read as a rendering fault on
   every card at once, which is exactly the kind of thing the eye stops
   reporting after a while.

   It sits ON the note now, which is also what a real pushpin does: it goes
   through the paper, it does not hover above it. */
.pin {
    position: absolute; top: 8px; left: 50%; margin-left: -6px;
    width: 12px; height: 12px; border-radius: 50%;
    background: radial-gradient(circle at 36% 32%, var(--pin-hi), var(--pin) 62%, var(--pin-dk));
    box-shadow: 0 2px 4px rgba(48,34,20,.34), 0 0 0 1px rgba(48,34,20,.10);
}

/* A board of identical red pins looks printed. Real ones come out of the tin
   in whatever colour. The class is assigned from a hash of the project name in
   index.php, so a given project keeps its colour across every render rather
   than flickering on each page load. */
.pin.p1 { --pin: #a8493a; --pin-hi: #c9705f; --pin-dk: #7d3226; }
.pin.p2 { --pin: #3a5f8a; --pin-hi: #6f93bd; --pin-dk: #27415f; }
.pin.p3 { --pin: #4a7a4e; --pin-hi: #7aab7e; --pin-dk: #2f5433; }
.pin.p4 { --pin: #b07d2a; --pin-hi: #dcae57; --pin-dk: #7d571a; }
.pin.p5 { --pin: #6b4a7a; --pin-hi: #9b76a9; --pin-dk: #4a3256; }
.pin.p6 { --pin: #2f7370; --pin-hi: #5da4a0; --pin-dk: #1e504e; }
.note h3 { margin: 0 0 4px; font-size: 13.8px; line-height: 1.24; font-weight: 650; letter-spacing: -.005em; color: var(--board-ink); }
.note .meta {
    font-size: 9px; letter-spacing: .1em; text-transform: uppercase;
    color: var(--ink-2); opacity: .8; margin-bottom: 7px; font-weight: 600;
}
.note .next {
    font-size: 11.8px; line-height: 1.42; color: var(--ink-2);
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.flags { position: absolute; right: 9px; bottom: 8px; display: flex; gap: 4px; }
.flag {
    background: var(--alert); color: #fff; font-size: 9px; font-weight: 700;
    padding: 2px 7px; border-radius: 10px; letter-spacing: .03em;
}
.flag.hand { background: var(--good-b); }
/* active but quiet for a week - a spine down the left edge */
.stale {
    position: absolute; left: 0; top: 0; width: 4px; height: 100%;
    background: linear-gradient(180deg, #c25640, #96341f);
}

.empty { grid-column: 1 / -1; text-align: center; color: var(--paper); padding: 40px 20px; }
.empty p { opacity: .9; }

@media (max-width: 720px) {
    .frame { padding: 12px 13px 28px; }
    .note { padding: 25px 13px 22px; transform: none; }
    .note:nth-child(3n+2), .note:nth-child(3n+3) { transform: none; }
    .note .next { -webkit-line-clamp: 2; }
    .bar .stats { margin-left: 0; }
}

/* ---- connect page ---- */
pre.code {
    background: var(--sunk); border: 1px solid var(--rule); border-radius: 8px;
    padding: 15px 17px; overflow-x: auto; font-size: 13px; line-height: 1.6;
    color: var(--ink); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
/* A fold-away section on the Night page (connect.php hides manual tokens in one).
   Two things it must not do, both of which it did:

   1. Inherit paper ink. The summary was inline-styled color:var(--panel-ink),
      #332a20 on #2B241F - 1.09:1, invisible. It reads as a missing heading, not
      as a control, so nobody finds what is folded inside it.
   2. Ship the browser's own disclosure triangle, which matches nothing else
      here. It is suppressed and the caret drawn, same as .selectwrap. */
.disclosure { margin-top: 34px; border-top: 1px solid var(--rule); padding-top: 20px; }
.disclosure > summary {
    display: inline-flex; align-items: center; gap: 10px;
    list-style: none; cursor: pointer;
    font-size: 14.5px; font-weight: 600; color: var(--ink);
    padding: 3px 2px; border-radius: 5px;
}
.disclosure > summary::-webkit-details-marker { display: none; }
.disclosure > summary::before {
    content: ""; flex: 0 0 auto; width: 7px; height: 7px;
    border-right: 2px solid var(--accent); border-bottom: 2px solid var(--accent);
    transform: translateY(-2px) rotate(-45deg);
    transition: transform .15s ease;
}
.disclosure[open] > summary::before { transform: translateY(-2px) rotate(45deg); }
.disclosure > summary:hover { color: var(--accent); }
.disclosure > summary:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--page), 0 0 0 4px var(--accent); }
/* Not .n - that already means the numbered circle in .step. */
.disclosure .count { color: var(--muted); font-weight: 500; }

/* ---- connected agents ----
   One row per agent that has actually called in. A grid rather than a list so
   the three facts line up in columns and the eye runs down them: what it is,
   what it last touched, when we last heard from it. */
.agents { display: grid; gap: 1px; background: var(--rule); border: 1px solid var(--rule); border-radius: 8px; overflow: hidden; margin: 0 0 8px; }
.agent {
    display: grid; grid-template-columns: 22px minmax(180px, 1.1fr) minmax(160px, 1.2fr) max-content;
    gap: 14px; align-items: baseline;
    padding: 11px 15px; background: var(--band); font-size: 13.5px; color: var(--body);
}
.agent .dot {
    width: 8px; height: 8px; border-radius: 50%; align-self: center;
    background: var(--muted); box-shadow: 0 0 0 3px rgba(168,153,138,.14);
}
.agent.on .dot { background: var(--good); box-shadow: 0 0 0 3px rgba(127,185,138,.2); }
.agent .what b { color: var(--ink); font-weight: 600; }
.agent .ver, .agent .as { color: var(--muted); font-size: 12px; margin-left: 5px; }
/* More than one key behind one tool. Quiet, but present: hiding it would make
   the collapsed row a lie by omission. */
.agent .keys {
    margin-left: 6px; font-size: 11px; color: var(--muted);
    border: 1px solid var(--rule); border-radius: 999px; padding: 1px 7px;
}
.agent .proj { color: var(--body); }
.agent .proj b { color: var(--ink); font-weight: 600; }
.agent .proj .none { color: var(--muted); font-style: normal; }
.agent .seen { color: var(--muted); font-size: 12.5px; text-align: right; white-space: nowrap; }
.agent.on .seen { color: var(--good); font-weight: 600; }
@media (max-width: 720px) {
    .agent { grid-template-columns: 22px 1fr; row-gap: 4px; }
    .agent .proj, .agent .seen { grid-column: 2; text-align: left; }
}

.step { display: flex; gap: 14px; margin: 26px 0; }
.step .n {
    flex: 0 0 27px; height: 27px; border-radius: 50%; background: var(--accent); color: #2B241F;
    display: grid; place-items: center; font-size: 13px; font-weight: 700;
}
.step .body { flex: 1; }
.step h2 { margin: 2px 0 8px; }

.pill { display: inline-block; padding: 3px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; }
.pill.waiting { background: #fdf6e7; color: var(--ink-warn); border: 1px solid #ecd9ae; }
.pill.live { background: #f0f9f5; color: var(--ink-good); border: 1px solid #b7e0cd; }

@media (max-width: 620px) {
    .wrap { padding: 22px 16px 48px; }
    .stats { flex-wrap: wrap; gap: 16px; }
    .board { padding: 16px; gap: 14px; }
}

/* ---- copyable command blocks ---- */
.cmd { position: relative; margin: 10px 0; }
.cmd pre.code { margin: 0; padding-right: 92px; white-space: pre-wrap; word-break: break-all; }
.cmd button {
    position: absolute; top: 9px; right: 9px;
    background: var(--page); color: var(--accent);
    border: 1px solid var(--border); border-radius: 5px;
    padding: 5px 11px; font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.cmd button:hover { background: var(--accent-soft); }
.cmd button.copied { background: var(--good); border-color: var(--good); color: #fff; }

.ready { border-left: 3px solid var(--good); padding-left: 14px; margin: 4px 0 18px; }
.notready { border-left: 3px solid var(--rule); padding-left: 14px; color: var(--muted); font-size: 14px; }

/* ---- edit page ---- */
/* login and register already use .narrow at 440px; an edit FORM at that width
   is a column of cramped textareas, so it gets its own class rather than
   widening the sign-in pages as a side effect. */
.wrap.form { max-width: 720px; }
.crumb { margin: 0 0 14px; font-size: 13.5px; }
.crumb a { text-decoration: none; }
textarea {
    width: 100%; padding: 10px 12px; font: inherit; font-size: 14.5px; line-height: 1.5;
    border: 1px solid var(--border); border-radius: 6px; background: var(--page); color: var(--ink);
    resize: vertical;
}

/* A bare <select> renders as an OS widget that matches nothing else on the
   page. Appearance is stripped and the arrow drawn, so it belongs here. */
.selectwrap { position: relative; }
.selectwrap::after {
    content: ""; position: absolute; right: 14px; top: 50%; width: 8px; height: 8px;
    border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
    transform: translateY(-70%) rotate(45deg); pointer-events: none;
}
.selectwrap select {
    -webkit-appearance: none; -moz-appearance: none; appearance: none;
    width: 100%; padding: 10px 36px 10px 12px; font: inherit; font-size: 14.5px;
    border: 1px solid var(--border); border-radius: 6px; background: var(--page); color: var(--ink);
}

.actions { display: flex; gap: 10px; align-items: center; margin-top: 22px; }
.actions .btn { text-decoration: none; }
.card.danger { margin-top: 22px; border-color: #e6d6b8; background: #fdfaf3; }
.card.danger h2 { color: var(--ink-bad); }
.meta-line { margin: 18px 0 0; font-size: 13px; color: var(--muted); }

/* ---- themed confirm ---- */
.modal {
    position: fixed; inset: 0; background: rgba(15,23,42,.45);
    display: grid; place-items: center; padding: 20px; z-index: 50;
}
.modal[hidden] { display: none; }
.modal-box {
    background: var(--page); border-radius: 12px; padding: 26px; max-width: 440px; width: 100%;
    box-shadow: 0 18px 44px rgba(15,23,42,.28);
}
.modal-box h2 { margin: 0 0 8px; }
.modal-box p { margin: 0 0 4px; font-size: 14.5px; color: var(--body); line-height: 1.55; }

/* A destructive action inside a table row. Rendered as a quiet link rather than
   a button so a row of them does not read as the point of the page, but it is a
   real <button> so it is keyboard-reachable and announced as an action. */
.linkbtn {
    background: none; border: 0; padding: 4px 2px; cursor: pointer;
    font: inherit; font-size: 13px; color: var(--muted); text-decoration: underline;
}
.linkbtn:hover { color: var(--bad); }
.btn.danger { background: var(--bad); }
.btn.danger:hover { background: #96271d; }

/* An answered thought. The reply is the payload, so it gets the emphasis and
   the original question drops back to a label above it. */
.pnote.answered { border-left: 3px solid var(--good-b); background: linear-gradient(172deg, #f7faf5, #eef4ea); }
.pnote.answered p { font-size: 11.5px; color: var(--ink-3); margin-bottom: 5px; }
.pnote .reply { font-size: 13px; line-height: 1.48; color: var(--board-ink); }
.pnote .replymeta { margin-top: 6px; font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); }

/* One block per agent on the connect page. Nobody uses four of these - they
   scan for the one they have, so each needs its own heading and edge. */
.client { margin: 18px 0; padding-left: 14px; border-left: 3px solid var(--rule); }
.client h3 { margin: 0 0 8px; font-size: 14.5px; color: var(--ink); }
.client:hover { border-left-color: var(--accent); }

/* ---- legal documents ---- */
/* A measure narrower than the edit form. These pages are read, not scanned,
   and 720px of 15px prose is already past the width the eye tracks well. */
.wrap.doc { max-width: 680px; }
.doc .updated { font-size: 13px; color: var(--muted); margin: 0 0 26px; }
.doc h2 { margin: 32px 0 4px; font-size: 15.5px; color: var(--accent); }
.doc p  { margin: 10px 0; font-size: 15px; line-height: 1.72; color: var(--body); }
.doc .lead { font-size: 16.5px; color: var(--ink); }
.doc ul { margin: 10px 0; padding-left: 20px; }
.doc li { margin: 6px 0; font-size: 15px; line-height: 1.66; color: var(--body); }

/* ---- site footer ---- */
/* Not on the board. That page is full-bleed cork on purpose and a rule across
   the bottom of it reads as a mistake; every other page carries these links,
   including the two a stranger actually reads before signing up. */
.foot {
    border-top: 1px solid var(--rule);
    margin: 56px auto 0; padding: 18px 26px 30px;
    max-width: 1280px; text-align: center;
    font-size: 13px; color: var(--muted);
}
.foot a { color: var(--muted); text-decoration: none; margin: 0 10px; }
.foot a:hover { color: var(--accent); text-decoration: underline; }

/* ---- billing ---- */
.planline { font-size: 15.5px; color: var(--ink); margin: 0 0 4px; }
.planline strong { font-weight: 700; }
form.inline { display: inline; }
form.inline .btn { margin-top: 18px; }

/* Two options side by side, stacking on a narrow screen. Never a <select>:
   picking a price is the decision on this page, and it should be visible. */
.prices { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 16px; }
.price {
    flex: 1 1 200px; padding: 18px; text-align: center;
    border: 1px solid var(--border); border-radius: 10px; background: var(--page);
}
.price:hover { border-color: var(--accent); }
.price .amount { font-size: 30px; font-weight: 700; color: var(--ink); letter-spacing: -.02em; }
.price .amount span { font-size: 14px; font-weight: 500; color: var(--muted); letter-spacing: 0; }
.price .every { font-size: 12.5px; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; margin-top: 2px; }
.price .btn { width: 100%; margin-top: 14px; }
.price .btn:disabled { opacity: .6; cursor: default; }

/* The trial countdown in the header. Quiet until it is nearly relevant, then
   it is the only colored thing in the chrome. */
.trialtag {
    font-size: 12.5px; font-weight: 600; text-decoration: none;
    padding: 4px 10px; border-radius: 999px;
    color: var(--ink-warn); background: #fdf6e7; border: 1px solid #ecd9ab;
}
.trialtag.out { color: var(--ink-bad); background: #fdf2f2; border-color: #f3c7c3; }
.trialtag:hover { border-color: currentColor; }

/* The account link is the signed-in email in the chrome. It looked like plain
   text and read as a label, so nobody would have tried clicking it. */
a.who { text-decoration: none; }
a.who:hover { color: var(--accent); }
a.who.on { color: var(--accent); font-weight: 600; }

/* A bar button that needs noticing. Same shape as the others so the bar keeps
   its rhythm, warmed up so it reads as a state and not a fifth menu item. */
.barbtn.warn { background: #6b4a12; border-color: #8a6320; color: #f6e3bd; }
.barbtn.warn:hover { background: #7d5716; color: #fff; }

/* "optional" beside a label. Small enough not to compete with the label, clear
   enough that nobody hunts for what to type in a field they can skip. */
.opt { font-weight: 500; font-size: 11.5px; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; margin-left: 6px; }

/* ---- first-run progress ----
   On the board, above everything, until all four steps are done and then gone
   permanently. Paper on the cork, like the cards, so it reads as a note pinned
   to the board rather than as chrome. */
.setup {
    background: var(--panel); border: 1px solid var(--panel-rule);
    border-left: 3px solid var(--accent);
    border-radius: 9px; padding: 14px 18px; margin: 0 0 16px;
    box-shadow: var(--sh-2);
}
.setuphead { display: flex; align-items: baseline; gap: 10px; margin-bottom: 10px; }
.setuphead b { color: var(--panel-ink); font-size: 14px; }
.setuphead span {
    color: var(--panel-mute); font-size: 11px; letter-spacing: .12em;
    text-transform: uppercase; font-weight: 700;
}
.setupsteps {
    list-style: none; margin: 0; padding: 0;
    display: grid; gap: 8px 20px; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}
.setupsteps li { display: flex; align-items: flex-start; gap: 9px; font-size: 13.5px; }
.setupsteps .tick {
    flex: 0 0 17px; height: 17px; margin-top: 1px; border-radius: 50%;
    border: 1px solid #b09a7c; display: flex; align-items: center; justify-content: center;
    font-size: 11px; line-height: 1; color: transparent;
}
/* Done is carried by the filled disc AND the tick, not by colour alone. */
.setupsteps li.on .tick { background: var(--accent); border-color: var(--accent); color: #2B241F; }
.setupsteps .what { color: var(--panel-ink); }
.setupsteps li.on .what { color: var(--panel-mute); }
.setupsteps .what a { color: #8a5a1e; font-weight: 600; }
.setupsteps em { display: block; font-style: normal; color: var(--panel-mute); font-size: 12px; margin-top: 1px; }

/* ============================================================================
   OPERATIONS PAGE (admin.php)

   A dashboard is scanned, not read, so state has to survive a glance: the
   headline numbers carry semantic colour, the funnel is a shape before it is a
   list of figures, and every table scrolls inside its own box so a long board
   name can never push the page sideways.

   TWO SURFACES, TWO PALETTES, AND THE FIRST DRAFT GOT IT WRONG. The .kpi tiles
   sit on the Night page and take --ink/--muted. Everything else lives inside a
   .card, which is PAPER, and there --ink is #F7F1E8 on #F7F1E8: literally
   1.00:1, invisible. The funnel labels and every board name rendered as blank
   space. Measured, not guessed:

       --ink    on paper   1.00:1   invisible   -> --panel-ink  12.53:1
       --body   on paper   1.33:1   FAIL        -> --panel-ink  12.53:1
       --muted  on paper   2.46:1   FAIL        -> --panel-mute  5.74:1
       --accent on paper   1.78:1   FAIL (3:1 for a graphic) -> #8a5a1e 5.25:1

   No new colour beyond that: --good and --warn already mean something
   everywhere else, and inventing a dashboard palette would make the same state
   read two different ways on two pages.
   ========================================================================== */

/* --- on the Night page ---------------------------------------------------- */

.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 13px; }
.kpi {
    background: var(--band); border: 1px solid var(--rule); border-radius: 10px;
    padding: 16px 18px; display: grid; gap: 2px; align-content: start;
}
.kpi b {
    font-size: 30px; line-height: 1.1; color: var(--ink); font-variant-numeric: tabular-nums;
}
.kpi b.good { color: var(--good); }
.kpi b.hot  { color: var(--warn); }
.kpi span { font-size: 13px; color: var(--muted); }
.kpi i { font-style: normal; font-size: 12px; color: var(--muted); margin-top: 4px; }
.kpi i.up  { color: var(--good); }
.kpi i.hot { color: var(--warn); }

/* --- inside a .card, which is paper --------------------------------------- */

/* The funnel. The bar is the point: five numbers in a column is a table, five
   bars is a drop-off you can see without reading. */
.funnel { list-style: none; margin: 0; padding: 0; display: grid; gap: 11px; }
.funnel > li {
    display: grid; align-items: center; gap: 4px 14px;
    grid-template-columns: minmax(150px, max-content) minmax(80px, 1fr) 3.5ch;
}
.card .funnel .fn { font-size: 14.5px; color: var(--panel-ink); }
.card .funnel b {
    font-size: 15px; color: var(--panel-ink);
    font-variant-numeric: tabular-nums; text-align: right;
}
.funnel .fbar {
    height: 10px; border-radius: 99px; background: #efe7d6;
    /* The track needs a real boundary or the 100% extent is invisible, and no
       tint of the paper reaches 3:1 against it. The border carries it. */
    border: 1px solid #8a5a1e; overflow: hidden;
}
.funnel .fbar i { display: block; height: 100%; background: #8a5a1e; }
/* The rationale sits under the row on narrow screens rather than beside it,
   because squeezing it into a fourth column is what makes these unreadable. */
.card .funnel .det { grid-column: 1 / -1; font-size: 12.5px; color: var(--panel-mute); margin-top: -3px; }
@media (min-width: 760px) {
    .funnel > li { grid-template-columns: minmax(150px, max-content) minmax(80px, 1fr) 3.5ch minmax(0, 1.1fr); }
    .card .funnel .det { grid-column: auto; margin-top: 0; }
}

/* Tables scroll in their own box. The page body must never scroll sideways. */
.tscroll { overflow-x: auto; }
.tbl { width: 100%; border-collapse: collapse; font-size: 14px; }
.card .tbl th {
    text-align: left; font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
    color: var(--panel-mute); font-weight: 700; padding: 0 14px 8px 0;
    border-bottom: 1px solid var(--panel-rule); white-space: nowrap;
}
.card .tbl td {
    padding: 9px 14px 9px 0; border-bottom: 1px solid var(--panel-rule);
    vertical-align: top; color: var(--panel-ink); font-variant-numeric: tabular-nums;
}
.tbl tr:last-child td { border-bottom: 0; }
.card .tbl td strong { color: var(--panel-ink); font-weight: 600; }
.card .tbl .det, .card .paths .det, .card .plain .det { font-size: 12.5px; color: var(--panel-mute); }
/* Internal boards stay visible but recede. Hiding them would make the table
   disagree with the counts above it for no stated reason. Only to .62, because
   panel-ink at .55 drops under 4.5:1 and this is still data somebody reads. */
.card .tbl tr.muted td { opacity: .62; }

.plain { list-style: none; margin: 0; padding: 0; display: grid; gap: 9px; }
.plain > li { font-size: 14.5px; }
.plain .det { margin-left: 8px; }

.paths { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; font-size: 14px; }
.paths > li { display: grid; grid-template-columns: 4ch 1fr; gap: 10px; }
.card .paths .det { text-align: right; font-variant-numeric: tabular-nums; }
