/* Chalk and canvas — the design language since 2026-09-09.
   Tokens, type and components: docs/design/DESIGN.md.

   The first ground was cream and terracotta, the look every generated page has; the second was
   ruled paper. Dodo asked for a fresh direction with the chat rebuilt to feel like Claude's, so
   this one is grounded in the room he actually trains in: the chalk on a lifter's hands, the
   canvas of a ring, the tape. A cool chalk ground, white surfaces for the things you act on, a
   deep green-slate ink, and one cobalt accent that carries every action — it clears 7:1 with white
   text, so a button can be small and a link can be a word.

   Two typefaces with two jobs: Newsreader, a serif made for reading, for the trainer's words and
   every heading — the coach speaks in the voice of the books he quotes; IBM Plex Sans, with
   tabular figures, for the interface — doses, buttons, chips, the box you type in. Both come from
   Google Fonts by a <link> in base.html with system fallbacks, never imported from this file.

   Flat still: solid fills, no shadows, no gradients, no imagery. Motion only where it answers an
   action, under 300ms, and none at all under prefers-reduced-motion.

   Two grounds [2026-09-10, Dodo: "add dark mode please"]. The page follows the system: the tokens
   below are the light ones, and the block at the end of this section says every one of them again
   for a dark room. Nothing but tokens changes — every rule reads its colour by name, so the whole
   app turns at once, the heat map and the charts included. Every floor the light palette clears
   is measured again on the dark one by the same tests. */
:root{
  color-scheme:light;
  --bg:#F3F4F1;          /* chalk: the ground */
  --panel:#FFFFFF;       /* a surface you act on — the composer, a card, a menu */
  --soft:#E6E9E4;        /* a secondary fill: your own messages, ghost buttons, hover */
  --ink:#22302C;         /* green-slate ink; 12.5:1 on the ground */
  --muted:#5F6B68;       /* 5.0:1 on the ground, 5.5:1 on a panel */
  --line:#D6DAD5;        /* rules inside lists and around a panel */
  --accent:#2447D9;      /* cobalt: the one action, links in prose, the focus ring. White on it is
                            7:1, so it carries text at any size */
  --accent-ink:#FFFFFF;
  --accent-hover:#1F3FC4;
  --accent-soft:#C7D3F5; /* the accent as a wash: a source chip, a day waiting to be logged.
                            1.2:1 from the soft fill, so the two are never mistaken; ink on it 9:1 */
  --soft-hover:#DADED8;
  --attention:#9A5312;   /* amber, for a caveat or a warning in words; 5.3:1 on the ground */
  /* Ink for text sitting on a light wash that does not change with the scheme — the journal's
     highlight is the only one [2026-09-15]. It is deliberately NOT redefined in the dark block
     below: a highlighter is a pale band with dark ink on it in any room, and following `--ink`
     into the dark scheme is exactly what made highlighted words vanish at 1.02:1. */
  --ink-on-wash:#22302C;
  /* The two directions on a dashboard card [2026-09-13, Dodo: "put green and red colors based on
     the direction on the arrow"]. A deliberate second and third colour in an app that has had one:
     they name a DIRECTION, never an action, so neither may be mistaken for the accent — 1.3:1 and
     1.1:1 from it, which is to say nothing like it. Both clear 4.5:1 on the card they sit on
     (5.3 and 6.5 here, 7.5 and 6.6 in the dark), because this is 12px text and 12px text is not
     large text. The arrow and the word stay either way: colour is never the only channel. */
  --up:#1E7A4B; --down:#B3261E;
  --on-ink:#C9D1CE;      /* secondary text on an ink fill */
  --veil:rgba(20,17,15,.10);   /* the page behind a dialog */
  --serif:"Newsreader",Georgia,"Times New Roman",serif;
  --sans:"IBM Plex Sans",-apple-system,"SF Pro Text","Helvetica Neue",Helvetica,Arial,sans-serif;
  /* the bars on Today (app/web/svg.py, BAR_INK): ink at 78% over the ground */
  --l3:rgba(34,48,44,.78);
  /* The year grid says WHAT was trained, not how hard [2026-09-07, Dodo], so it needs a colour
     per session type. A deliberate, recorded departure from the one-accent rule: a category is
     not an action, and eleven kinds of day cannot be told apart by one ink ramp. Muted and equal
     in weight so no type shouts louder than another, and none of them is `--accent`, which stays
     the colour of the thing you can press. Every value lives here; `app/web/svg.py` reads them by
     name, like the ramp above. */
  --t-push:#C1553C; --t-pull:#2E6F6B; --t-lower:#3D5A98; --t-core:#7B5EA7;
  --t-arms:#B5822E; --t-boxing:#A62E2A; --t-upper_power:#4A7C3F; --t-aux_neck_forearm:#6E7F8D;
  --t-conditioning:#C98A2E; --t-mobility:#5E8C7D; --t-other:#8A8177;
  --t-running:#C251AA;   /* 313°: the emptiest angle in the palette, 49° from the nearest named */
  /* …and the ten for kinds nobody hard-coded [2026-09-13, Dodo: "running needs a different
     color"]. `svg.SPARE` has referenced `--t-x1`..`--t-x10` since the day a session type stopped
     being a fixed list, and they were never written down: `running` drew as `var(--t-x5)`, which
     is not a colour, so the declaration was dropped and the square rendered as nothing at all.
     Every kind but the eleven above was invisible — judo, climbing, "upper/lower A".

     They are SEARCHED for rather than chosen, against the rule the test states: every colour on
     this grid must be thirteen degrees of hue from every other, or a clear step apart in weight.
     Ten hues will not fit around twelve named ones on hue alone — greens run out first — so the
     search takes lightness as the second axis and five of these are a dark version of an angle
     another one holds light. Twice while writing this the hand-picked set put two colours on top
     of each other and the test said which two; the numbers below are what came back from asking
     for the constraint instead of eyeballing it. Thirteen degrees of green
     is not much, so they alternate light and dark as well — neighbours differ on two axes, not
     one — and all ten sit inside the family's weight, 0.13 to 0.23 against the named ones' 0.10
     to 0.31. Nobody shouts. */
  --t-x1:#798C36; --t-x2:#459258; --t-x3:#C74D7B; --t-x4:#AB6DBA; --t-x5:#706DBA;
  --t-x6:#615D2E; --t-x7:#466D22; --t-x8:#2E6876; --t-x9:#377654; --t-x10:#316833;
  /* how long since a region was worked: fresh, a few days, over a week, a fortnight or more.
     Ink on every band clears 9:1, so a figure printed inside any cell stays legible. */
  --r0:#FFFFFF; --r1:#E3E7E1; --r2:#CCD3CB; --r3:#F1DCC6;
  --mark:#F6ECB3;                    /* the highlighter: what a search matched, in text and on a
                                        book page. A yellow wash, because that is what a highlighter
                                        is, and not the accent, which belongs to actions */
  --mark-box:rgba(36,71,217,.22);    /* …and the accent at a fifth, over a book's own page */
  --t-sick:#D8DDD6;                  /* a rest day illness took: the same fill, striped */
  --cell-edge:#6F7B8B;               /* the hairline that makes a square a square: 4.3:1 on a panel,
                                        3.8:1 on an empty square */
  --t-rest:#D8DDD6;                  /* a day you rested is a day you logged: it gets a fill */
  --t-none:#EEF2F7;                  /* nothing logged — still a square, read against the panel */
  /* A session written and waiting to be logged [2026-09-09]: the accent wash, which is already
     the "look here" fill on a source chip. */
  --t-prescribed:#C7D3F5;
}

/* The dark room [2026-09-10]. Chalk becomes the text and the slate becomes the ground: a deep
   green-slate rather than a black, white-ish chalk for the words, the cobalt lifted so it carries
   dark text on a button and reads as a link on the ground. The session hues of the year grid keep
   their values — they are mid-tones and read on both grounds — and the empty, rest and sick squares
   get fills of their own again, each still a step from the ground and the panel. */
@media (prefers-color-scheme: dark){
  :root{
    color-scheme:dark;
    --bg:#161C1A;
    --panel:#1F2725;
    --soft:#2B3430;
    --ink:#E9EDE9;         /* 14.6:1 on the ground */
    --muted:#A4AFAA;       /* 7.7:1 on the ground, 6.8:1 on a panel */
    --line:#374039;
    --accent:#8DA5F7;      /* 7.3:1 on the ground as a link; carries dark text at 7.6:1 */
    --accent-ink:#0E1533;
    --accent-hover:#A4B7F9;
    --accent-soft:#2D3F74; /* 1.2:1 from the soft fill; chalk on it 8.7:1 */
    --soft-hover:#36413C;
    --attention:#E4A960;   /* 8.4:1 on the ground */
    --up:#72C79A; --down:#F0908A;
    --on-ink:#4B5652;      /* secondary text on the chalk fill of a selected thing, 6.5:1 */
    --veil:rgba(0,0,0,.45);
    --l3:rgba(233,237,233,.78);
    --r0:#1F2725; --r1:#2B3430; --r2:#3A4640; --r3:#4E3A22;
    --mark:#5C5220;
    --mark-box:rgba(141,165,247,.28);
    --t-sick:#3A4540;
    --cell-edge:#8E9A93;   /* 5.2:1 on a panel, 4.8:1 on an empty square */
    --t-rest:#3A4540;
    --t-none:#252E2B;
    --t-prescribed:#2D3F74;
  }
}

*{box-sizing:border-box}
/* Said for a screen reader, drawn for nobody: the chat's own heading, which the thread bar makes
   redundant to the eye. */
.visually-hidden{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
                 clip:rect(0 0 0 0);white-space:nowrap;border:0}
body{margin:0;background:var(--bg);color:var(--ink);
     font:16px/1.5 var(--sans);
     font-variant-numeric:tabular-nums}
a{color:inherit;text-decoration:none}
.wrap a{text-decoration:underline;text-decoration-color:var(--line);text-underline-offset:2px}
.wrap a:hover{text-decoration-color:var(--ink)}
/* The exceptions: a link that is a CONTROL rather than a word in a sentence. `.entry-open` joined
   them on 2026-09-15 — a journal row is a card you press, and underlining a date, a clock and
   three lines of somebody's diary drew a rule under all of it [the row had been quietly underlined
   since it became an anchor: its own `text-decoration:none` is (0,1,0) and loses to `.wrap a`,
   which is (0,1,1). This list is where that argument is settled, not the rule down in the block]. */
.wrap a.btn,.wrap a.opt,.wrap a.chip,.wrap a.panel,.wrap a.source,.wrap a.entry-open,
.wrap .menu-list a{text-decoration:none}

/* ── shell ──────────────────────────────────────────────────────────────────────────────── */
/* Three things now, not two — the brand, where you can go, and who is here — so the brand pushes
   and the other two ride the right edge together. */
.top{display:flex;align-items:center;gap:20px;
     max-width:1040px;margin:0 auto;padding:20px 32px}
/* The mark and the name, one object [2026-09-13]. The image is decorative — the word beside it
   says the same thing — so its `alt` is empty and a screen reader reads the name once. Sized to
   the cap height of the word, not to a round number, so the two sit on one line. */
.brand{display:inline-flex;align-items:center;gap:9px;font:600 20px/1 var(--serif);
       letter-spacing:0;margin-right:auto}
.brand img{height:26px;width:auto;flex:none}
.nav{display:flex;flex-wrap:wrap;gap:28px;align-items:center;font-size:14px;color:var(--muted)}
/* 4px above and below makes each link a 26px target in its own right, rather than a 17px one
   leaning on WCAG's spacing exception — which the nav lost the day it went from three links to
   five and started wrapping onto two rows on a phone [2026-09-07]. */
.nav a{padding:4px 0}
.nav a[aria-current="page"]{color:var(--ink);font-weight:600}
/* Who you are, and the way out. Both used to sit in the nav in the nav's own colour and size, so
   the header read as four places to go, two of which were a person and a verb [2026-09-12, Dodo:
   "accountname and the logout button needs some love, both looks like menu items"]. They are one
   object now, drawn with an edge of its own: the outline is the divider, the name is ink and a
   little heavier because it is a label and not a destination, and the way out is the only half
   that answers the pointer. Outlined and not filled for two reasons — signing out is rare and
   should look it, and the filled pill in this header is already taken by the plan being written,
   which sits two items away whenever it is there. */
/* The name, as the thing you open rather than a label beside a button [2026-09-13]. The pill is
   the summary now, so the whole of it answers the pointer, and the chevron says it opens. */
.who{position:relative}
.who>summary.me{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--line);
     border-radius:999px;padding:6px 10px 6px 14px;font-size:13px;font-weight:500;color:var(--ink);
     cursor:pointer;list-style:none}
.who>summary.me::-webkit-details-marker{display:none}
.who>summary.me:hover{background:var(--soft)}
.who>summary.me svg{width:13px;height:13px;flex:none;color:var(--muted)}
.who[open]>summary.me svg{transform:rotate(180deg)}
.who .menu-list{min-width:200px}
/* The way out is not one of the places this can take you, so it sits under a rule. */
.menu-list li.apart{margin-top:6px;padding-top:6px;border-top:1px solid var(--line)}
/* A short form on an ordinary page — the password change, and whatever comes next like it. NOT
   `.signin`: that one is the door, centred and full-height, and borrowing it put a centred column
   under a heading sitting at the page margin [2026-09-13]. Left-aligned, like every other page
   here, and only as wide as a password needs. */
.narrow-form{max-width:344px;margin:18px 0 0}
.narrow-form .field{margin-top:10px}
.narrow-form .field input{font-size:16px}
.narrow-form .actions{margin-top:18px}
.narrow-form .btn{width:100%}
.narrow-form .note{margin-top:18px}

.signin{max-width:344px;margin:0 auto;text-align:center}
.signin .mark{display:block;height:168px;width:auto;margin:0 auto 22px}
.signin h1{font:600 42px/1 var(--serif);letter-spacing:-.02em;margin:0 0 8px}
.signin h1 .beta{font:400 12px/1 var(--sans);color:var(--muted);letter-spacing:.02em;
      vertical-align:.85em;margin-left:9px;border:1px solid var(--line);border-radius:999px;
      padding:3px 8px}
.signin .slogan{margin:0 0 24px;padding-bottom:24px;border-bottom:1px solid var(--line);
      font-size:15px;color:var(--muted)}
.signin .field{margin-top:10px;text-align:left}
.signin .field input{font-size:16px}          /* 16px or the phone zooms when it takes focus */
.signin .actions{margin-top:18px}
.signin .btn{width:100%}
.signin .note{margin-top:20px}
/* The way in for somebody who has no way in, under a rule that says the form above is finished. */
.waitlist{margin-top:28px;padding-top:24px;border-top:1px solid var(--line)}
.waitlist .note{margin:0 0 12px}
.waitlist .actions{margin-top:14px}
.waitlist .btn{width:100%}
.joined{margin:0;font-size:15px;color:var(--ink)}
/* The honeypot. Out of sight and out of the tree, but SUBMITTED — `display:none` is skipped by
   some form fillers, which is the opposite of what this is for. */
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
@media (max-height:700px){.login-page .wrap{min-height:0;padding-top:6vh}.signin .mark{height:104px}}
@media (max-width:520px){.signin h1{font-size:36px}}
.wrap{max-width:1040px;margin:0 auto;padding:16px 32px 96px}
/* Prose stops at a readable measure; chips, tables and the grids keep the whole column. A note
   running the full 976px was about twice a comfortable line [2026-09-07]. */
.wrap>p,.wrap>.note,.wrap>.muted,.panel>p,.instr{max-width:66ch}
.foot{max-width:1040px;margin:0 auto;padding:24px 32px;color:var(--muted);font-size:13px}

/* ── type ───────────────────────────────────────────────────────────────────────────────── */
.date{font-size:14px;color:var(--muted);margin:24px 0 4px}
/* Headings are set in the serif: the voice of the page is the coach's. Sentence case, since
   2026-09-09 — every section used to carry a 13px tracked-out uppercase label, the eyebrow that
   frontend-design names as the commonest tell of a generated page. */
h1{font:500 36px/1.1 var(--serif);margin:0 0 32px;letter-spacing:-.01em}
h2{font:500 22px/1.25 var(--serif);color:var(--ink);margin:56px 0 16px;letter-spacing:0}
h3{font:500 18px/1.3 var(--serif);margin:24px 0 8px}
h4{font:600 13px/1.4 var(--sans);color:var(--muted);margin:18px 0 6px}
p{margin:0 0 12px}
.muted{color:var(--muted)}
.warn{color:var(--attention)}       /* a warning is amber, in words; the accent is for actions */
.meta{font-size:13px;color:var(--muted)}
.note{font-size:13px;color:var(--muted);margin-top:14px}
.instr{max-width:720px;margin:4px 0 8px}    /* how to do the movement — body text, not a caption */
/* why this session: three short parts, each sitting under the words that say which part it is */
.why{max-width:720px}
.why .meta{margin:0}
.why p+.meta{margin-top:14px}

/* ── chips: the numbers are the headlines ───────────────────────────────────────────────── */
/* One waiting plan: the panel every other card on Today uses, and nothing on it that is not a
   title, a fact or a way through [2026-09-07]. The state is the heading above it — drafts and
   scheduled sessions are two lists — so a card never has to label itself. */
/* A run in flight is a STATUS, not a disabled button [2026-09-08]. It sat where the primary
   button sits, in the accent at .55 opacity, which reads as broken rather than as busy — the
   loudest thing on the page, greyed. Ink on the soft fill, the same height as the buttons beside
   it so the row does not jump when it appears. */
.status{margin:0;background:var(--soft);border-radius:6px;padding:15px 20px;
        font-size:17px;font-weight:600;color:var(--ink)}

.panel.plan{padding:16px 20px}
.panel.plan h3{font-size:19px;margin:0 0 2px}
.panel.plan .meta{margin:0}
.panel.plan .warn{margin:10px 0 0;font-size:15px;font-weight:600}
.panel.plan>.note{margin:10px 0 0}
/* what it is on the left, what you can do about it on the right, on one line: stacked, this was
   four rows tall against an empty right-hand half [2026-09-07] */
.plan-row{display:flex;flex-wrap:wrap;gap:12px 20px;align-items:center;justify-content:space-between}
.plan-what{min-width:0}
.plan-act{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
/* the confirm is a BUTTON, not a line with a triangle hanging outside the panel's own left edge.
   `<details>` is the flex item, so the warning and the button it reveals are laid out by the
   details and never become items of this row. */
.plan-act .confirm{margin:0}
.plan-act .confirm>summary{list-style:none;display:inline-block;width:auto;color:var(--ink);
       font-size:15px;font-weight:600;padding:11px 16px;border-radius:6px;background:var(--soft)}
.plan-act .confirm>summary::-webkit-details-marker{display:none}
.plan-act .confirm[open]{flex:1 0 100%}
.plan-act .confirm[open]>summary{margin-bottom:10px}

/* The whole body as one strip of shaded cells [2026-09-07]. Four versions came before it — ten
   pills, ten bars, six bars, a sentence — and the ramp is what none of them had: with every cell
   the same grey, ten regions reading nearly the same number look like ten identical facts, and the
   shape of the week is invisible. Ink on the darkest of these is 11.6:1, so the figure inside a
   cell is legible on every band.

   `auto-fit` rather than a fixed count: ten cells sit in one row on the full column and fold to
   two or three as it narrows, with no breakpoint of their own. */
/* One region of the page, divided from the next [2026-09-07]. Today's answer, the decisions you
   still owe, and what has already happened are three unrelated things that were siblings in one
   flat list of h2s — so a plan written on Friday read as today's session. A rule ACROSS the page
   separates two regions and encloses nothing, which is not the hairline-around-a-box that Paper
   forbids. The space does as much of the work as the line. */
.chunk{border-top:1px solid var(--line);margin-top:52px;padding-top:36px}
.chunk>h2:first-child{margin-top:0}
.eyebrow{font-size:13px;color:var(--muted);
         font-weight:600;margin:0 0 8px}
.regions{display:grid;grid-template-columns:repeat(auto-fit,minmax(92px,1fr));gap:4px;margin:0 0 10px}
.region{background:var(--r0);border-radius:4px;padding:9px 10px;min-width:0}
.region b{display:block;font-size:13px;font-weight:600;line-height:1.25}
.region span{display:block;font-size:12px;color:var(--muted);margin-top:2px}
.region.r1{background:var(--r1)}
.region.r2{background:var(--r2)}
.region.r3{background:var(--r3)}
.region.r2 span,.region.r3 span{color:var(--ink)}   /* --muted is 5:1 on paper, less on these */

/* A movement is three parts and each says which it is [2026-09-08]. Three grey paragraphs in a row
   made you read the whole thing to find out which sentence was the cue and which was the reason.
   The label sits on the same line as the text, not above it — a heading per sentence would be
   taller than the sentence — and it is the only thing in the row that is not muted, so the eye
   can skip to "Tip" without reading "How". */
.part{margin:8px 0 0;font-size:15px;line-height:1.5}
.part-of{display:inline-block;min-width:42px;margin-right:8px;font-size:12px;font-weight:600;
         letter-spacing:0;color:var(--muted)}
.part.tip{color:var(--muted)}
.part.tip .part-of{color:var(--ink)}          /* the cue is the part worth finding */
.part.why{color:var(--muted);font-size:14px}

/* ── pills ───────────────────────────────────────────────────────────────────────────────────
   The facts about a plan waiting on a decision, as pills rather than one dot-separated muted line
   [2026-09-07, Dodo: "maybe make the pills to make it more prominent"]. A run-on `.meta` line gave
   the same weight to the thing you have to decide and the two facts you do not.

   A badge says a STATE, a tag says a value — two different things, and only the first earns the
   strong fill. So the state is paper on ink, a caveat is the accent wash, and the plain facts sit
   on the same soft fill every secondary control uses. Colour is never the only channel: each pill
   carries its own words, and each stays whole on one line while the row wraps around it. */
.tags{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
.tag{background:var(--soft);color:var(--ink);border-radius:999px;padding:5px 11px;
     font-size:13px;font-weight:600;white-space:nowrap;min-width:0}
.tag.state{background:var(--ink);color:var(--bg)}
.tag.warn{background:var(--accent-soft);color:var(--ink)}
/* the body-part row is longer than a card's three pills and reads as a sentence, so it is set in
   the regular weight and given room to breathe [2026-09-07] */
.tags.parts{gap:8px;margin:4px 0 12px}
.tags.parts .tag{font-weight:400}

.chips{display:flex;flex-wrap:wrap;gap:12px}
.chip{background:var(--panel);border-radius:6px;padding:14px 16px;min-width:132px}
/* A heading with its own control on the right: the window switch belongs to the section it
   changes, not to the page [2026-09-07]. */
.head-row{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;
      margin:56px 0 16px}                        /* the h2's own margin, moved to the row that holds it */
.head-row h2{margin:0}                           /* …and zeroed here, or the buttons fall into the chips */
.segs.small{margin:0}
.segs.small .opt{padding:7px 12px;font-size:14px;text-decoration:none}
.segs.small .opt.on{background:var(--ink);color:var(--bg)}
.chip.wide b{font-size:24px;letter-spacing:-.01em}   /* a word where a number would scold */
.chip b{display:block;font-size:28px;line-height:1;font-weight:700;margin-bottom:6px}
.chip span{font-size:13px;color:var(--muted)}
.chip.hot b{color:var(--accent)}
/* a dose is a phrase, not a headline number: same chip, read at body size */
.chip.dose{min-width:0}
.chip.dose b{font-size:16px;line-height:1.4;font-weight:600;margin:0}

.two{display:grid;grid-template-columns:1fr 1fr;gap:40px}

/* ── plain list: hairline between items, meta line under each ───────────────────────────── */
.list{margin:0;padding:0;list-style:none}
.list li{padding:12px 0;border-top:1px solid var(--line)}
.list li:first-child{border-top:0}
.list .meta{display:block;font-size:13px;color:var(--muted);margin-top:2px}
/* The way into a document, beside its name: a word, not a button — reading one is the common
   case and changing it is not [2026-09-12]. */
.list .edit{margin-left:10px;font-size:13px;color:var(--muted)}
.list li:hover .edit,.list .edit:focus-visible{color:var(--accent)}
/* Starting another one: a field and a quiet button on one line, under the list it adds to. */
.newdoc{display:flex;align-items:flex-end;gap:10px;flex-wrap:wrap;margin:14px 0 0}
/* Nothing named, nothing to start [2026-09-12, Dodo: "start one clicking with empty input errors
   out, the button should be disabled instead"]. `:placeholder-shown` is "this field is empty" said
   in CSS, so the button goes quiet exactly as a disabled one does and the page grows no script for
   it. It stays pressable on purpose: the input is `required`, so a press puts the browser's own
   "fill this in" on the field, which says more than a dead button does. The upload form beside it
   is untouched — a file input shows no placeholder, so it never matches. */
.newdoc:has(input:placeholder-shown) .btn{opacity:.55}
.newdoc .field{margin:0;min-width:240px;flex:1;max-width:420px}
/* Two cards side by side hold two different controls, and a file input is as tall as the button
   the browser draws inside it — so the pair sat 14px out of true [2026-09-13, Dodo: "math the
   height"]. The control line is given one height in both cards and the button is sized to sit
   inside it, rather than the card being padded to match whatever the browser drew. */
.newdoc .field input{min-height:23px;display:flex;align-items:center}
.newdoc input[type=file]{font:inherit;font-size:14px;color:var(--muted);padding:0}
.newdoc input[type=file]::file-selector-button{font:inherit;font-size:14px;font-weight:500;
     margin-right:10px;padding:1px 12px;line-height:21px;border:0;border-radius:6px;
     background:var(--soft);color:var(--ink);cursor:pointer}
/* Writing one. The box is the page: a document is prose and the only honest editor for prose is
   the room to write it in. Monospace, because what is being written is markdown. */
.writing textarea{display:block;width:100%;min-height:56vh;margin:0 0 16px;padding:16px 18px;
     background:var(--panel);border:1px solid var(--line);border-radius:12px;color:var(--ink);
     font:14px/1.65 ui-monospace,SFMono-Regular,Menlo,monospace;resize:vertical;
     tab-size:2;white-space:pre-wrap}
.writing textarea:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-color:var(--accent)}
.writing .actions{margin-top:0}
.writing .note code{background:var(--soft);border-radius:3px;padding:1px 5px;font-size:13px}
/* A version of the file that is not the one being edited: shown, never editable. */
pre.was{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:14px 16px;
     font:13px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace;white-space:pre-wrap;
     word-break:break-word;max-height:40vh;overflow-y:auto}
.past h3{font:600 13px/1.4 var(--sans);color:var(--muted);margin:16px 0 6px}
.more{color:var(--muted);font-size:14px;margin:8px 0 0}

/* the rest-or-push call, and any other one-word answer a page is built around: a headline at the
   page title's size. It was in the accent when the answer was "stop"; a rest day is a verdict, not
   an alert, so it is ink whatever it says [2026-09-10]. */
.verdict{font:500 36px/1.1 var(--serif);letter-spacing:-.01em;margin:0 0 8px}
/* One column whenever the stoic line is not beside it [2026-09-07, again 2026-09-13]: a
   two-column grid with nothing in the second column is a card two thirds empty, and the reason
   reads better at 56ch than at 46. The `.quote` figure and the grid are in git at 87e571f. */
.verdict-card>p{margin:0;max-width:56ch}
.verdict-card .run{margin-top:10px;color:var(--muted);font-size:13px}

/* ── question, option cards, fields ─────────────────────────────────────────────────────── */
.q{margin:0 0 22px;border:0;padding:0}
.q p{margin:0 0 10px;font-weight:600}
/* Three across, not two: the interview is twenty option cards and was the longest thing on the
   page even closed. Same cards, same words, a third less height [2026-09-07]. */
.opts{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.opt{display:block;position:relative;background:var(--panel);border-radius:6px;
     padding:11px 13px;cursor:pointer}
.opt b{display:block;margin-bottom:2px}
.opt small{display:block;color:var(--muted);font-size:12px;line-height:1.35}
/* the whole card is the control: the radio is hidden but still focusable and still validated,
   and it is positioned inside the card so a "please pick one" bubble points at the right place */
.opt input[type="radio"],.opt input[type="checkbox"]{position:absolute;top:14px;left:16px;
     width:1px;height:1px;margin:0;opacity:0}
/* The background switches are the only `.opt` that is a button rather than a label or a link
   [2026-09-15]: a radio group cannot be emptied and those three have to be. The browser's chrome
   comes off and nothing else does — the fill, the focus ring and the pressed state are the same
   component the lengths and the case-file tabs already use. */
button.opt{border:0;width:100%;font:inherit;color:inherit;text-align:inherit}
.opt.on,.opt:has(input:checked){background:var(--ink);color:var(--bg)}
.opt.on small,.opt.on .muted,
.opt:has(input:checked) small,.opt:has(input:checked) .muted{color:var(--on-ink)}
.q .note{font-weight:400}                    /* a hint under a field, not another question */

/* A panel you open only when you need it: the interview on Today, a movement's edits in the
   queue. Flat by rule — a hairline above the summary, no box around it, no marker of our own
   beyond the one the browser draws. */
/* ── the conversation ──────────────────────────────────────────────────────────────────────── */
/* The chat is the page [2026-09-09, Dodo: "match the experience to claude chat"]. The talk sits on
   the chalk itself — no panel inside a panel — with your own words on a soft fill to the right,
   the trainer's as serif body text on the left with no bubble, and the things it made as white
   cards. Under it the composer: a white card with a rule around it, the one surface on the page
   you act on constantly. The scroll is the talk's own, held at the newest thing by the script. */
/* A frame, so the conversation has an edge [2026-09-13, Dodo: "add a frame to the chat screen so
   i can clearly see where chat begins and ends"]. It ran from the rule above it to the composer
   with nothing around it, and on a long page the trainer's prose read as more page. The frame is
   the GROUND with a rule around it, not a fill: the composer and the cards inside it are `--panel`
   and would vanish against a panel of their own. `.chunk` gives the section its space above and
   the frame's own top edge replaces the rule that used to sit there. */
.chat{margin:8px 0 0;border:1px solid var(--line);border-radius:18px;padding:16px 18px 14px}
.chunk.chat{padding-top:16px}     /* the frame's own top edge IS the separator; `.chunk` only spaced it */

.threadbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:0 0 14px}
.threadbar .meta{margin:0 0 0 auto}
.menu{position:relative}
.menu>summary{list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:8px}
.menu>summary::-webkit-details-marker{display:none}
.menu>summary svg{width:14px;height:14px;flex:none}
.menu-list{position:absolute;top:calc(100% + 6px);left:0;z-index:30;min-width:260px;max-width:min(380px,90vw);
           margin:0;padding:6px;list-style:none;background:var(--panel);border:1px solid var(--line);
           border-radius:12px;max-height:60vh;overflow-y:auto}
.menu-list li a,.menu-list li button,.menu-list li .say button{display:block;width:100%;text-align:left;padding:9px 12px;
           border-radius:8px;font:inherit;font-size:15px;color:var(--ink);background:none;border:0;cursor:pointer}
.menu-list li a:hover,.menu-list li button:hover{background:var(--soft)}
.menu-list li a[aria-current]{background:var(--accent-soft)}
.menu-list .meta{display:block;font-size:12px;margin-top:1px}
.menu.right .menu-list{left:auto;right:0}
.menu.up .menu-list{top:auto;bottom:calc(100% + 6px)}

.talk{display:flex;flex-direction:column;gap:22px;max-height:min(72vh,900px);overflow-y:auto;
      padding:18px 4px 26px;scroll-padding-bottom:20px;border-top:1px solid var(--line)}
/* Inside the frame the threadbar and the messages are already two rows of one thing, so the
   talk's own top rule comes off [2026-09-13]. Written after `.talk` and not before it: two tests
   read the first `.talk{` in this file to check the scroll box is still a scroll box. */
.chat .talk{border-top:0;padding-top:6px}
.turn{display:flex;flex-direction:column;gap:14px}
/* You, on the right, on a soft fill — a short block set in the sans, at reading size. */
.asked{align-self:flex-end;margin:0;max-width:min(62ch,88%);background:var(--soft);
       border-radius:16px 16px 4px 16px;padding:10px 16px;font-size:16px;line-height:1.45;
       white-space:pre-wrap;word-break:break-word}
/* The trainer, on the left, in the serif, no bubble: the page's own voice. */
.reply{margin:0;max-width:66ch}
.reply p{margin:0 0 12px}
.reply.doc{font:17px/1.55 var(--serif)}
.reply.doc a{color:var(--accent);text-decoration-color:var(--accent-soft)}
.reply.doc > :first-child{margin-top:0}
.reply.doc > :last-child{margin-bottom:0}
.reply.doc ul,.reply.doc ol{padding-left:22px;margin:0 0 12px}
.reply.doc h1,.reply.doc h2,.reply.doc h3{font-size:19px;margin:14px 0 6px}
.reply.doc code{font:14px var(--sans);background:var(--soft);border-radius:4px;padding:1px 5px}
/* The words still arriving end in a bar: the one moving thing, and it moves because the text does. */
.reply.doc.streaming > :last-child::after{content:"";display:inline-block;width:2px;height:1em;
       margin-left:3px;vertical-align:-2px;background:var(--accent);animation:blink 1s steps(2) infinite}
/* A card: a white surface with a rule around it, radius 12. Every thing the trainer made — the
   session, the log check, the logged day — is one of these. */
.reply.card{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px 18px;
            max-width:none;width:100%}
.reply.card > :last-child{margin-bottom:0}
.reply.card .tools{display:flex;flex-wrap:wrap;gap:8px;margin:16px 0 0}
.reply.card .plan-text:not([hidden]){display:block;margin:14px 0 0;padding:14px 16px;background:var(--bg);
       border:1px solid var(--line);border-radius:8px;font:13px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace;
       white-space:pre-wrap;word-break:break-word;max-width:none}
/* The case-file document a draft is proposing [2026-09-12]. Inset like the session's plain-text
   block, but set as PROSE — this is a page of somebody's record, and the monospace the copy block
   wears is for a thing that goes on a clipboard. */
.reply.card .draft-text{margin:14px 0 0;padding:14px 18px;background:var(--bg);border:1px solid var(--line);
       border-radius:8px;font:16px/1.6 var(--serif);max-width:none}
.reply.card .draft-text > :first-child{margin-top:0}
.reply.card .draft-text > :last-child{margin-bottom:0}
.reply.card .draft-text h1,.reply.card .draft-text h2,.reply.card .draft-text h3{
       font-size:19px;margin:16px 0 6px;letter-spacing:0}
.reply.card .draft-text ul,.reply.card .draft-text ol{padding-left:22px;margin:0 0 12px}
.reply.card .draft-text hr{border:0;border-top:1px solid var(--line);margin:12px 0}
.reply.card.check{border-color:var(--accent-soft);background:var(--accent-soft)}
.reply.card.check.done{background:var(--panel);border-color:var(--line)}
.reply.card.check .meta{margin:0 0 4px}
.reply.card .sentence{font:17px/1.5 var(--serif);margin:0 0 12px}
.reply.card.check.done .sentence{color:var(--muted);margin:0}
.reply.card.logged p,.reply.card.removed p{margin:0}
.reply.card.remembered p,.reply.card.checkin p{margin:0}           /* a note written down; a check-in finished */
.reply.card.remembered .chips-row{margin-top:8px}
.checkin-due{display:flex;align-items:center;flex-wrap:wrap;gap:10px;margin:12px 0 0}
.checkin-due .note{margin:0}
.list.notes li{flex-wrap:wrap}
.list.notes .chips-row{flex-basis:100%;margin-top:6px}
.reply.card.logged a{color:var(--accent)}
/* Where the answer came from: a row of chips, each a link, a book page or a web page. */
.reply.sources{max-width:none;display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.reply.sources > .meta{margin:0 2px 0 0}
.source-list{list-style:none;margin:0;padding:0;display:contents}
.source-list li{max-width:100%;min-width:0}
.source{display:inline-block;max-width:100%;padding:6px 11px;border-radius:999px;box-sizing:border-box;
        background:var(--accent-soft);color:var(--ink);font-size:13px;font-weight:500;line-height:1.3;
        overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle}
.source .meta{font-size:12px;font-weight:400;margin-left:4px}
/* What it did, folded: a record of how a session was written, worth keeping and not worth
   reading twice. Open only while the work is still going on. */
.did{margin:0}
.did>summary{cursor:pointer;font-size:13px;color:var(--muted);list-style-position:inside}
.did[open]>summary{margin-bottom:6px;color:var(--ink)}
.steps{list-style:none;margin:0;padding:0 0 0 12px;border-left:2px solid var(--line)}
.steps li{margin:0 0 3px;font-size:13px;color:var(--muted)}
.steps li:last-child{margin-bottom:0}
/* The trainer is working: a cobalt dot that breathes, and the words of what it is doing. */
.status{display:flex;align-items:center;gap:10px;margin:0;font-size:14px;color:var(--muted);
        background:none;padding:0;font-weight:400;border-radius:0}
.status::before{content:"";width:9px;height:9px;border-radius:50%;background:var(--accent);flex:none;
        animation:breathe 1.4s ease-in-out infinite}
/* Nothing said yet: the question, and five ways to start. */
.chat-empty{padding:34px 0 30px;text-align:center}
.chat-empty h3{font:400 26px/1.25 var(--serif);margin:0 0 18px;letter-spacing:-.01em}
.quick{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;margin:0}
.quick form{display:contents}
.quick .btn,.quick a.btn{display:inline-flex;align-items:center;gap:8px}
.quick svg{width:15px;height:15px}
/* The box: a white card with a rule around it and the one cobalt button on the page. The textarea
   grows with what you type, up to eight lines; the send button is round, the size of a thumb. */
.composer{background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:10px 10px 8px 14px;
          margin:6px 0 0;transition:border-color 150ms ease}
.composer:focus-within{border-color:var(--accent)}
.ask{display:block;margin:0}
.ask textarea{display:block;width:100%;font:inherit;font-size:16px;line-height:1.5;border:0;
              background:transparent;color:var(--ink);resize:none;padding:6px 4px 4px;max-height:12em}
.ask textarea::placeholder{color:var(--muted)}
.ask textarea:focus-visible{outline:0}
.ask-row{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:4px}
.ask-row .meta{margin:0}
.iconbtn,.btn.iconbtn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;
         border-radius:50%;border:0;background:var(--soft);color:var(--ink);cursor:pointer;font:inherit;
         padding:0;flex:none;transition:background-color 150ms ease}
.iconbtn:hover{background:var(--soft-hover)}
.iconbtn svg{width:18px;height:18px;flex:none}
.btn.send{background:var(--accent);color:var(--accent-ink)}
.btn.send:hover{background:var(--accent-hover)}
.btn.send[disabled]{background:var(--soft);color:var(--muted);opacity:1}
.ask-row .left{display:flex;align-items:center;gap:10px;min-width:0}
.ask-hint{font-size:12px;color:var(--muted);margin:0}
.chat-note{margin:10px 0 0;font-size:13px;color:var(--attention)}
/* A question with answers you can tap, and the actions on a card: small buttons in a wrapping row.
   Each answer is a form, so it works with no script and the script sends it without a reload. */
.chips-row{display:flex;flex-wrap:wrap;gap:8px;margin:10px 0 0}
.chips-row form{display:contents}
/* the chip and its look-up are one thing in the row, closer to each other than to the
   next chip; without this the arrow reads as belonging to whichever chip you guess */
.opt-pair{display:flex;align-items:center;gap:2px}
/* the arrow draws small and is TAPPED big: a 14px glyph is a thumb's miss on a phone, and the
   miss lands on the chip beside it, which would swap the movement rather than look it up */
.chips-row a.look-up{display:inline-flex;align-items:center;justify-content:center;
  min-width:28px;min-height:28px;color:var(--muted);text-decoration:none;font-size:14px;line-height:1}
.chips-row a.look-up:hover{color:var(--accent)}
/* New words arrived below what you were reading: a pill over the talk that takes you there. */
.newer{position:sticky;bottom:8px;align-self:center;margin:-40px 0 0;z-index:5}

/* ── the session, as a document you can act on ───────────────────────────────────────────────── */
.session h3{font:500 24px/1.2 var(--serif);margin:0 0 4px;letter-spacing:-.01em}
.session > .meta{margin:0 0 4px}
.session h4{margin:18px 0 4px}
.moves{list-style:none;margin:0;padding:0}
.move{border-top:1px solid var(--line)}
.move>details>summary{cursor:pointer;display:flex;align-items:baseline;gap:10px;padding:10px 0;list-style:none}
.move>details>summary::-webkit-details-marker{display:none}
.move .n{flex:none;width:22px;color:var(--muted);font-size:14px}
.move .mv{flex:1;min-width:0;display:flex;flex-wrap:wrap;gap:2px 12px;align-items:baseline}
.move .mv b{font-weight:600}
/* Each set group is its own small tag, so a left dose and a right dose never run together. */
.move .rx{display:inline-flex;flex-wrap:wrap;gap:4px 6px;font-size:14px}
.move .dose{background:var(--soft);border-radius:6px;padding:1px 8px;color:var(--ink);white-space:nowrap}
.move .chev{flex:none;width:14px;height:14px;color:var(--muted);transition:transform 150ms ease}
.move>details[open] .chev{transform:rotate(180deg)}
.move .body{padding:0 0 12px 32px}
.move .body .part{margin:6px 0 0}
.move-actions{display:flex;flex-wrap:wrap;gap:6px;margin:12px 0 0}
.move-actions form{display:contents}
.session .fold{border-top:1px solid var(--line);margin-top:4px}
.session .fold>summary{cursor:pointer;padding:10px 0;font-size:14px;color:var(--muted);list-style-position:inside}
.session .fold[open]>summary{color:var(--ink)}
.session .fold .why{margin:0 0 8px}
.session .fold ul{margin:0 0 8px;padding-left:20px;font-size:14px}
.session .fold li a{color:var(--accent)}

/* Motion answers an action and is otherwise still. Under reduced motion, nothing moves at all. */
.turn.arrived{animation:arrive 160ms ease-out}
@keyframes arrive{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
@keyframes breathe{0%,100%{opacity:.35}50%{opacity:1}}
@keyframes blink{50%{opacity:0}}
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{animation:none !important;transition:none !important}
}

/* What you have asked of a draft, oldest first [2026-09-08]. A record rather than a form: the ask
   is already sent, and this says so. Unanswered ones carry the accent rule the app puts on
   anything still waiting on somebody, so a request the revision could not honour stays visible
   instead of scrolling past as one more grey line. */
.asks{list-style:none;margin:16px 0 28px;padding:0}
.asks li{margin:0 0 8px;padding:10px 14px;background:var(--soft);border-radius:6px;font-size:15px}
.asks li.open{border-left:3px solid var(--accent);padding-left:11px}
.asks .meta{display:block;margin-top:2px}

.more-panel{margin:0 0 28px;border-top:1px solid var(--line);padding-top:16px}
.more-panel>summary{cursor:pointer;font-weight:600;list-style-position:outside}
.more-panel>summary .meta{font-weight:400}
.more-panel[open]>summary{margin-bottom:20px}
/* the Case File's sections: the same option card, sized to a word and laid out in a row. Which
   section you are reading is an answer, so the current one takes the selected state's ink fill. */
.segs{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 32px}
.segs .opt{padding:10px 14px;font-size:15px;font-weight:600}
.field{display:block;background:var(--panel);border-radius:6px;padding:12px 16px;
       color:var(--muted);font-size:15px;margin-top:10px}
.field .meta{display:block;margin-bottom:2px}   /* what the box is for, above what you type in it */
.field input,.field select,.field textarea{font:inherit;width:100%;padding:0;
       border:0;background:transparent;color:var(--ink)}
.field input::placeholder,.field textarea::placeholder{color:var(--muted)}
/* Keyboard focus stays visible, and the card carries the ring: the control inside has no border
   of its own, and the radio on an option card is 1px wide. The 2px offset puts the ink ring on
   the paper outside the card, so it reads on a selected (ink-filled) card too. */
.opt:has(input:focus-visible),.field:has(:focus-visible){outline:2px solid var(--accent);outline-offset:2px}
.field :focus-visible{outline:0}
/* …and the SAME ring on everything else you can reach with a keyboard [2026-09-07]. The option
   cards, the fields, the heat-map squares and the summaries each grew one; every button and every
   link was left on the browser's own, which is a thin blue hairline that all but disappears on the
   orange button. One ring, one offset, everywhere — a focus style is a system, not a per-component
   decision. */
a:focus-visible,button:focus-visible,select:focus-visible,input:focus-visible,
textarea:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:3px}

/* ── actions: one cobalt button per screen, everything else ink on chalk ───────────────────── */
.actions{display:flex;flex-wrap:wrap;gap:16px;align-items:center;margin-top:36px}
/* White on cobalt is 7:1, so the primary button carries 16px text and needs no bold to be read;
   the red-margin and orange accents before it needed 19px bold to pass. */
.btn{border:0;border-radius:10px;padding:12px 22px;font:inherit;font-size:16px;font-weight:600;
     background:var(--accent);color:var(--accent-ink);cursor:pointer;
     transition:background-color 150ms ease}
.btn:hover{background:var(--accent-hover)}
.btn.ghost{background:var(--soft);color:var(--ink);font-weight:500}
.btn.ghost:hover{background:var(--soft-hover)}
/* the row-level size: a movement carries four of these, and four page-sized buttons would read
   as four decisions. The page's own decision stays full size. */
.btn.small{padding:9px 14px;font-size:14px;font-weight:500;border-radius:8px}   /* 38px tall */
.btn[disabled]{opacity:.55;cursor:default}

/* ── panel: one movement, one card ──────────────────────────────────────────────────────────
   Inside a panel the paper is already white, so everything that would have been white on the
   page — a chip, a field, an option card, a nested panel — takes the soft fill instead. */
.panel{background:var(--panel);border-radius:12px;padding:16px 20px;margin:0 0 12px}
a.panel{display:block}                       /* the landing page: the whole card is the link */
.panel h3{margin:0 0 4px}
.panel > :last-child{margin-bottom:0}
.panel .chip,.panel .field,.panel .opt,.panel .panel{background:var(--soft)}
.panel .opt.on,.panel .opt:has(input:checked){background:var(--ink)}
.panel .actions,.list .actions{margin-top:12px;gap:10px}
.doses{display:flex;flex-wrap:wrap;gap:10px;margin-top:12px}
.doses .field,.actions .field{margin-top:0}
.doses .field input{width:88px}
.narrative{white-space:pre-wrap}                 /* a session's own words, as they were written */
.narrative.md{white-space:normal}                /* …unless they are markdown, and now rendered */
.narrative.md p{margin:0 0 10px;max-width:66ch}
.narrative.md p:last-child{margin-bottom:0}
.narrative.md ul{margin:0 0 10px;padding-left:20px}

/* ── picker: the window or filter row a history page is read through ─────────────────────────
   A row of the same white fields the rest of the app uses — a filter is not a different kind of
   control, so it does not get a different kind of box. */
.picker{display:flex;flex-wrap:wrap;gap:12px;align-items:flex-end;margin:0 0 32px}
.picker .field{margin-top:0;min-width:150px}

/* ── tables: hairline rows, numbers right ───────────────────────────────────────────────── */
/* A wide table scrolls INSIDE its own box; the page body never scrolls sideways [2026-09-07].
   Five columns squeezed into a phone's width turned every movement name into a column of single
   words, which is not narrower, only taller and harder to read. */
.scroll-x{overflow-x:auto}
.scroll-x table.blocks{min-width:560px}
table{border-collapse:collapse;width:100%;font-size:15px}
th{font-size:13px;text-transform:uppercase;letter-spacing:.1em;color:var(--muted);font-weight:600;
   text-align:left;padding:10px 12px 8px;border-bottom:1px solid var(--line)}
td{padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:top}
td.n,th.n{text-align:right}
td .meta{display:block;margin-top:2px}            /* the movement's pattern, under its name */
/* the session page draws one of these per block; fixed layout is what makes two tables stacked on
   the same page share a grid instead of each sizing itself to its own longest movement name */
table.blocks{table-layout:fixed}
tr.deviation td:first-child{border-left:2px solid var(--accent);padding-left:14px}
.norec{color:var(--muted);font-style:italic}      /* no performed dose recorded — never a zero */

/* ── reading column ─────────────────────────────────────────────────────────────────────── */
.doc{max-width:720px;line-height:1.6}
.doc-title{font-size:32px;line-height:1.1}    /* the document/book page title — the page title's own 44px wraps to four lines */
.doc h1{font-size:28px;margin:32px 0 12px;letter-spacing:0}
.doc > h1:first-child{display:none}    /* the page prints the file's title once, above the column */
.doc h2{font-size:20px;text-transform:none;letter-spacing:0;color:var(--ink);margin:28px 0 8px}
.doc h3{font-size:16px;margin:20px 0 6px}
.doc code{background:var(--soft);border-radius:3px;padding:1px 4px;font-size:14px}
.doc pre{background:var(--panel);border-radius:6px;padding:16px;overflow-x:auto}

/* ── charts: ink and muted only, values printed as text ─────────────────────────────────── */
svg text{font-family:inherit}
.chart{display:block;width:100%;height:auto;margin:12px 0}
/* The year heat map: same ramp as the calendar, a whole year wide. It keeps its own aspect, so a
   narrow screen scrolls it sideways rather than shrinking a square to nothing. */
/* The year as a grid of squares: seven rows, a column per week, filling column by column. HTML
   rather than SVG so the label on hover is drawn by CSS and appears at once — the browser's own
   tooltip on an SVG <title> waited a second and a half and could not be hurried [2026-09-07]. */
/* Squares at 16px, not 11: they are the thing you click to reach a day, and a 11px target is a
   dare [2026-09-07]. Half a year fits the column at this size; the whole year is a link away. */
.year{display:grid;grid-template-columns:auto 1fr;gap:5px 8px;margin:0;width:max-content}
.ymonths{grid-column:2;display:grid;grid-auto-columns:16px;gap:8px;color:var(--muted);font-size:10px;
         letter-spacing:.06em;height:12px}
.ymonths span{grid-row:1;white-space:nowrap}
.ydow{grid-column:1;grid-row:2;display:grid;grid-template-rows:repeat(7,16px);gap:8px;
      color:var(--muted);font-size:10px;letter-spacing:.06em;align-items:center}
/* 16px squares on an 8px gutter — a 24px pitch, so the 24px hit area below neither overlaps its
   neighbour nor falls under WCAG 2.2's target-size floor [2026-09-07]. */
.ygrid{grid-column:2;grid-row:2;display:grid;grid-template-rows:repeat(7,16px);grid-auto-flow:column;
       grid-auto-columns:16px;gap:8px}
/* The fill cannot carry the contrast: an empty day against its own white panel is 1.24:1 and a
   rest day against an empty one is 1.26:1. WCAG 1.4.11 lets a boundary do it instead, so every
   square carries a hairline: 4.25:1 against the panel, 3.42:1 against an empty day. A recorded departure from "no hairline
   around a box" — the alternative was three states nobody can tell apart. */
.d{border-radius:3px;position:relative;display:block;border:1px solid var(--cell-edge)}
/* The target on ::before and the label on ::after — they were BOTH ::after, so hovering replaced
   the 24px hit area with the label and the square flickered under the pointer [2026-09-07]. */
.d::before{content:"";position:absolute;inset:-4px}           /* the target, 24×24, invisible */
a.d:hover{outline:2px solid var(--accent);outline-offset:1px}    /* it is a link: say so under the pointer */
a.d:focus-visible{outline:2px solid var(--accent);outline-offset:2px}   /* …and under the keyboard */
.d.pad{background:none;border-color:transparent}
/* Sick is told apart from rest by a MARK, not by a second grey: two pale fills cannot reach 3:1 of
   each other, and colour alone should not carry a meaning anyway. A drawn shape rather than a
   pattern fill or an image, so the flat rule holds [2026-09-07]. */
/* A session written and waiting to be logged. A drawn dot, not a pattern fill and not an image,
   so the flat rule holds — and a second channel, because the prescribed fill is a warm light one
   a step from the rest day's and colour alone would not carry it [2026-09-09]. */
.d .dot{position:absolute;left:50%;top:50%;width:5px;height:5px;margin:-2.5px 0 0 -2.5px;
        border-radius:50%;background:var(--ink)}
.d .slash{position:absolute;left:-1px;right:-1px;top:50%;height:2px;
      background:var(--ink);transform:rotate(-45deg)}
/* the label, immediately: no script, no waiting, and the panel's top padding is what it sits in */
.d[data-t]:hover::after{content:attr(data-t);position:absolute;bottom:calc(100% + 5px);left:0;
       white-space:nowrap;background:var(--ink);color:var(--bg);
       font-size:12px;line-height:1.3;padding:4px 8px;border-radius:4px;z-index:5;pointer-events:none}
/* anchored at the square's left edge and, in the last weeks, at its right: a label centred on the
   square hung outside the scroll box, and inline overflow there is unreachable [2026-09-07].
   `n+43` is the guard the flip was missing: a grid clamped to a first session three weeks ago is
   ~20 squares wide and EVERY square is one of the last 21, so the whole thing anchored right and
   the label ran off the left of the panel and was cut [2026-09-13, Dodo: "the hover text gets
   truncated"]. Six weeks of squares is more than a label is wide, so past that the flip is safe
   and before it there is nothing to flip away from. */
.ygrid .d:nth-child(n+43):nth-last-child(-n+21)[data-t]:hover::after{left:auto;right:0}
/* One day's quick actions, shown when its square is clicked. `:target`, not a script — and under
   the grid rather than beside the square, so the scroll box cannot clip it and the answer appears
   in the same place every time [2026-09-07]. */
/* One day's quick actions, as a card over the page. It does not move the page behind it — that is
   the whole reason it is not a `:target` panel any more [2026-09-07]. Paper's own card: the panel
   fill, the 6px radius, no shadow. */
/* Hidden until it is asked for. `display` is set on `:not([hidden])` and nowhere else: an author
   rule of `display:flex` on the element itself beats the browser's own `[hidden]{display:none}`,
   which is exactly how this card came to sit on the page permanently [2026-09-07]. */
.daypop{display:none}
.daypop:not([hidden]){position:fixed;inset:0;display:flex;align-items:center;justify-content:center;
        padding:24px;z-index:20;background:var(--veil)}
/* Small, and it closes by clicking away from it — there is nothing to hunt for. */
.daypop-card{background:var(--panel);border-radius:6px;padding:16px 18px;max-width:340px;width:100%}
.daypop .pop-when{margin:0 0 12px;font-size:16px}
.daypop .pop-when .meta{display:block;margin-top:2px;font-weight:400}
.daypop .actions{margin:0;flex-wrap:wrap}

/* A confirm: its own block under the actions, never a flex child of them. The summary reads as
   the quiet control it is, and the browser's own focus ring is replaced by this app's. */
.confirm{margin:10px 0 0}
.confirm>summary{cursor:pointer;font-size:15px;font-weight:600;color:var(--muted);
      list-style-position:outside;display:list-item;width:max-content}
.confirm[open]>summary{margin-bottom:10px;color:var(--ink)}
.confirm .note{margin:0 0 10px}
summary:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:3px}
/* The grid sits on a panel of its own: an empty day is a pale square and against the paper it
   vanished, so the ground behind it is white and the whole thing has room to breathe [2026-09-07]. */
/* the top padding is also the room the hover label sits in: overflow clips at the padding box,
   so a label above the first row stays visible */
.year-scroll{overflow-x:auto;background:var(--panel);border-radius:6px;padding:34px 20px 18px;margin:12px 0}
/* The interview as four rows of segmented buttons, all of it visible without opening anything
   [2026-09-07]. The label sits in a fixed column so the rows line up and read as one control;
   what an option means is its `title`, not four lines of the page. */
/* The prompt, before you send it: the line the buttons compose, then the box that is yours. */
.prompt{margin:0 0 18px}
.prompt .meta{margin:0 0 4px}
.prompt-line{margin:0;font-size:17px;font-weight:600}
.prompt .field{margin-top:10px}

.modes{margin:0 0 16px}
.mode{display:flex;align-items:baseline;gap:12px;padding:7px 0;border-top:1px solid var(--line)}
.mode-label{flex:0 0 108px;color:var(--muted);font-size:13px}
.mode .segs{display:flex;flex-wrap:wrap;gap:6px}
.mode .opt{padding:7px 12px;font-size:14px;font-weight:600}

/* The session as a list you can read between sets: one row per movement, its dose beside the name,
   everything else behind the row's own summary. The marker sits on the row, so the whole row is
   the control [2026-09-07]. */
.list.plan>li{padding:0;border-top:1px solid var(--line)}
.movement>summary{cursor:pointer;padding:12px 0;list-style-position:outside}
.movement .mv{display:flex;flex-wrap:wrap;gap:4px 12px;align-items:baseline}
.movement .rx{color:var(--muted)}
.movement>summary .meta{display:block;margin-top:2px}
.movement[open]>summary{border-bottom:1px solid var(--line);margin-bottom:14px}
.movement>*:last-child{margin-bottom:14px}

/* The book's own page, with the matched words boxed over it. The boxes are positioned in per-cent
   of the picture, so they hold at any width; the fill is the same warm mark the text uses. */
.bookpage{position:relative;display:block;max-width:560px;margin:12px 0}
.bookpage img{display:block;width:100%;height:auto;border-radius:4px;background:var(--panel)}
.bookpage .hl{position:absolute;background:var(--mark-box);border-radius:2px;pointer-events:none}

/* A book page quoted whole, with what the index matched marked. The mark is a fill, not a colour
   change — Paper has one accent and it belongs to actions [2026-09-07]. */
.narrative.book{max-height:26em;overflow-y:auto;white-space:pre-wrap;font-size:14px;line-height:1.65}
mark{background:var(--mark);color:inherit;padding:0 1px;border-radius:2px}

/* The session rendered for reading, and the same text hidden for the clipboard. */
.plan-text{display:none}                       /* the clipboard's copy, not the reader's */
.plan-doc{background:var(--panel);border-radius:6px;padding:20px 24px;margin:12px 0 0;max-width:none}
.plan-doc h1{font-size:26px;margin:0 0 4px}
.plan-doc p{margin:0 0 10px;max-width:66ch;white-space:pre-wrap}

/* An open question is a question: it carries the field that answers it, on its own line under the
   question rather than beside it, so the question is never squeezed by its own input. */
.list.reads li>span{display:block}
.list.reads form{display:flex;gap:8px;align-items:center;margin-top:8px}
.list.reads .field{flex:1;margin:0}

/* The key: tighter than a list of thirteen things has any right to be, with a rule between what
   you trained and the three that mean you did not [2026-09-07]. */
.year-key{display:flex;flex-wrap:wrap;align-items:center;gap:4px 12px;color:var(--muted);font-size:12px;margin:12px 0 0}
.year-key .sep{width:1px;height:14px;background:var(--line);margin:0 2px}
/* The header's own pill for a plan being written went on 2026-09-13 ("remove this progress thing
   on the nav bar, we dont need it"), and its styles with it. `.status` under the exchange is the
   one that remains, and it still carries the breathing dot. */

.year-key .key{display:inline-flex;align-items:center;gap:5px;white-space:nowrap}
.year-key .sw{width:11px;height:11px;border-radius:3px;display:inline-block;flex:none}
/* ── narrow panes ────────────────────────────────────────────────────────────────────────────
   Under about 820px the top bar cannot hold the brand and the four nav links on one line: the nav
   wrapped onto two lines and the brand dropped below its baseline. So the header stacks — brand
   on its own line, the nav under it as a wrapping row of links (the user's name is last in that
   row, as it is in the markup) — and the 32px gutters close to 20px. Answer grids and the
   two-column block go to one column, as DESIGN.md says; chip rows already wrap. */
/* Between a wide desk and a phone, two across rather than three — a third of a 900px column is
   too narrow for an option's sentence. */
@media (max-width:1000px){
  .opts{grid-template-columns:repeat(2,1fr)}
}

@media (max-width:820px){
  .talk{max-height:62vh}
  .asked{max-width:92%}
  .reply.card{padding:14px}
  .move .body{padding-left:0}
  .ask-hint{display:none}                       /* a phone sends by the button */
  /* the brand takes the first row on its own; the menu and who you are share the second */
  .top{flex-wrap:wrap;gap:14px 16px;padding-top:16px;padding-bottom:16px}
  .top .brand{flex-basis:100%;margin-right:0}
  .nav{gap:16px;margin-right:auto}
  .two,.opts,.verdict-card{grid-template-columns:1fr}
  h1{font-size:34px}
  .top,.wrap,.foot{padding-left:20px;padding-right:20px}
}

/* The Phase 2/3 bridge section is gone: with the Case File and the landing page redrawn, no
   template emits `.empty`, `.plain`, `.filters`, `.tabs`, `.hit`, `pre.narrative` or `.display`
   any more. Every rule in this file is now a Paper component. */

/* a book's contents: one indent per outline level, and the jump box beside the page links */
.contents .l1{padding-left:1.5rem}
/* the book itself: tall enough to read in, and it keeps its own scrollbar */
.reader{width:100%;height:78vh;min-height:520px;border:1px solid var(--line);border-radius:3px;background:#fff}
.contents-wrap{margin-top:18px}
.contents-wrap>summary{cursor:pointer;font-weight:500;padding:6px 0}
.picker.inline{display:inline-flex;gap:6px;align-items:center}
.bookpage{display:block;max-width:100%;margin:0 0 12px;background:#fff;border-radius:2px}

/* A demonstration sits with the words about the movement, not above them: you read the setup and
   the cue, then check the picture. Capped so a wide GIF cannot set the column width [2026-09-13]. */
.demo{margin:.6rem 0 0}
.demo img{display:block;max-width:min(100%,22rem);border-radius:3px}

/* ── sitting ────────────────────────────────────────────────────────────────────────────────
   The timer is the page, so the clock is the biggest thing on it: the same serif the verdict is
   set in, at a size you can read across a room with your eyes half open. Everything else on the
   panel is quiet, because the point of the page is to stop looking at it [2026-09-13]. */
.sit{padding:24px 20px}
.sit .q{margin-bottom:16px}
.sit .opts.lengths{grid-template-columns:repeat(6,1fr);max-width:560px}
/* Four of them, and wider than a number needs because each says what it is [2026-09-13; five
   became four when the two synthesised pads went, three when "Nothing" stopped being a card you
   press, and four again when the white noise arrived, 2026-09-16]. It sits below the clock rather
   than beside the lengths, because the background outlives the sit and the timer must not be able
   to hide it. */
.sit .opts.sounds{grid-template-columns:repeat(4,1fr);max-width:560px}
.sit fieldset.sound{margin-top:22px}
.sit .opt{text-align:center;padding:10px 6px}
.sit .opt b{font-size:19px}
.sit .opt small{font-size:11px}
.clock{font:500 72px/1 var(--serif);margin:6px 0 10px;letter-spacing:-.02em;
       font-variant-numeric:tabular-nums}
#sit-live .note{margin:0 0 16px}

/* The days-sat calendar: one hue at four steps, because a sit has a length and not a kind. The
   training grid's eleven colours answer "what did I do"; this answers "how long did I sit", and a
   ramp is what a quantity looks like. Same squares, same geometry, same stylesheet. */
:root{--m0:#D8DDDA; --m1:#B3BADF; --m2:#8D95CC; --m3:#666FB4; --m4:#434B92;
      /* the same four steps with the colour taken out, for a practice shown as context */
      --g0:#EEF2F7; --g1:#DCE1DE; --g2:#B4BDB8; --g3:#8B9791; --g4:#667069}
@media (prefers-color-scheme: dark){
  :root{--m0:#333B38; --m1:#414A85; --m2:#5761A8; --m3:#7580C6; --m4:#98A2E0;
        --g0:#1F2725; --g1:#2D3632; --g2:#3E4A45; --g3:#55615B; --g4:#6E7A73}
}
.d.m0{background:var(--m0)}
.d.m1{background:var(--m1)}
.d.m2{background:var(--m2)}
.d.m3{background:var(--m3)}
.d.m4{background:var(--m4)}
/* The roomy grid [2026-09-13, Dodo with a screenshot: "match the heatmap style to this, not the
   colors but the box style, spacings"]. Bigger squares on a tighter gap, and a radius that reads
   as a tile rather than a pixel: the same calendar, drawn to be looked at rather than scanned past.

   No hairline here, which is a deliberate departure from the rule above it and worth being honest
   about. The year grid carries a boundary because an empty day on a white panel is 1.24:1 and
   WCAG 1.4.11 lets a boundary do what a fill cannot. Dropping it buys the tile look and costs
   that: measured, an empty square is 1.37:1 on the light panel and 1.33:1 on the dark one, and
   each step of the ramp is 1.39–1.69:1 from the one beside it. Neither is 3:1 and neither is
   claimed to be.

   What makes it defensible is that the fill is not the only channel: every square carries its
   exact minutes in a `title` and again in the hover label, so the value is READABLE and not only
   shaded — and end to end the ramp is 5.7:1 in the light ground and 4.7:1 in the dark, so an empty
   day and a long sit are never in question. If that trade ever looks wrong, the fix is one line:
   drop `.year.roomy .d{border:0}`. */
.year.roomy .ygrid{grid-template-rows:repeat(7,22px);grid-auto-columns:22px;gap:5px}
.year.roomy .d{border:0;border-radius:6px}
/* THE TRAINING YEAR'S HAIRLINE CAME OFF EVERYWHERE [2026-09-15, Dodo: "match the heatmap on the
   trainer to the one we put on the landing page"]. It survived on the four kinds of empty day —
   no entry, rested, ill, a plan still waiting — because those four sit within a fifth of each
   other and a boundary was doing what the fill could not [2026-09-13]. It was raised once against
   the landing page and lifted there; asked for again, on the trainer, and the argument is now
   settled the other way for all of them.

   WHAT IT COSTS, kept here because the reason it existed has not stopped being true: no-entry
   against rest is 1.23:1 in the light scheme and 1.40:1 in the dark, against the 3:1 WCAG 1.4.11
   asks of a boundary — so those two are told apart by the hover label and the `title`, not by
   looking. That is the same trade the sit and journal ramps take a few rules above, for the same
   stated reason, and it is now taken by all three calendars rather than by two of them. Ill and
   prescribed are unaffected: they carry a drawn slash and a drawn dot, which is a channel a fill
   never was, and two a11y tests hold them there. To put it back, this comment is where the
   selector was. */
.year.roomy .d.pad{background:none}
/* The frame steps with the squares. A month name sits over its week and a weekday over its row
   because the three grids share a pitch, and the roomy grid changed the square (16→22) and the gap
   (8→5) in the squares' grid alone: every label drifted 3px a week and 3px a row away from what it
   named [2026-09-13, Dodo, with a screenshot: "the month name is placed wrongly"]. */
.year.roomy .ymonths{grid-auto-columns:22px;gap:5px}
.year.roomy .ydow{grid-template-rows:repeat(7,22px);gap:5px}
.year.roomy .ydow span{line-height:22px}
.d.g0{background:var(--g0)}
.d.g1{background:var(--g1)}
.d.g2{background:var(--g2)}
.d.g3{background:var(--g3)}
.d.g4{background:var(--g4)}
/* The journal's ramp [2026-09-13]: entries, not minutes, and a warm hue so the three calendars in
   this app are told apart at a glance — training is many colours, sitting is indigo, writing is
   ink on paper. Measured the way the sitting ramp was, and the same trade taken for the same
   reason, with the numbers to show it rather than assert it:

   light — j0 vs j1 1.22:1, j1 vs j2 1.46:1, j2 vs j3 1.61:1, j3 vs j4 1.73:1; the empty square
   (j0) is 1.40:1 on the panel; end to end, j0 vs j4 is 4.97:1.
   dark — j0 vs j1 1.36:1, j1 vs j2 1.56:1, j2 vs j3 1.64:1, j3 vs j4 1.60:1; j0 is 1.27:1 on the
   panel; end to end, j0 vs j4 is 5.59:1.

   No step clears 3:1 against its neighbour, in either scheme, and neither does the empty square
   against the panel — that is not claimed. What makes it defensible is that the fill is not the
   only channel: every square carries its exact count in a `title` and again in the hover label,
   so the value is READABLE and not only shaded. If that ever looks wrong the fix is one line:
   `.year.roomy .d{border:0}`. */
:root{--j0:#DCDAD4; --j1:#D9C3A0; --j2:#C09F6C; --j3:#9C7940; --j4:#71551F}
@media (prefers-color-scheme: dark){
  :root{--j0:#3A362F; --j1:#5A4823; --j2:#7C6431; --j3:#A2874C; --j4:#C9AE72}
}
.d.j0{background:var(--j0)}
.d.j1{background:var(--j1)}
.d.j2{background:var(--j2)}
.d.j3{background:var(--j3)}
.d.j4{background:var(--j4)}
.year-key .sw.m0{background:var(--m0)}
.year-key .sw.m1{background:var(--m1)}
.year-key .sw.m2{background:var(--m2)}
.year-key .sw.m3{background:var(--m3)}
.year-key .sw.m4{background:var(--m4)}
.year-key .sw.j0{background:var(--j0)}
.year-key .sw.j1{background:var(--j1)}
.year-key .sw.j2{background:var(--j2)}
.year-key .sw.j3{background:var(--j3)}
.year-key .sw.j4{background:var(--j4)}
@media (max-width:820px){
  .sit .opts.lengths{grid-template-columns:repeat(3,1fr)}
  .sit .opts.sounds{grid-template-columns:repeat(2,1fr)}
  .clock{font-size:56px}
}

/* The meditation conversation wears the app's chat components as they are — `.chat`, `.talk`,
   `.turn`, `.asked`, `.reply.doc`, `.composer`, the send button — so the two tabs cannot drift
   into looking like two products [2026-09-13]. These are the only rules it needs of its own. */
/* the last item of the composer's menu, kept apart because it is not another way to start */
.menu-list li.apart{border-top:1px solid var(--line);margin-top:4px;padding-top:4px}
/* The turn that is waiting for an answer borrows the streaming caret, because waiting for one and
   watching one arrive should not look like two different things. */
.turn.waiting .reply.doc{min-height:1.55em}
/* A square with a sit on it opens that day, so it says so to a pointer [2026-09-13]. */
.year.roomy .d[data-date]{cursor:pointer}
/* …except on the journal, where a square opens nothing [2026-09-15]. The attributes stay because
   the hover label is built from the same render, but a pointer cursor over something that cannot
   be pressed is the page promising an action it does not have. */
#journal-record .year.roomy .d[data-date]{cursor:default}

/* How far through a sit is. The clock says how much is left; this says how much is left of what,
   which a number cannot [2026-09-13]. It fills from empty, so it reads as time spent rather than
   time owed, and it is the only thing on the panel that moves. */
.sit-bar{height:4px;border-radius:999px;background:var(--soft);overflow:hidden;max-width:420px;
     margin:0 0 14px}
.sit-bar span{display:block;height:100%;width:0;background:var(--accent);border-radius:999px;
     transition:width 300ms linear}
@media (prefers-reduced-motion: reduce){.sit-bar span{transition:none}}

/* a Start button that has been used is not a button any more, it is a fact */
.reply.card .meta.sat{font-size:14px}
/* the popup's own form: the two facts about a rest day, and the way to take it off */
.daypop-card .opt{margin:12px 0 0}
.daypop-card .field{margin-top:10px}
.daypop-card .actions{margin-top:14px}
.daypop-clear{margin-top:10px;padding-top:10px;border-top:1px solid var(--line)}

/* Editing a day from the day [2026-09-13]. Folded, so a day you are only reading is not a form,
   and the summary is a word rather than a button — it opens a panel, it does not do anything. */
.edit-day{margin:10px 0 0}
.edit-day > summary{font-size:14px;color:var(--muted);cursor:pointer;padding:4px 0;width:max-content}
.edit-day > summary:hover{color:var(--ink)}
.edit-day .opt{margin-top:10px}
/* …and answering a question where it is asked: one line, the box and the word. */
.answer-read{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:8px 0 0;flex-basis:100%}
.answer-read input[type=text]{flex:1;min-width:200px;font:inherit;font-size:14px;
      background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:8px;
      padding:7px 11px}
.answer-read input[type=text]:focus-visible{outline:2px solid var(--accent);outline-offset:1px}

/* ── the numbers, as a dashboard ─────────────────────────────────────────────────────────────
   [2026-09-13, Dodo: "i want it to look like an analytics dashboard"]. Same cards as the chips
   they replace — this app has one surface and one radius — but with the two things a chip never
   had: the same number from the window before, and the metric's own shape under it. A card is a
   column, so the eye reads label, number, change, shape, five times across. */
.metrics{display:grid;grid-template-columns:repeat(auto-fit,minmax(168px,1fr));gap:10px;margin:0}
.metric{background:var(--panel);border-radius:12px;padding:14px 16px 12px;display:flex;
     flex-direction:column;gap:2px;min-width:0}
.metric .k{font-size:12px;color:var(--muted);letter-spacing:.01em}
.metric .v{font:500 30px/1.1 var(--serif);letter-spacing:-.01em;font-variant-numeric:tabular-nums}
/* The direction is an arrow, a sign and a word — never colour alone, and never green or red:
   fewer training days with more sets in them is a denser month, not a worse one. */
.metric .delta{font-size:12px;font-variant-numeric:tabular-nums;margin-top:2px;color:var(--muted)}
.metric .delta.up{color:var(--up)}
.metric .delta.down{color:var(--down)}
.metric .delta.flat{color:var(--muted);opacity:.8}
.metric .spark{display:block;width:100%;height:26px;margin-top:10px}
@media (max-width:820px){.metrics{grid-template-columns:repeat(auto-fit,minmax(140px,1fr))}}
/* the two charts on the same paper as the cards above them, so the section is one surface */
.two.charts{gap:10px;margin-top:10px}
.two.charts > .panel{padding:16px 18px 14px;margin:0}
.two.charts h4{margin-top:0}
/* The journal's unlock panel. It is the whole page until the key is in hand, so it is centred and
   given room rather than tucked into a corner: what it is asking for is the thing without which
   there is nothing to show. */
.unlock{max-width:34rem;margin:40px auto}
.unlock h2{margin-top:0}
.unlock .field{display:block;margin:16px 0}
.unlock .field span{display:block;font-size:13px;color:var(--muted);margin-bottom:6px}
.unlock .field input{width:100%;padding:10px 12px;border:1px solid var(--line);border-radius:10px;
  background:var(--panel);color:var(--ink);font:inherit;font-size:15px}
.unlock .field input:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
.unlock .warn{color:var(--attention);font-size:14px}
.rewrap{margin-top:20px;border-top:1px solid var(--line);padding-top:12px}
.rewrap>summary{cursor:pointer;color:var(--muted);font-size:14px}

/* The writing surface. The serif and a wider line, because this is the one place in the app where
   somebody writes at length rather than reads a prescription.

   THE EDITOR IS QUILL [2026-09-14], and quill.snow.css comes with it — a light-grey toolbar, blue
   #06c actives, Helvetica 13px and a white tooltip, none of which belongs to this app or survives
   a dark room. Everything below re-states those in tokens, so the editor turns with the rest of
   the page and the writing surface keeps the serif and the line it had. What is NOT re-stated is
   the structure: the picker mechanics, the list counters and the check-box glyphs are snow's, and
   re-implementing them would be re-implementing the reason for taking a library.

   Each rule is scoped under `.writer`, which outranks snow's own selectors on specificity — so it
   does not matter that the journal page loads quill.snow.css after this file. */
.writer{padding:0;overflow:hidden}
.writer .tools.ql-toolbar.ql-snow{display:flex;align-items:center;gap:2px;padding:6px 8px;
  border:0;border-bottom:1px solid var(--line);background:var(--bg);flex-wrap:wrap;
  font-family:var(--sans)}
/* Every toolbar rule carries `.tools` as well as `.ql-snow` [2026-09-14]. Not decoration: snow's
   own selectors are `.ql-snow.ql-toolbar button:hover` and the like, which ties or beats a plain
   `.writer .ql-snow …` — and quill.snow.css is loaded after this file, so a tie goes to snow. One
   more class on this side settles it without an `!important` anywhere. */
.writer .tools.ql-toolbar.ql-snow .ql-formats{display:flex;align-items:center;margin:0;
  padding:0 6px;border-right:1px solid var(--line)}
.writer .tools.ql-toolbar.ql-snow .ql-formats:last-child{border-right:0}
.writer .tools.ql-toolbar.ql-snow button{width:28px;height:28px;padding:4px;border-radius:8px;float:none}
.writer .tools.ql-toolbar.ql-snow button:hover,
.writer .tools.ql-toolbar.ql-snow .ql-picker-label:hover{background:var(--soft-hover)}
.writer .tools.ql-toolbar.ql-snow button:focus-visible,
.writer .tools.ql-toolbar.ql-snow .ql-picker-label:focus-visible{outline:2px solid var(--accent);
  outline-offset:1px}
/* The icons are SVG with `.ql-stroke` and `.ql-fill` on their parts — snow paints them #444 and
   #06c, which are two more colours this app does not have. */
.writer .tools.ql-toolbar.ql-snow .ql-stroke,.writer .tools.ql-toolbar.ql-snow .ql-stroke-miter{stroke:var(--muted)}
.writer .tools.ql-toolbar.ql-snow .ql-fill,.writer .tools.ql-toolbar.ql-snow .ql-stroke.ql-fill{fill:var(--muted)}
.writer .tools.ql-toolbar.ql-snow button.ql-active .ql-stroke,.writer .tools.ql-toolbar.ql-snow button:hover .ql-stroke,
.writer .tools.ql-toolbar.ql-snow button.ql-active .ql-stroke-miter,
.writer .tools.ql-toolbar.ql-snow button:hover .ql-stroke-miter{stroke:var(--accent)}
.writer .tools.ql-toolbar.ql-snow button.ql-active .ql-fill,.writer .tools.ql-toolbar.ql-snow button:hover .ql-fill,
.writer .tools.ql-toolbar.ql-snow button.ql-active .ql-stroke.ql-fill,
.writer .tools.ql-toolbar.ql-snow button:hover .ql-stroke.ql-fill{fill:var(--accent)}
.writer .tools.ql-toolbar.ql-snow .ql-picker{color:var(--ink);font:500 13px var(--sans)}
.writer .tools.ql-toolbar.ql-snow .ql-picker-label{border-radius:8px;padding:0 6px;border:0}
/* Snow greys the expanded label out to #ccc — an open picker whose own name has gone faint. */
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label{background:var(--soft);
  color:var(--ink);border-color:transparent}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke{
  stroke:var(--ink)}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-fill{fill:var(--ink)}
.writer .tools.ql-toolbar.ql-snow .ql-picker-options{background:var(--panel);border:1px solid var(--line);
  border-radius:10px;padding:6px;box-shadow:none}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options{border-color:var(--line)}
.writer .tools.ql-toolbar.ql-snow .ql-picker-item{color:var(--ink);border-radius:6px;padding:3px 6px}
.writer .tools.ql-toolbar.ql-snow .ql-picker-item:hover,
.writer .tools.ql-toolbar.ql-snow .ql-picker-item.ql-selected{color:var(--accent);background:var(--soft)}

/* The font picker's seven rows, in plain words rather than CSS jargon [2026-09-14]. Quill draws a
   picker row's text from the `content` of a `::before` on the row with that `data-value` — there is
   no label markup to write, only this. Snow ships its own two of these, for "serif" and
   "monospace"; the six new values below have none of their own, and a whitelist entry with no
   label here is a blank, unreadable row in the dropdown. `.writer …` is two classes past snow's own
   worst `.ql-font` selector for the same reason every other toolbar rule in this file carries
   `.tools` and `.ql-snow` together — see the note above the toolbar block. The default row (no
   `data-value` at all) keeps its generic selector last so the `[data-value=…]` rows, which are more
   specific, win over it regardless of source order. */
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=reading]::before,
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=reading]::before{
  content:"Reading"}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=sans]::before,
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=sans]::before{
  content:"Sans"}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=mono]::before,
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=mono]::before{
  content:"Mono"}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=rounded]::before,
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=rounded]::before{
  content:"Rounded"}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=condensed]::before,
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=condensed]::before{
  content:"Condensed"}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=oldstyle]::before,
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=oldstyle]::before{
  content:"Old style"}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=typewriter]::before,
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=typewriter]::before{
  content:"Typewriter"}
/* Two different strings for the same row [2026-09-15, from a screenshot: the closed button read
   "Reading" on one line and "(default)" on a second, which fell out of the button and landed on
   top of the open menu]. The dropdown row has a whole line to itself and can afford to say which
   one is the default; the button is a fixed slot in a toolbar and can only afford one word. They
   were the same declaration, so the longer text set the button's width and then wrapped anyway.
   The `nowrap` below is the guarantee rather than the fix: no label on this toolbar can wrap, so
   a future row named something long breaks the width and not the layout. */
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-label::before{content:"Writing"}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-item::before{
  content:"Writing (default)"}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-item::before{
  font-family:var(--writing)}
/* Every picker label on this toolbar, not only the font's: one line, and a width that does not
   jump as the selection changes from "Sans" to "Condensed". */
.writer .tools.ql-toolbar.ql-snow .ql-picker-label{white-space:nowrap;overflow:hidden}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font{width:auto;min-width:9.5em}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-options{min-width:11em}
/* Each dropdown row previewed in its own face, the way snow already previews its stock two — a
   picker is exactly where "what does this look like" belongs. Not on the closed label: the button
   that is always on screen keeps the toolbar's own sans, and only opens into the seven faces. */
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=sans]::before{
  font-family:var(--sans)}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=mono]::before{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=rounded]::before{
  font-family:ui-rounded,"SF Pro Rounded","Hiragino Maru Gothic ProN",system-ui,sans-serif}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=condensed]::before{
  font-family:"Avenir Next Condensed","Roboto Condensed","Arial Narrow",sans-serif}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=oldstyle]::before{
  font-family:"Iowan Old Style",Palatino,"Palatino Linotype",Georgia,serif}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=typewriter]::before{
  font-family:"American Typewriter","Courier New",Courier,monospace}
.writer .tools.ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-item::before{font-family:var(--serif)}

/* The same seven, on the writing surface itself — what the picker promises, the page has to keep.
   `ql-font-sans` through `ql-font-typewriter` are the six new whitelist values; `ql-font-serif` and
   `ql-font-monospace` are Quill's own stock two, left to Quill's own stock rules in quill.snow.css
   on purpose — nothing here overrides them, so an entry saved under either before today keeps
   rendering exactly as it did, on a menu that no longer offers either as a fresh choice. */
/* Highlighted text carries its own ink [2026-09-15, Dodo: "what is the default color for
   highlight? it is unreadable"]. It was: measured in the dark scheme, `--ink` on the highlight
   wash came to **1.02:1**, which is not low contrast but none at all — the words vanished into the
   colour meant to pick them out.

   Quill's `background` format sets a background and nothing else, so the text kept whatever the
   surface gave it, and in the dark scheme that is near-white. The fix is not a darker wash or a
   lighter one: a highlighter is dark ink on a pale band in every scheme, on paper and here, so the
   ink is pinned to the light palette's own value rather than to `--ink`, which flips. That takes
   the pair to about 11.7:1 in both schemes.

   The selector matches Quill's inline `background-color`, which is how that format writes itself —
   there is one background value on this toolbar, so matching the property is matching the
   highlight. A second colour would want a class attributor and a rule each.

   ANY element, not `span` [2026-09-15, Dodo: "highlight and bold is also unreadable"]. It was
   `span[style*=…]`, which is where Quill hangs the background on plain text — but highlight a word
   that is already bold and it puts the same inline style on the `<strong>` instead, which that
   selector did not match, so the pair went straight back to 1.02:1. Quill will hang it on whatever
   element it has to hand: strong, em, u, s, a. Matching the property wherever it lands is the only
   version of this rule that cannot be defeated by a second format being on at the time. */
.writer .sheet .ql-editor [style*="background-color"]{color:var(--ink-on-wash)}

.writer .sheet .ql-editor .ql-font-reading{font-family:var(--serif)}
.writer .sheet .ql-editor .ql-font-sans{font-family:var(--sans)}
.writer .sheet .ql-editor .ql-font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.writer .sheet .ql-editor .ql-font-rounded{
  font-family:ui-rounded,"SF Pro Rounded","Hiragino Maru Gothic ProN",system-ui,sans-serif}
.writer .sheet .ql-editor .ql-font-condensed{
  font-family:"Avenir Next Condensed","Roboto Condensed","Arial Narrow",sans-serif}
.writer .sheet .ql-editor .ql-font-oldstyle{
  font-family:"Iowan Old Style",Palatino,"Palatino Linotype",Georgia,serif}
.writer .sheet .ql-editor .ql-font-typewriter{
  font-family:"American Typewriter","Courier New",Courier,monospace}

/* The link editor. Snow gives it a white card, a grey border and a drop shadow; this app is flat
   and has a panel colour. */
.writer .ql-snow .ql-tooltip{background:var(--panel);border:1px solid var(--line);border-radius:10px;
  box-shadow:none;color:var(--ink);font:400 13px var(--sans);padding:8px 12px;z-index:2}
.writer .ql-snow .ql-tooltip input[type=text]{background:var(--bg);border:1px solid var(--line);
  border-radius:8px;color:var(--ink);font:inherit;padding:4px 8px}
.writer .ql-snow .ql-tooltip a{color:var(--accent)}

/* Anonymous Pro, vendored [2026-09-15, Dodo: "make the default text 15 pts and font anonymous
   pro"]. Four latin faces, 38 KB in all, served from `static/fonts/` rather than linked from
   Google the way this app's other two typefaces are.

   That is a deliberate difference and worth the words. The rest of the app fetches Newsreader and
   IBM Plex Sans from Google's font service on every page, which is a request carrying an IP and a
   referring URL. On most pages that is an ordinary cost of using a webfont. This page is the one
   somebody writes their diary on, and the request would say so — `/u/<slug>/journal` in the
   Referer, every time they sit down to write. Four files in the repository cost 38 KB once and say
   nothing to anybody. Licensed under the SIL Open Font License, which permits exactly this.

   Neither that host nor the at-rule for pulling a stylesheet into another one is spelled out
   anywhere above, and that is not squeamishness. tests/test_web_paper.py asserts this file
   contains neither string, so that webfonts arrive by a link in the template where they cannot
   block the first paint — and a comment naming what it is avoiding fails the very test it is
   explaining. This file already carries one scar from that exact mistake, noted at the top of it;
   mine were the second and third. */
@font-face{font-family:"Anonymous Pro";font-style:normal;font-weight:400;font-display:swap;
  src:url("fonts/anonymous-pro-400.woff2") format("woff2")}
@font-face{font-family:"Anonymous Pro";font-style:normal;font-weight:700;font-display:swap;
  src:url("fonts/anonymous-pro-700.woff2") format("woff2")}
@font-face{font-family:"Anonymous Pro";font-style:italic;font-weight:400;font-display:swap;
  src:url("fonts/anonymous-pro-400i.woff2") format("woff2")}
@font-face{font-family:"Anonymous Pro";font-style:italic;font-weight:700;font-display:swap;
  src:url("fonts/anonymous-pro-700i.woff2") format("woff2")}
:root{--writing:"Anonymous Pro",ui-monospace,SFMono-Regular,Menlo,monospace}

/* The sheet itself. `height:100%` is snow's, and it collapses in a panel that has no height of its
   own — the min-height is what gives a blank page something to be.

   15pt, not 15px [2026-09-15]. He asked in points, which is the unit a writing app states a size
   in, and the browser resolves it at 20px. That is larger than the 17px this surface used while it
   was set in a serif, and it should be: Anonymous Pro has a small x-height for its size, so the
   same number of points reads smaller here than it would in Newsreader. */
.writer .sheet.ql-container.ql-snow{border:0;height:auto;font:inherit}
.writer .sheet .ql-editor{min-height:22rem;height:auto;padding:28px 30px;
  font:400 15pt/1.6 var(--writing);color:var(--ink)}
.writer .sheet .ql-editor.ql-blank::before{color:var(--muted);font-style:normal;left:30px;right:30px}
.writer .sheet .ql-editor h1{font-size:26px;margin:18px 0 8px}
.writer .sheet .ql-editor h2{font-size:21px;margin:18px 0 6px}
.writer .sheet .ql-editor h3{font-size:18px;margin:16px 0 4px}
.writer .sheet .ql-editor h4,.writer .sheet .ql-editor h5,.writer .sheet .ql-editor h6{
  font-size:16px;margin:14px 0 4px}
.writer .sheet .ql-editor p{margin:0 0 12px}
.writer .sheet .ql-editor a{color:var(--accent);text-decoration-color:var(--accent-soft)}
.writer .sheet .ql-editor blockquote{margin:12px 0;padding-left:16px;
  border-left:3px solid var(--line);color:var(--muted)}
.writer .sheet .ql-editor code,.writer .sheet .ql-editor .ql-code-block-container{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.9em;background:var(--soft);
  color:var(--ink);border-radius:5px}
.writer .sheet .ql-editor code{padding:1px 5px}
.writer .sheet .ql-editor .ql-code-block-container{padding:12px 14px;margin:12px 0}
.writer .sheet .ql-editor hr{border:0;border-top:1px solid var(--line);margin:20px 0}
.writer .sheet .ql-editor img{max-width:100%;height:auto;border-radius:8px;margin:6px 0}
.writer .sheet .ql-editor li[data-list=checked] > .ql-ui,
.writer .sheet .ql-editor li[data-list=unchecked] > .ql-ui{color:var(--muted)}
.writer .sheet .ql-editor li[data-list=checked]{color:var(--muted);text-decoration:line-through}
#journal-state{padding:0 30px 14px;margin:0}

/* ── one entry, on its own page [2026-09-15] ───────────────────────────────────────────────
   The way back sits above the heading, and the two ways out sit under the sheet. Nothing else
   belongs on this page, so nothing else is styled for it. */
.crumb{margin:0 0 4px}
.crumb a{color:var(--muted);text-decoration:none}
.crumb a:hover{color:var(--accent)}
.entry-foot{display:flex;flex-wrap:wrap;gap:12px;align-items:center;margin-top:20px}
#journal-gone{margin:12px 0 0}

/* ── the index's entry list [2026-09-15, Dodo: "we need a new style for the past entries and
   separate them by date somehow, i dont like the current list view", then "dont show the content
   on the main page just show the date and the time", then a picture of the shape] ──────────────
   A month band, and under it one card per entry: a tile with the weekday over the day of the
   month, the clock beside it, and an eye at the far end that shows one line when it is pressed.
   No first line and no word count until it is asked for — the index is a way back into an entry,
   not a place the diary is on display.
   The LINK fills everything left of the button, so the target is the card and not the figures in
   it; the button cannot be inside it, because a button inside a link is not pressable. */
.past-head{display:flex;align-items:baseline;justify-content:space-between;gap:16px;
  flex-wrap:wrap;margin-bottom:4px}
.past-head h2{margin-bottom:0}
.month-group{margin:0 0 4px}
/* The band. Sentence case and the page's own ink — an uppercase tracked-out eyebrow is the tell
   this app took out of every other section on 2026-09-09, and a month is a heading like any
   other. The fill is what separates one month from the next now that the hairline rule is gone. */
.month-head{margin:22px 0 0;padding:9px 14px;border-radius:8px;background:var(--soft);
  font:600 17px/1.3 var(--sans);color:var(--ink)}
.month-group:first-child .month-head{margin-top:6px}
.entries .list li{padding:0;border:0}
.entry-row{display:flex;align-items:center;gap:6px;border-bottom:1px solid var(--line)}
.entries .list li:last-child .entry-row{border-bottom:0}
/* The link is the row, less the button: `flex:1` so a press anywhere across the empty middle
   still opens the entry, which is what the whole line looked like it promised. */
.entry-open{display:flex;align-items:center;gap:14px;flex:1;min-width:0;padding:10px 4px 10px 14px;
  color:var(--ink)}
.entry-open:hover{background:var(--soft)}
/* One column beside the tile: the entry where the entry goes, the clock under it. `min-width:0`
   or the clamped snippet below refuses to shrink and pushes the button off the row. */
.entry-said{display:flex;flex-direction:column;gap:3px;flex:1;min-width:0}
/* The tile: two lines, centred, on a fill one step off the row so the date reads as an object and
   not as the first word of a sentence. `tabular-nums` and a two-figure day give the column one
   edge; the fixed width keeps it there when the weekday is three letters in one locale and two
   in another. */
.daytile{display:flex;flex-direction:column;align-items:center;justify-content:center;
  width:56px;padding:7px 0;border-radius:10px;background:var(--soft);flex:none}
.daytile .dow{font:600 10px/1 var(--sans);letter-spacing:.09em;color:var(--muted)}
.daytile .dom{font:600 24px/1.1 var(--sans);color:var(--ink);font-variant-numeric:tabular-nums}
/* `.list .meta` is a block on its own line everywhere else in this app; here the clock rides
   beside the tile, so it is put back inline rather than given a new class the rest of the app
   would have to learn. It takes the space so the eye sits at the far end. */
.entry-row .meta{display:inline;margin-top:0;white-space:nowrap}
.entry-row .when{flex:1;min-width:0;font-size:13px;color:var(--muted)}
/* The eye. A real button now, so it is sized as one — 44px of target around a 20px picture, which
   is the floor a thumb needs — and it carries no fill of its own until it is pointed at, so a row
   reads as one thing rather than as a line with a widget bolted to the end. */
.peek{display:flex;align-items:center;justify-content:center;width:44px;height:44px;flex:none;
  padding:0;border:0;border-radius:8px;background:none;color:var(--muted);cursor:pointer}
.peek:hover{background:var(--soft);color:var(--accent)}
.peek .kept-back{width:20px;height:20px}
/* The snippet. Serif and in the page's own ink, because it is the one thing here that is the
   person's own writing and everything around it is furniture.

   THREE LINES, AND THE STYLESHEET IS WHAT DECIDES WHERE THEY END. `line-clamp` cuts at whatever
   three lines come to in the reader's font at the reader's width and puts the ellipsis there —
   which is a measurement JavaScript would have had to guess at, and guess again on every resize.
   The `-webkit-` spellings are not legacy: they are still the only ones Safari and Firefox
   implement, so the unprefixed `line-clamp` rides with them rather than replacing them.

   `[hidden]` NEEDS ITS OWN RULE HERE and this is the one line in this block that will break the
   feature if it goes: `display:-webkit-box` beats the user-agent's `[hidden]{display:none}` on
   specificity, so a hidden snippet would stay on screen and the shut row would show the diary. */
.snippet{font:17px/1.4 var(--serif);color:var(--ink);overflow:hidden;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;line-clamp:3}
.snippet[hidden]{display:none}

/* An entry this browser's key does not open [2026-09-13]. It has no link in it, and it has to
   LOOK like it has none: a row that reads as editable is a row somebody types into, and typing
   into that one used to autosave a placeholder over the real ciphertext. */
.entries li.sealed{cursor:default;opacity:.6}
.entries li.sealed .entry-row{padding:10px 14px}       /* no link inside it to carry the padding */
.entries li.sealed .note-text{font-style:italic;font-size:13px}

/* `.scope-line` and `.scope-note` stood here and are gone with the two lines above the journal's
   box [2026-09-15, Dodo: "remove this from journal chat"]. Nothing else ever wore either class. */
#journal-talk:empty::before{content:"Nothing asked yet.";color:var(--muted);font-size:14px}

/* ── the landing page [2026-09-15, Dodo: "build a new landing page that shows todays date and time,
   a stoic quote and the author and shows the three heatmaps one below the other"] ───────────────
   The date, one line of somebody else's thinking, and the three records. It is the only page in
   this app that asks nothing of the person reading it, and it is styled like that: no cards to
   act on, no verdict, and the one thing it makes large is the time. */
.home-when{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap;margin:0 0 40px}
.home-when b{font:500 44px/1 var(--serif);letter-spacing:-.01em;
  font-variant-numeric:tabular-nums}   /* or the whole line shifts when 21:59 becomes 22:00 */
.home-when span{font-size:16px;color:var(--muted)}

/* The quote reads as a quotation and not as an instruction: serif, the page's ink, and no
   quotation marks drawn around it — the indent and the attribution are what say what it is. A
   blockquote that shouts is the app having an opinion, which this line is expressly not. */
.home-quote{margin:0 0 8px;padding:0 0 0 18px;border-left:2px solid var(--line)}
.home-quote p{font:400 20px/1.5 var(--serif);color:var(--ink);margin:0 0 8px;max-width:34em}
.home-quote footer{font-size:14px;color:var(--muted)}

/* One record, and its name is the way into it. The heading carries the underline because it IS the
   link — there is nothing else on the row to click, so the affordance has to be on the words. */
.home-record h2{margin:48px 0 12px}
.home-record h2 a{text-decoration-thickness:1px}
.home-record .year-scroll{margin-top:0}
/* Nothing in these grids can be pressed [Dodo: "thet heatmaps cant be clicked but can only be
   hovered"], so nothing in them offers to be. The squares are spans already; this is the cursor
   that `.year.roomy .d[data-date]` would otherwise put over the two dose grids. */
.home-record .year.roomy .d[data-date]{cursor:default}

/* THE TRAINING GRID WEARS THE OTHER TWO'S SQUARE, HERE AND ONLY HERE [2026-09-15, Dodo: "can we
   match the trainer heatmap to other 2?"].

   The fills already matched — `--t-none` and `--g0` are the same hex in the light scheme. The one
   thing that differed was the hairline the training year keeps on its four kinds of empty day, and
   the reason it keeps it is measured and good: no-entry against rest is 1.23:1 light and 1.40:1
   dark, so without a boundary they are the same square, and an empty day is 1.12:1 on the panel it
   sits on. On Today, where a square is a link into that day and a thing you act on, that boundary
   stays exactly where it is.

   Here it comes off, and this is the same trade the sit and journal grids already took twice in
   this stylesheet, for the same reason: nothing on this page can be clicked, every square carries
   its day in a `title` and again in the hover label, so the value is READABLE and not only shaded.
   What it costs is that no-entry and rest are told apart by hovering rather than by looking. Sick
   and prescribed are unaffected — they carry a drawn slash and a drawn dot, which is a channel the
   fill never was. If the trade looks wrong, this block is the one line to delete. */
/* The landing page's own lift is gone with it: it overrode a rule that no longer exists, and a
   rule that cancels nothing is a rule the next person has to read twice. */
