/* ============================================================
   THE PAPER — the left half of the booth, in the house style
   ============================================================

   The screen is split the way Papers, Please splits it: the world on
   one side, the documents you work at on the other. This file styles
   the documents side — and it speaks the same visual language as the
   library app one folder up, copied by hand the way everything shared
   is copied: values, not imports.

   THE HOUSE STYLE, in one paragraph (learned from the app's css/):
   every surface is a cream plate — background #f7efd9, a 2px solid ink
   border, a small radius, and a HARD offset shadow like `2px 2px 0 ink`.
   Nothing blurs, nothing fades. Headings are Pixelify Sans on chip
   plates; body text is Nunito — and the reader's words too: the
   handwriting face was retired 6 Aug 2026 ("I dont like the cursive
   look, it should be uniform"). Hovering lifts a
   plate (-1px,-1px and a deeper shadow); pressing pushes it (1px,1px
   and a shallower one). The shadow ladder runs 1px → 2px → 3px → 4px 6px
   → 8px 10px by importance. */

:root {
  --bg-page: #f7efd9;      /* the cream every plate is made of */
  --ink: #2d3a48;          /* summer's ink; the season swaps it */
  --ink-soft: #5c6878;
  --accent: #d9b967;       /* summer's mustard — see the season block below */
  --highlight: #e5d19e;
  /* THE READER WRITES IN BLACK *(15 Aug 2026, from the user: "user
     types in black ink all over game. not blue for summer. black or
     dark dark gray … the blue hurts my eyes")*. The pen was a slate
     blue mixed to sit beside summer's blue-grey ink, and on a cream
     page every word she typed came out tinted. Words you wrote
     yourself are the one thing on the page that is not decoration, so
     they are the one thing that wears no colour: near-black, the same
     in every season. */
  --write: #23252a;        /* the colour of the reader's pen */
  /* The two colours the reader can choose, and the only two. They are
     CHOSEN, which is why they may be bright where the pen may not:
     black is what you get, yellow and red are what you reach for. */
  --pen-hilite: #ffe66d;
  --pen-red:    #c62f2f;
  /* THE MARKING HAND IS GRAY, NOT RED *(13 Aug 2026, from the user:
     "remove red ink from today page, seems too urgent")* — the strike
     through a ticked line and the "still open" note beside the date used
     to be ink-red, and red on a page reads as a correction being made.
     Neither is a correction: one is work finished, the other is a day
     left standing. Soft gray says both without raising its voice. */
  --done: #7f7a72;
}
/* The season's palette, copied from the app's css/palette.css. The pane
   carries data-season, set from the same wheel the shelf book uses.

   THE SEASONS ARE DUSTY *(15 Aug 2026, from the user: "summertime
   palette: the orange is too warm … it should be less jarring …
   dusty color palette across seasons")*. Summer was the loud one and
   the reason is compositional: every other season's accent stays in
   its page's own hue family — rose on rose, brick on tan, violet on
   lilac — and summer's alone jumped to the far side of the wheel, a
   saturated orange laid on cool blue. Two near-opposite colours at
   full strength buzz against each other; that buzz was the jarring.
   Summer's highlight was worse still, sitting at a hundred percent.

   For spring, autumn and winter the fix is one move made three times:
   keep the hue exactly where it was, keep the lightness, take the
   saturation out — grey mixed in until each reads sun-bleached rather
   than fresh. Roughly forty percent for an accent, a little under
   sixty for a highlight (a highlighter still has to look like a pen).

   SUMMER'S ACCENT IS MUSTARD, NOT ORANGE *(same day, from the user:
   "the orange has gone brown entirely", then "change orange for
   mustard yellow")*. Dusting the orange by the rule above produced a
   flat tan, and the reason is a fact about orange in particular: brown
   IS a desaturated orange. No other hue keeps its own dull form in the
   middle of the palette under a separate name. Grey out a rose and you
   get a dusty rose; grey out an orange and you get mud. Two attempts
   at a dusty-but-still-orange apricot were mixed and neither was
   right, because the request was pulling in two directions at once —
   less warmth AND still orange.

   Mustard settles it, and settles it better than a tuned orange could
   have. It is a hue that arrives dusty — nobody has to grey it down,
   the muted form is the form the colour is named for. And a yellow
   sits far nearer the page's blue on the wheel than an orange does, so
   the complementary buzz that started all this is simply gone rather
   than damped. #d9b967: 43° hue, 60% saturation, 63% lightness. It
   stays clear of --highlight, which is a much paler straw, by a wide
   margin of lightness — the two are in one family now and must never
   be allowed to converge.

   All of it judged by eye against candidate strips laid on both the
   sky and the cream. The arithmetic said the tan was correct.

   Nothing here changes what season a colour IS. Spring is still rose,
   winter still violet — the volume came down, not the palette. */
#paper[data-season="spring"] { --bg: #dfbac1; --ink: #3a2432; --ink-soft: #6b4a5c; --accent: #c9919d; --highlight: #a1c492; }
#paper[data-season="summer"] { --bg: #b9ccdc; --ink: #2d3a48; --ink-soft: #56677a; --accent: #d9b967; --highlight: #e5d19e; }
#paper[data-season="autumn"] { --bg: #c4ad7a; --ink: #3e2a20; --ink-soft: #6e5240; --accent: #ac604d; --highlight: #85a365; }
#paper[data-season="winter"] { --bg: #c5bcd4; --ink: #2e2446; --ink-soft: #5a4c78; --accent: #7969ab; --highlight: #7ea0c5; }

/* ---------- lights off ----------

   THE PAPER GOES DARK WITH THE ROOM *(5 Aug 2026, from the user: "I have
   a dark mode wiring from the previous app for the left side")*. These
   are the app's own four dark values, copied by hand out of its
   css/palette.css the same way the seasons above were — values, not
   imports, so the game never depends on the app's files.

   The SEASON'S accent and highlight are deliberately left alone: turning
   the lights off in a library does not change what season it is, and the
   accent is the one warm thing left on a dark page.

   THE FLAG GOES ON THE BODY, not on the pane, because the modals (config,
   the weather box) are the paper's own furniture but live outside it in
   the markup — flagged on #paper they would have stayed cream while
   everything behind them went dark. Two selectors, one set of values:
   the body's reaches the modals, and the pane's is written heavier than
   the season rule above so it wins inside the paper. */
body[data-dark],
body[data-dark] #paper[data-season] {
  --bg:       #1a1620;
  --bg-page:  #2b2430;
  --ink:      #f0e6d8;
  --ink-soft: #c8bea8;
  /* The pen and the marking hand, lifted for a dark page: both are mixed
     for cream and go muddy on near-black. The pen keeps its rule with
     the lights off — no colour, just the far end of the same grey, so
     black ink on cream becomes near-white ink on charcoal. */
  --write:    #f6f1e8;
  --done:     #a49c92;
  /* The highlighter keeps its yellow with the lights off — a
     highlighter that dims is a different pen. The red lifts, because
     #c62f2f on charcoal goes to mud. */
  --pen-red:  #ff8b7d;
}

/* A PLATE THAT WEARS THE SEASON KEEPS ITS DARK INK. The season's accent
   and highlight are pale warm colours and they do not go dark with the
   room — so every chip, header and button painted in one needs the ink
   it always had, or the page ends up with cream lettering on apricot,
   which is the one unreadable pairing in the whole palette. The rule is
   "dark text on a coloured plate", the same way the weekday plate has
   always worked. */
body[data-dark] .paper-date,
body[data-dark] .mode-btn:hover,
body[data-dark] .mode-btn.chosen,
body[data-dark] .weather-widget:hover,
body[data-dark] .day-arrow:hover,
body[data-dark] .cal-cell.has-entry:hover,
body[data-dark] .cal-cell.is-marked,
body[data-dark] .cal-cell.is-marked .cal-cell-mark,
body[data-dark] .mark-symbol:hover,
body[data-dark] .mark-symbol.chosen,
body[data-dark] .modal-header,
body[data-dark] .modal-label,
body[data-dark] .modal-select,
body[data-dark] .modal-input,
body[data-dark] .modal-submit,
body[data-dark] .unit-toggle button:hover,
body[data-dark] .pick-toggle button:hover,
body[data-dark] .unit-toggle button.chosen,
body[data-dark] .pick-toggle button.chosen { color: #2b2430; }

/* Except the two that sit ON the coloured header while keeping a plate of
   their own — the title and the close. Their plate went dark with the
   page, so their lettering has to stay cream or they read as two blank
   boxes stuck to the header. */
body[data-dark] .modal-title,
body[data-dark] .modal-close { color: var(--ink); }

/* AND EVERY INK MARK THAT WAS WRITTEN AS A COLOUR RATHER THAN AS A
   VARIABLE *(5 Aug 2026, from the user: "dark mode should have legible
   text: switch to light text when needed … right now the check list is
   impossible to see in dark mode")*. The list's rules, the hint in an
   empty line and the line drawn through a done one were all mixed by
   hand out of the summer ink — dark marks, at a fifth of their strength,
   which on cream is a whisper and on near-black is nothing at all. Here
   they are the same marks in cream, at the strength it takes for a
   whisper to still be a whisper you can see. */
body[data-dark] .goal-row      { border-bottom-color: rgba(240, 230, 216, .22); }
body[data-dark] .goal-text::placeholder { color: rgba(240, 230, 216, .45); }
/* The struck-through lines used to be listed here too, in dark ink-red.
   They now read --done, which the dark page swaps for itself, so there is
   nothing left to say about them here. */
body[data-dark] .paper-rule    { opacity: .3; }
body[data-dark] .modal-note    { color: #e8836b; }
body[data-dark] #configTab     { opacity: .85; }

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
  background: #17110d;
  overflow: hidden;
  /* A phone turned sideways used to swell the type on its own — the
     browser's old "help" for pages never meant for a small screen.
     This one was, so it keeps the sizes it was drawn in. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}
/* THE PHONE'S REAL HEIGHT *(15 Aug 2026)*. On a phone, 100% of the
   window is measured as if the browser's own bars weren't there, so
   the bottom of the room sat behind the toolbar — the room's floor,
   cut off, on the one screen that can least afford it. dvh is the
   height actually visible; nothing here ever scrolls the page, so the
   bars never move and the measure never jitters. */
@supports (height: 100dvh) {
  html, body { height: 100dvh; }
}
body {
  display: flex;
  /* No double-tap zoom. Every press in the booth means something —
     a tick, a turn, a card — and a second quick one is another press,
     not a magnifying glass. Pinch still works. */
  touch-action: manipulation;
}

/* ---------- the two panes ---------- */

#paper {
  position: relative;
  /* The reader decides the split — the bar between the panes drags, and
     paper.js sets this pane's basis from the saved fraction. Until they
     have chosen, the paper takes what the console doesn't need. */
  flex: 1 1 auto;
  min-width: 280px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 18px 18px 12px;
  overflow-y: auto;
  background: var(--bg);
  box-shadow: inset -10px 0 22px rgba(40, 24, 10, .22);
  font-family: 'Nunito', system-ui, sans-serif;
  color: var(--ink);
}

#stage {
  position: relative;                /* the narration hangs off this */
  flex: 0 0 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 14px;
  background: #17110d;
  /* The room stays in its half, whatever happens. fitScreen sizes the
     canvas to fit, but if any squeeze ever leaves the console wider than
     the stage, the centred overflow would bleed LEFT across the seam and
     over the paper — clip it at the wall instead. */
  overflow: hidden;
}
#screen {
  /* auto, not pixelated: the canvas's inner pixels are prescaled to a
     whole multiple finer than the glass (see fitScreen in pixel.js),
     so the browser's only job is a smooth SHRINK — `pixelated` here
     would re-cut the stair-steps the prescale exists to avoid. */
  image-rendering: auto;
  display: block;
  outline: none;
  box-shadow: 0 0 0 2px #0d0a07, 0 12px 40px rgba(0,0,0,.6);
}

/* ---------- the top row: the clock and the weather ---------- */

.paper-top {
  display: flex;
  align-items: flex-start;
  /* One row, not three points pinned to the edges — the weather chip's
     width changes as its text does ("add weather" vs "72° / 61°"), and
     space-between let that reshuffle the gap on both sides of the title
     plate, which read as the whole row drifting left and right. Now the
     three sit together as a single cluster, flush left, and only ever
     grow rightward. */
  justify-content: flex-start;
  gap: 10px;
  /* The row itself is allowed to overflow #paper rather than squeeze its
     three plates — without this a narrow pane (a small window, or the
     splitter dragged over) shrank the clock, title and weather chip
     below their own text, and the letters ran past the edge of the box
     they sit on. #paper's overflow-y: auto turns this into overflow-x:
     auto too, so a too-narrow pane scrolls the row instead of crushing
     it. */
  overflow-x: visible;
}

/* The clock, the title plate and the weather chip keep their own size no
   matter how wide or narrow #paper is — flex-shrink and flex-grow both
   off, so the window and the splitter can move freely without touching
   them. */
.stardew-clock-frame,
.library-title-box,
.weather-widget {
  flex: 0 0 auto;
}

/* The clock, exactly the app's: a wooden frame, the day on a plate that
   takes the colour of the weekday, the time on a dark face. */
.stardew-clock-frame {
  background: linear-gradient(to bottom, #c59d6a 0%, #a97f4b 55%, #8b6437 100%);
  border: 3px solid var(--ink);
  border-radius: 6px;
  padding: 4px;
  display: flex;
  flex-direction: column;
  box-shadow: 3px 3px 0 var(--ink),
              inset 0 0 0 2px rgba(0,0,0,.18),
              inset 2px 2px 0 rgba(255,255,255,.25);
}
.clock-day {
  text-align: center;
  font-family: 'Pixelify Sans', monospace;
  font-size: 0.72rem;
  letter-spacing: .16em;
  color: #3a2b1a;                 /* stays dark on its pastel plate */
  background: #e6c28a;
  border: 2px solid var(--ink);
  border-radius: 3px;
  padding: 2px 0;
  margin-bottom: 3px;
}
/* The colour of the week — the app's dusty rainbow, Mon → Sun — used to
   be seven rules here. It is now ONE list, WEEK in js/pixel.js, painted
   onto the plate by paintClock and onto the band around the mug by the
   room, so the plate on this pane and the mug over there always show the
   same colour on the same day. The amber above is what shows if the
   script never runs; [data-weekday] is left on the element because it is
   still how you read the plate's day in the inspector. */

.clock-face {
  background: #1b1a18;
  border: 2px solid var(--ink);
  border-radius: 3px;
  padding: 4px 12px;
  text-align: center;
  box-shadow: inset 0 0 6px rgba(0,0,0,.6);
}
.clock-time {
  font-family: 'Pixelify Sans', monospace;
  font-size: 1.05rem;
  color: #f4e6c8;
  letter-spacing: .06em;
  text-shadow: 0 0 4px rgba(244,230,200,.35);   /* the little CRT glow */
  white-space: nowrap;
}

/* The library's name, on a plate between the clock and the weather —
   the app's own rounded title banner, copied by hand the way everything
   shared here is. Rounded rather than the app's default pill, because
   this plate is short and wide and a pill on it would read as a stretched
   oval rather than a rectangle with soft corners. */
.library-title-box {
  background: var(--bg-page);
  border: 2px solid var(--ink);
  border-radius: 12px;
  padding: 5px 16px;
  box-shadow: 2px 2px 0 var(--ink);
  text-align: center;
  /* Stretches to the clock's own height — the tallest thing on this row
     — instead of the row staying top-aligned as it does for the clock
     and the weather chip either side of it. */
  align-self: stretch;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.library-title-text {
  margin: 0;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: 0.85rem;
  color: var(--ink);
  white-space: nowrap;
}
.library-subtitle {
  margin: 2px 0 0;
  font-family: 'Nunito', sans-serif;
  font-style: italic;
  font-size: 0.72rem;
  color: var(--ink-soft);
  cursor: text;
  /* Not nowrap, unlike the name above it — this line is yours to write,
     up to 60 characters, and the plate has to hold whatever you put
     there instead of running off its edge. */
  max-width: 160px;
}
/* The box the subtitle becomes while you're changing it — sized and
   placed to sit exactly where the text it replaces was sitting, so
   nothing on the plate jumps. */
.library-subtitle-input {
  margin: 2px 0 0;
  width: 100%;
  font-family: 'Nunito', sans-serif;
  font-style: italic;
  font-size: 0.72rem;
  color: var(--ink);
  background: var(--bg-page);
  border: none;
  border-bottom: 1px solid var(--ink-soft);
  text-align: center;
  padding: 0;
}
.library-subtitle-input:focus { outline: none; }

/* The weather, the app's widget: a chip you press to say what the sky
   is really doing. The room's window agrees with what you record. */
.weather-widget {
  font-family: 'Pixelify Sans', sans-serif;
  font-size: 0.9rem;
  background: var(--bg-page);
  color: var(--ink);
  border: 2px solid var(--ink);
  border-radius: 4px;
  padding: 6px 10px;
  box-shadow: 2px 2px 0 var(--ink);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  line-height: 1;
}
/* Sits at the middle of the row, not at the top of it *(15 Aug 2026,
   from the user: "center weather next to inner library logo … instead
   of sharing upper edge")*. The row is top-aligned so the clock and the
   title plate start on one line; the chip is shorter than both, and
   hanging it from that same top edge left it looking dropped in beside
   the name rather than set against it. Centred, it reads as the name's
   companion. */
.weather-widget { align-self: center; }
.weather-widget:hover { background: var(--highlight); }
.weather-widget--empty { opacity: .75; font-style: italic; }
.weather-widget .icon { font-size: 1.1rem; line-height: 1; }

/* ---------- page ⇄ calendar ---------- */

/* Four tabs is enough to overrun a narrow pane. The row does NOT wrap
   to a second line to solve that: the paper's height is spoken for by
   the page below, and a row that quietly becomes two rows takes a line
   of the day away. It scrolls sideways instead — always one row tall,
   whatever is in it, and nothing ever out of reach. */
.mode-row {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  scrollbar-width: none;
  flex: 0 0 auto;
  /* ROOM FOR THE SHADOW TO FALL IN *(15 Aug 2026, from the user: "the
     bottom black line looks too thin compared to others")*. The row
     scrolls sideways, and a scroll box CLIPS whatever hangs outside
     it — so the 2px drop under each tab was being sliced off and only
     the 2px border showed. Every other plate on the page shows border
     AND shadow, four pixels of black along the bottom; the tabs alone
     showed two, which is exactly the thin line. The padding is the
     shadow's own size, down and to the right, so it lands inside the
     box and is drawn. Nothing moved to do this — the row simply stopped
     cutting its own tabs off. */
  padding: 0 3px 3px 0;
}
.mode-row::-webkit-scrollbar { display: none; }
.mode-btn { flex: 0 0 auto; }
.mode-btn {
  font-family: 'Pixelify Sans', sans-serif;
  font-size: 0.8rem;
  background: var(--bg-page);
  color: var(--ink);
  border: 2px solid var(--ink);
  border-radius: 4px;
  padding: 5px 12px;
  box-shadow: 2px 2px 0 var(--ink);
  cursor: pointer;
}
.mode-btn:hover { background: var(--highlight); }
.mode-btn.chosen { background: var(--accent); }
.mode-btn:active { transform: translate(1px,1px); box-shadow: 1px 1px 0 var(--ink); }

/* THE TAB IS A PLATE LIKE ANY OTHER — no lip, no second shadow *(15
   Aug 2026)*. For part of this day the row wore an extra darker band
   under its bottom edge, asked for as "a darker shadow on the bottom
   lip"; it went out at 4px, came back to 2px when the tabs read
   heavier than every other tile, and came off altogether when the user
   looked again: "the tabs page/log/study need to have the same design
   as inner library shadow, weather shadow etc". So the rule above is
   the whole of it. Border, radius and drop are now identical to the
   weather chip's, and the title box differs only in its rounder
   corner. Anything that thinks a tab needs its own shadow belongs
   somewhere else. */

/* ---------- the page: a today-book card ---------- */

.today-book {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  background: var(--bg-page);
  border: 2px solid var(--ink);
  border-top: 10px solid var(--accent);       /* the book's coloured head band */
  border-radius: 6px;
  box-shadow: 4px 6px 0 var(--ink);
  padding: 16px 16px 18px;
  min-height: 0;
}
.today-book[hidden] { display: none; }

/* The date between its page-turning arrows. The arrows are the
   calendar's own, worn smaller; at either end of the walk — the season's
   first day, the open page — the spent arrow fades rather than leaves,
   so the row never shifts underfoot. */
/* THE DATE STANDS IN THE MIDDLE OF THE PAGE *(15 Aug 2026, from the
   user: "be sure date is centered in today page column no matter the
   date")*. It used to hang off the left edge, so the plate grew
   rightward and the header sat differently on "friday, may 1" than on
   "wednesday, september 30".

   Centred by SYMMETRY, not by measuring: the row runs the card's full
   width and centres its contents, and the two arrows are the same
   fixed box, so whatever is between them has the middle of the column
   for its own middle. The arrows shift outward as the date lengthens —
   they are its brackets, they belong beside the word — but the date
   itself does not move a pixel from one day to the next. Fixed WIDTH
   rather than min-width on purpose: min-width leaves the size to the
   glyph, and ‹ and › are only the same width by the good manners of
   this typeface. */
.paper-date-row {
  align-self: stretch;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.paper-date-row .day-arrow {
  flex: 0 0 24px;
  width: 24px; min-width: 24px; min-height: 24px;
  font-size: .95rem;
}
/* And the season line under it is the date's own second line — it
   centres with it, or the header reads as two headers. */
.paper-date-row + .paper-week { text-align: center; }
.day-arrow:disabled {
  opacity: .35;
  cursor: default;
  box-shadow: 2px 2px 0 var(--ink);
  transform: none;
  background: var(--bg-page);
}
.paper-date {
  align-self: flex-start;
  margin: 0;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: .03em;
  background: var(--highlight);
  border: 2px solid var(--ink);
  border-radius: 3px;
  padding: 3px 12px;
  box-shadow: 2px 2px 0 rgba(0,0,0,.25);
  cursor: pointer;         /* a door to the calendar, not just a label */
}
.paper-date:active { transform: translate(1px,1px); box-shadow: 1px 1px 0 rgba(0,0,0,.25); }
.paper-date.is-plain { cursor: default; }        /* the recap page opens nothing */
.paper-date.is-plain:active { transform: none; box-shadow: 2px 2px 0 rgba(0,0,0,.25); }
/* `.paper-date .stale` dressed the second clause on the date — `· still
   open`, `· filed away`. Gone 15 Aug 2026 with the clause itself; the
   date is just the date now. */
.paper-week {
  margin: 8px 0 0;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .78rem;
  letter-spacing: .08em;
  color: var(--ink-soft);
  text-transform: lowercase;
}
.paper-rule { height: 2px; margin: 10px 0 4px; background: var(--ink); opacity: .18; }

/* The ＋ that lends out the pen. At rest the page shows only what was
   written — a line standing open forever reads as undone-ness — and
   the ＋ waits where the pen line would be. Pressed, it steps aside
   for the line itself; an empty line folds away when the pen leaves
   it, and the ＋ comes back. */
.pen-plus {
  align-self: flex-start;
  margin: 4px 0 0;
  padding: 0;
  min-width: 24px; min-height: 24px;
  background: none;
  border: none;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: 1.05rem;
  line-height: 1;
  color: var(--ink-soft);
  cursor: pointer;
  transition: color .15s ease;
}
.pen-plus:hover { color: var(--ink); }
.pen-plus[hidden] { display: none; }

/* THE AIMS STRIP — the day's middle part, and it FOLDS like the habits
   *(13 Aug 2026, from the user: "all three sections can have collapse
   like habits")*. Open, it is the tall part of the card: the well
   inside takes whatever height is left and scrolls. Folded, it stands
   only as tall as its label, and the paper below is simply blank —
   sections stack down the sheet like writing, they don't float to the
   floor. A <details>' children live inside its ::details-content box
   (a plain block since Chrome ~131), so the flex chain has to be
   handed through that box explicitly or the well ignores every height
   above it and grows to its content — which is exactly the overflow
   this layout exists to prevent. Engines too old to know the selector
   skip it and lay the children out directly, which also works. */
.day-strip {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.day-strip[open]::details-content {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.day-strip:not([open]) { flex: 0 0 auto; }
/* The hidden attribute must still win over display:flex — a recap page,
   or the strip taken off in CONFIG. */
.day-strip[hidden], .study-strip[hidden] { display: none; }
/* All three strips share one arrow, and it turns for each. */
.day-strip[open] > .strip-label::before,
.study-strip[open] > .strip-label::before { transform: rotate(90deg); }

/* THE BODY OF THE PAGE — your own lines and the standing aims, in one
   scrolling well. They used to be two: the written lines took whatever
   room was left and the aims took their full height, so a long day (or
   the big words, at 1.45×) pushed the aims down onto the habits, the
   habits out through the bottom border, and squeezed the pen line to
   nothing — press ＋ and a line opened where it could not be seen
   *(13 Aug 2026, from the user: "the plus sign is no longer yielding
   ability to add goals", "habit … 'biggest' font is out of bounds")*.
   One well, one scrollbar, and the habits keep their seat on the floor. */
.page-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* The well's scrollbar rides its right edge, and so did the rows —
     which put the eraser's × directly under the bar *(13 Aug 2026,
     from the user: "the x to erase is getting caught with the scroll
     up and down bar")*. A small right margin keeps every row's end —
     the × especially — out of the bar's lane. */
  padding-right: 12px;
}
#goalList {
  flex: 0 0 auto;
  margin: 0;
  padding: 0;
  list-style: none;
}
.goal-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 3px 0;
  border-bottom: 1px solid rgba(45, 58, 72, .22);
}
/* THE PENCIL AND THE ERASER on a written line (pencilFor/eraserFor,
   paper.js) — ✎ puts the pen in, × takes the line back. Kept off the
   page until they could be wanted: a mouse finds them by resting on
   the row; a finger finds them by holding the words, which puts the
   pen in and the row in :focus-within. Only YOUR lines wear them —
   the generated rows have none to show. The .habit-row reveals cover
   the project steps, which wear the same pair at the station and in
   the page's fold. */
.goal-erase,
.goal-pencil {
  flex: 0 0 auto;
  display: none;
  padding: 0;
  min-width: 20px; min-height: 20px;
  background: none;
  border: none;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .95rem;
  line-height: 1;
  color: var(--ink-soft);
  cursor: pointer;
}
.goal-pencil { font-size: .8rem; }
.goal-erase:hover, .goal-pencil:hover { color: var(--ink); }
.goal-row:focus-within .goal-erase, .goal-row:focus-within .goal-pencil,
.habit-row:focus-within .goal-erase, .habit-row:focus-within .goal-pencil {
  display: block;
}
@media (hover: hover) {
  .goal-row:hover .goal-erase, .goal-row:hover .goal-pencil,
  .habit-row:hover .goal-erase, .habit-row:hover .goal-pencil {
    display: block;
  }
}

/* THE SEAT ARROWS (moversFor, paper.js) — a stacked ▲▼ on an open
   project step, found the same two ways as the pencil. They trade the
   step's seat with its open neighbour's; done steps wear none.

   They stand OUT of the flow, in the list's left padding beside the
   tick — the step lists have no slack, and one more in-flow tool
   slid the clip left under an arriving cursor (the bait-and-switch,
   caught once already). Out of the flow, their coming and going can
   move nothing. The padding is the lane they stand in; it must be on
   the LIST as padding, not margin, so a capped list's overflow clip
   never cuts them off. */
.proj-card .habit-list { padding-left: 14px; }  /* the fold's own is below */
/* The shopping list wears the same seat arrows *(14 Aug 2026, from
   the user: "shopping list needs ability as others lists" — "edit,
   order, etc")* — same lane, same reveal, its own dataset pair. */
.shopping-list .habit-list, .shopping-list-body .habit-list { padding-left: 14px; }
.habit-row, .plan-row { position: relative; }
.goal-move {
  position: absolute;
  left: -14px;
  width: 13px;
  height: 11px;
  display: none;
  padding: 0;
  background: none;
  border: none;
  font-size: 8px;
  line-height: 1;
  color: var(--ink-soft);
  cursor: pointer;
}
.goal-move[data-step-up], .goal-move[data-shop-up] { top: 1px; }
.goal-move[data-step-down], .goal-move[data-shop-down] { bottom: 1px; }
.goal-move:hover { color: var(--ink); }
.habit-row:focus-within .goal-move, .plan-row:focus-within .goal-move { display: block; }
@media (hover: hover) {
  .habit-row:hover .goal-move, .plan-row:hover .goal-move { display: block; }
}

/* THE PAPERCLIP (clipFor, paper.js) — a project step can carry one
   address: a web page, or a document on this computer *(14 Aug 2026,
   from the user: her brother "has too many windows open" — clip them
   "into the proper line of the to do steps")*. An unclipped row keeps
   the clip beside the pencil and eraser — found the same way, by
   resting on the row or holding the words. A CLIPPED row wears it
   always: there the clip is no longer a tool but the doorway back to
   the doc, and a doorway is never hidden. */
.goal-clip {
  flex: 0 0 auto;
  display: none;
  padding: 0;
  min-width: 20px; min-height: 20px;
  background: none;
  border: none;
  font-size: .7rem;
  line-height: 1;
  cursor: pointer;
  /* The emoji ignores color, so the filter is its ink *(14 Aug 2026,
     from the user: "is there a darker version of a clip")* — the
     silver clip drawn down toward the page's own dark ink. */
  filter: grayscale(1) brightness(.5);
  opacity: .5;
}
.goal-clip:hover { opacity: .95; }
.goal-clip.is-linked { display: block; opacity: .85; }
/* On dark paper the same clip must go the other way — pale, not inky. */
body[data-dark] .goal-clip { filter: grayscale(1) brightness(1.6); }
/* The shopping list wears the same clip *(15 Aug 2026)* — a link to
   where a thing is found, same lane as the seat arrows above. */
.habit-row:focus-within .goal-clip, .plan-row:focus-within .goal-clip { display: block; }
@media (hover: hover) {
  .habit-row:hover .goal-clip, .plan-row:hover .goal-clip { display: block; }
}

/* The clip's own line, opened under the words to take (or change) the
   pasted address; emptied and left, it takes the clip off. */
.habit-row.is-clipping, .plan-row.is-clipping { flex-wrap: wrap; }
.clip-line {
  flex: 1 1 100%;
  min-width: 0;
  margin: 0 0 2px 24px;   /* under the words, clear of the tick */
  border: none;
  border-bottom: 1px dashed var(--ink-soft);
  outline: none;
  background: transparent;
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: 12.5px;
  color: var(--ink-soft);
}
.clip-line::placeholder { color: inherit; opacity: .55; }

/* A clipped row wears its clip at the END OF THE NAME, not the row's
   far edge *(14 Aug 2026, from the user: "make clip be at end of name
   when user has a link clipped")* — a real paperclip grips the words
   it belongs to. The words stop flexing and size to their content
   (field-sizing, Chrome 123+; elsewhere the input falls back to its
   default width and the clip still follows it), so the clip stands
   just past the last letter and the pencil and eraser step out BEYOND
   it — nothing an arriving cursor aims at ever moves. */
.habit-row.has-clip .goal-text, .plan-row.has-clip .goal-text {
  flex: 0 1 auto;
  field-sizing: content;
}

/* The legend under the open line — the clip's three gestures said
   plainly, standing exactly while the line does. */
.clip-note {
  flex: 1 1 100%;
  min-width: 0;
  margin: 1px 0 3px 24px;
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: 11px;
  font-style: italic;
  color: var(--ink-soft);
  opacity: .8;
}

.tick {
  flex: 0 0 auto;
  width: 15px; height: 15px;
  padding: 0;
  appearance: none;
  border: 2px solid var(--ink);
  border-radius: 2px;
  background: var(--bg-page);
  cursor: pointer;
  position: relative;
}
.tick:hover { background: var(--highlight); }
.tick.done { background: var(--ink); }
.tick.done::after {
  content: '';
  position: absolute; left: 3px; top: 0;
  width: 4px; height: 8px;
  border: solid var(--bg-page);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.goal-text {
  flex: 1 1 auto;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--write);
  /* A written line ticks on a quick press and edits on a hold (see
     goalRow, paper.js) — pan-y keeps a touch scroll that starts on
     the words scrolling, and lets pointercancel tidy the press up. */
  touch-action: pan-y;
}
.goal-text::placeholder { color: rgba(45, 58, 72, .32); }
.goal-row.is-done .goal-text {
  color: var(--ink-soft);
  text-decoration: line-through;
  /* One knob for the marking hand: --done carries the strike here and the
     "still open" note by the date, and swaps itself on a dark page. */
  text-decoration-color: var(--done);
}

/* ---------- the day's generated list: aims, then habits ----------
   Aims wear the goal rows' own clothes — same tick, same hand — with a
   quiet chip saying when the aim is wanted. The chip speaks in the
   system's voice (Pixelify), the aim in the reading face: the schedule is
   the library's business, the words are your own. */

.aim-list { margin: 0; padding: 0; list-style: none; }
.aim-text {
  flex: 1 1 auto;
  min-width: 0;
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--write);
}
.aim-row.is-done .aim-text {
  color: var(--ink-soft);
  text-decoration: line-through;
  text-decoration-color: var(--done);
}
.aim-when {
  flex: 0 0 auto;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .68rem;
  letter-spacing: .06em;
  color: var(--ink-soft);
  text-transform: lowercase;
  white-space: nowrap;
}
/* The group headings on the day's list — projects, until done, this
   week, and the rest. The same quiet voice as the habit strip's label:
   the when-chips left the rows, and the heading says it once for the
   whole group. A little air above each so the groups read as groups. */
.aim-heading {
  margin: 8px 0 2px;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .68rem;
  letter-spacing: .12em;
  color: var(--ink-soft);
  text-transform: lowercase;
}
/* The pips for an N-a-week aim: plain ink, deliberately not the weekday
   pastels — those have one job on this page and it is the habits. */
.aim-pips { flex: 0 0 auto; display: flex; gap: 3px; }
.aim-pip {
  width: 7px; height: 7px;
  border: 2px solid var(--ink);
  border-radius: 50%;
  opacity: .45;
}
.aim-pip.is-filled { background: var(--ink); opacity: .8; }

/* The habit strip: the day's floor, slim and at the bottom. The dot
   fills with the weekday's own pastel — the clock plate's colour, the
   mug band's colour — so a kept habit wears the day it was kept on. */
/* THE HABITS STAND AT THE HEAD OF THE PAGE, AND FOLD. A <details>, so
   the fold is the browser's own — one press, keyboard included — and
   the rule that used to sit above the strip now sits below it, closing
   it off from the day underneath. */
.habit-strip {
  flex: 0 0 auto;
  margin-bottom: 8px;
  padding-bottom: 6px;
  border-bottom: 2px solid var(--ink);
}
.strip-label {
  margin: 2px 0 4px;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .68rem;
  letter-spacing: .12em;
  color: var(--ink-soft);
  text-transform: lowercase;
  cursor: pointer;
  list-style: none;            /* the default triangle, in every engine */
  display: flex;
  align-items: center;
  gap: 6px;
}
.strip-label::-webkit-details-marker { display: none; }
/* Our own mark instead: a small arrow that turns down when the fold
   opens, drawn in the page's own hand rather than the browser's. */
.strip-label::before {
  content: '›';
  font-size: .9rem;
  line-height: 1;
  transition: transform .15s ease;
}
.habit-strip[open] > .strip-label::before { transform: rotate(90deg); }
.strip-label:hover { color: var(--ink); }
/* Positioned so the rows are measured FROM THE LIST, not from wherever
   the strip happens to sit on the page. Their offsets are fractional
   once the words are enlarged, and each one rounds on its own, so a
   strip that shifts by half a pixel used to hand back a cap a pixel
   short of a row's top edge — a hairline of the next row showing. */
.habit-list { position: relative; margin: 0; padding: 0; list-style: none; }
/* Habits can pile up; show the top five rows and let the rest scroll
   into view rather than pushing the day's floor off the paper. The
   height itself is set by renderDayLists — MEASURED to the top of the
   sixth row, never guessed in pixels, so the cut always lands between
   rows ("never let habits have a cut off line, it looks sloppy").
   Snap does the same for scrolling: it settles on whole rows. */
.habit-list.is-capped {
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: 2px;
  scroll-snap-type: y mandatory;
}
.habit-list.is-capped .habit-row { scroll-snap-align: start; }
.habit-row { display: flex; align-items: center; gap: 9px; padding: 2px 0; }
.habit-dot {
  flex: 0 0 auto;
  width: 14px; height: 14px;
  padding: 0;
  border: 2px solid var(--ink);
  border-radius: 50%;
  background: var(--bg-page);
  cursor: pointer;
}
.habit-dot:hover { background: var(--highlight); }
.habit-text {
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: 14px;
  font-weight: 600;
  color: var(--write);
}
/* THE CHECKLIST RULE *(14 Aug 2026)*: in any tickable row the words
   press the tick, so they say so. Scoped to .habit-row because the
   filed-away shelf wears .habit-text as a plain label. */
.habit-row .habit-text { cursor: pointer; }
.habit-row.is-done .habit-text { color: var(--ink-soft); }

/* ---------- the habit log: the wall chart's readable station ----------
   One card per habit, one cell per day. A kept day wears its weekday's
   pastel; a missed day is blank paper — no cross, no grey, no streak
   counts anywhere. The colours come inline from WEEK in pixel.js, the
   same list the clock plate and the mug band wear. */

.habit-log {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 14px 14px 20px;
  background: var(--bg-page);
  border: 2px solid var(--accent);
}

/* The shopping list's own tab *(14 Aug 2026, from the user: "insert
   'list' tab for shopping list")*: the habit log's own chrome, so the
   page's tabs read as one family. renderShoppingList fills it. */
.shopping-list {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 14px 14px 20px;
  background: var(--bg-page);
  border: 2px solid var(--accent);
}
.log-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}
/* How far it unrolls, and how big it is drawn — two little rows of
   buttons sharing the head's right end, wrapping onto two lines rather
   than crushing the title when the pane is dragged narrow. */
.log-tools { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px 10px; }
.log-extent, .log-size { display: flex; gap: 6px; }
.log-empty {
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: 14px;
  color: var(--ink-soft);
  text-align: center;
}
/* Each habit's card sits centered in the pane, and its grid stretches
   to the card's full width — seven equal columns, generous cells, so
   the record reads from a normal sitting distance instead of asking
   to be squinted at in a corner. */
.log-card { max-width: 300px; margin: 0 auto 22px; }
.log-habit-name {
  margin: 0 0 6px;
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: 15px;
  font-weight: 700;
  color: var(--write);
  text-align: center;
}
/* THE HABIT'S SEAT ARROWS *(15 Aug 2026, from the user: "give user
   ability to order their habit log page by habits … like slide up down
   which is top")* — the lists' own pair, standing in the card's right
   margin beside the name. Stacked at the TOP of the card, not at its
   corners: a card is as tall as a season, and a ▼ at the far bottom of
   that would read as belonging to the grid.

   HERE THEY STAND ALWAYS, faint until looked at, where a list's are
   hidden until the hand rests on the row. Two reasons. A list is a
   dozen rows deep and a pair of arrows on each would be a picket
   fence; a log is four or five wide cards with room to spare. And a
   button that is `display: none` cannot be reached at all without a
   mouse — not by finger, not by the tab key, since a thing that isn't
   drawn can't take focus. Absolutely positioned, they cost the card no
   room, so standing there they still move nothing.

   EVERYTHING HERE IS KEYED TO #logMode. `log-card` and
   `log-habit-name` are borrowed all over the app — the projects page's
   cards, the deck faces, the shelf's book rows — and a project card's
   own step arrows live in the left lane. This is the habit log's
   business alone. */
#logMode .log-card { position: relative; }
/* The room either side is the arrows' lane — kept on BOTH sides so the
   name stays truly centered, and so a long habit never runs under
   them. */
#logMode .log-habit-name { padding: 0 24px; }
#logMode .goal-move {
  display: block;
  left: auto;
  right: 2px;
  opacity: .38;
}
#logMode .goal-move[data-habit-up]   { top: 0; }
#logMode .goal-move[data-habit-down] { top: 11px; }
#logMode .goal-move:hover,
#logMode .goal-move:focus-visible { opacity: 1; color: var(--ink); }
.log-season-label {
  margin: 10px 0 4px;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .68rem;
  letter-spacing: .12em;
  color: var(--ink-soft);
  text-transform: lowercase;
  text-align: center;
}
/* In the year view each season folds behind its label — a summary the
   reader opens, marker riding along with the centered word. */
.log-fold summary.log-season-label {
  cursor: pointer;
  list-style-position: inside;
}
.log-grid { display: flex; flex-direction: column; gap: 4px; }
.log-row { display: flex; gap: 4px; }
.log-cell {
  flex: 1 1 0;
  aspect-ratio: 1 / 1;
  border-radius: 4px;
  /* blank: a day lived and simply not marked — still plain paper (no
     cross, no failure mark), but with a whisper of shading and a firm
     edge so the boxes hold their own against the cream */
  background: rgba(45, 58, 72, .05);
  box-shadow: inset 0 0 0 1px rgba(45, 58, 72, .25);
}
.log-cell.is-future { background: none; box-shadow: inset 0 0 0 1px rgba(45, 58, 72, .12); }
.log-cell.is-pre    { box-shadow: none; background: rgba(45, 58, 72, .10); }
.log-cell.is-today  { box-shadow: inset 0 0 0 2px var(--ink); }
body[data-dark] .log-cell         { background: rgba(240, 230, 216, .05); box-shadow: inset 0 0 0 1px rgba(240, 230, 216, .30); }
body[data-dark] .log-cell.is-future { background: none; box-shadow: inset 0 0 0 1px rgba(240, 230, 216, .14); }
body[data-dark] .log-cell.is-pre  { box-shadow: none; background: rgba(240, 230, 216, .10); }
body[data-dark] .log-cell.is-today { box-shadow: inset 0 0 0 2px var(--ink); }

/* ---------- the shrunk view ----------
   *(15 Aug 2026, from the user: "give user a 'shrunk view' so they can
   see more habits on their screen, however possible that is for a full
   13 week thing")*.

   A season is thirteen weeks, and drawn full size one habit eats a
   screen. Shrunk, the SQUARES get small — a fixed ten pixels instead of
   a share of the card — and that alone takes a season from four hundred
   pixels of page down to a hundred and fifty. Nothing is dropped and
   nothing is summarised: every day still has its own square, wearing
   the same weekday pastel it wore at full size.

   THE SHAPE DOES NOT TURN *(16 Aug 2026, from the user: "it definitely
   is wrong dimensions, it should be set to 13 weeks tall by 7 days
   wide, less the weeks before you started mid season", "it should show
   it small enough to fit two columns worth")*. The first shrunk view
   laid the season on its SIDE — a week became a column of seven, the
   thirteen weeks ran left to right. It fit, and it read wrong: a
   calendar has its days across and its weeks going down, and turned
   sideways one chart was "very long", while two habits begun in
   different weeks came out different LENGTHS — six squares against
   seven — which read as a week gone missing rather than a habit begun
   later. Days across, weeks down, at any size.

   Cells are a fixed small square rather than a share of the card, so
   the shrunk view stays shrunk however wide the pane is dragged. The
   text-size setting still zooms the whole pane, so eyes that need the
   big page keep it. */
/* AND THEY STAND IN COLUMNS *(15 Aug 2026, from the user: "shrunk mode
   for habit log: can we fit two habits side by side in column"; 16 Aug,
   "small enough to fit two columns worth")*. Seven small squares across
   is a narrow thing, so the shrunk log becomes a grid: two habits
   abreast on a normal pane, three or four when it is thrown wide, one
   when the splitter is dragged in tight. auto-fit does the deciding, so
   there is no width at which a card is squeezed narrower than its own
   chart. The head keeps the full width above them all.

   The card keeps a cap on it — the chart inside shrinks, the card does
   not spread. Let it fill a wide column and the seat arrows drift off
   to the far edge, a whole hand's width from the habit they belong
   to. */
#logMode[data-log-size="shrunk"] {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  align-items: start;               /* a shorter chart doesn't stretch */
  align-content: start;
  column-gap: 12px;
}
#logMode[data-log-size="shrunk"] .log-head,
#logMode[data-log-size="shrunk"] .log-empty { grid-column: 1 / -1; }
#logMode[data-log-size="shrunk"] .log-card { max-width: 150px; margin-bottom: 14px; }
#logMode[data-log-size="shrunk"] .log-habit-name { margin-bottom: 4px; font-size: 12px; padding: 0 18px; }
#logMode[data-log-size="shrunk"] .log-grid { gap: 2px; }
#logMode[data-log-size="shrunk"] .log-row { justify-content: center; gap: 2px; }
#logMode[data-log-size="shrunk"] .log-cell {
  flex: 0 0 auto;                   /* a day is a fixed small square */
  width: 10px;
  height: 10px;
  border-radius: 2px;
}
#logMode[data-log-size="shrunk"] .log-season-label { margin: 6px 0 3px; }

/* ---------- the plan station: an index card, opened ----------
   A pop-up of its own since 14 Aug 2026 — the paper behind keeps its
   page. pan-y keeps the finger's up-and-down for scrolling while a
   sideways throw reaches the swipe that walks the cards. */
/* IT OPENS BIG, AND THE WINDOW IS THE READER'S *(16 Aug 2026, from the
   user: "the goal planning modal feels small", "should be able to
   resize and move")*. 560 × 640 was a box you leaned into; the planner
   is where a season's habits and aims get written, so it opens on a
   generous card instead — and the height is PINNED rather than left to
   the contents, so walking the nine stops turns cards inside one steady
   holder instead of making the frame breathe in and out under the
   finger. Anything longer scrolls in the body.

   It carries by its header and sizes by the corner grip, the same
   hands the practice box and the card box wear, and its seat is
   remembered (save.planWin). This overrules the 15 Aug ruling for this
   one pop-up, at her word; collapsing is still refused everywhere. */
/* AND IT GROWS WITH THE DESK *(16 Aug 2026, from the user: "mostly
   desktop")*. A fixed 760 is a good box on a laptop and a small one
   again on a big monitor, which is the note this was answering. So the
   size is a share of the window between two walls: never smaller than
   the laptop box, never so wide that a line of habits has to be read
   across a room. The grip overrules all of it — this is only where the
   planner opens when nobody has said otherwise. */
.plan-modal .modal-box {
  width: min(clamp(760px, 62vw, 1000px), calc(100vw - 32px));
  height: min(clamp(620px, 72vh, 820px), calc(100vh - 32px));
  height: min(clamp(620px, 72dvh, 820px), calc(100dvh - 32px)); /* what is actually on screen */
  touch-action: pan-y;                 /* the sideways throw that walks
                                          the cards; the up-and-down
                                          stays the body's to scroll */
}

/* The calendar's own pop-up *(14 Aug 2026, from the user: "add a
   calendar item to game where user can open a pop up which opens the
   whole calendar")*. It opened at 380 wide, which is a month squeezed
   into a column — seven days across, each holding a number and
   whatever is marked on it. Wider *(16 Aug 2026: "optimal size on
   screen for reading")*, so a week is a week and the marks have room
   to say what they are. Still capped, so a tall month never pushes the
   close button off a short screen. */
.calendar-modal .modal-box {
  width: min(clamp(520px, 38vw, 640px), calc(100vw - 32px));
  max-height: min(680px, calc(100vh - 32px));
  max-height: min(680px, calc(100dvh - 32px));
}

.plan-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
}
.plan-title {
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .95rem;
  letter-spacing: .06em;
  color: #2d3a48;                 /* ink on the domain's own pastel —
                                     the background comes inline from
                                     GOAL_CARDS, one colour per card */
  text-transform: lowercase;
  padding: 3px 12px;
  border: 2px solid #2d3a48;
  border-radius: 4px;
  box-shadow: 2px 2px 0 rgba(45, 58, 72, .35);
}
.plan-idea { border-bottom: 1px dashed var(--ink-soft); }
/* The suggested-ideas fold — a strip like any other on the page, the
   words wrapping softly underneath. Nothing among them presses
   *(14 Aug 2026, from the user: "suggested ideas do not need to be
   clickable")*; only "+ your own…", the reader's door at the end,
   keeps the dashes and answers the cursor. The fold itself remembers
   however it was left, same as habits and the rest. */
.plan-ideas { margin: -4px 0 2px; }
.plan-ideas[open] > .strip-label::before { transform: rotate(90deg); }
.plan-ideas-words {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  margin-top: 2px;
}
.plan-idea-word {
  background: none;
  border: none;
  padding: 0;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .72rem;
  letter-spacing: .04em;
  color: var(--ink-soft);
  text-transform: lowercase;
}
.plan-own-idea {
  border-bottom: 1px dashed var(--ink-soft);
  cursor: pointer;
}
.plan-own-idea:hover { color: var(--ink); }
.plan-own-box { flex: 0 1 40%; font-size: 14px; }
.plan-section { margin-bottom: 14px; }
.plan-row { padding: 3px 0; gap: 8px; }
.plan-row .goal-text { font-size: 15px; }
.plan-row.is-filed .habit-text { color: var(--ink-soft); text-decoration: line-through; }
.plan-away {
  flex: 0 0 auto;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .64rem;
  letter-spacing: .06em;
  color: var(--ink-soft);
  background: none;
  border: 1px solid var(--ink-soft);
  border-radius: 3px;
  padding: 2px 6px;
  cursor: pointer;
  text-transform: lowercase;
}
.plan-away:hover { background: var(--highlight); color: var(--ink); }
.plan-add {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px dashed rgba(45, 58, 72, .25);
}
.plan-add .goal-text { flex: 1 1 auto; }
/* The store field on the shopping list's add row *(15 Aug 2026)* —
   narrower than the item's own words, since a store's name is
   usually the shorter of the two, and typed once then picked from
   the datalist thereafter. */
.plan-add-store { flex: 1 1 110px; min-width: 90px; }
.plan-when {
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .68rem;
  color: var(--ink);
  background: var(--bg-page);
  border: 1px solid var(--ink-soft);
  border-radius: 3px;
  padding: 2px 4px;
  max-width: 40%;
}
.plan-n {
  width: 44px;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .72rem;
  border: 1px solid var(--ink-soft);
  border-radius: 3px;
  background: var(--bg-page);
  color: var(--ink);
  padding: 2px 4px;
}
.plan-add-btn {
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .68rem;
  letter-spacing: .06em;
  border: 2px solid var(--ink);
  border-radius: 4px;
  background: var(--accent);
  color: var(--ink);
  padding: 3px 10px;
  cursor: pointer;
  text-transform: lowercase;
}
.plan-filed { margin-top: 10px; }
.plan-filed summary {
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .68rem;
  letter-spacing: .08em;
  color: var(--ink-soft);
  cursor: pointer;
  text-transform: lowercase;
}
body[data-dark] .plan-add { border-top-color: rgba(240, 230, 216, .25); }

/* ---------- the projects station ---------- */

.proj-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.proj-head .log-habit-name { flex: 1 1 auto; margin: 0; }
/* The star — the focus switch *(13 Aug 2026)*. Bare glyph, no box:
   an empty star waiting, a filled one wearing the focus. */
.proj-star {
  flex: 0 0 auto;
  font-size: .95rem;
  line-height: 1;
  color: var(--ink-soft);
  background: none;
  border: none;
  padding: 0 2px;
  cursor: pointer;
}
.proj-star:hover { color: var(--ink); }
.proj-star.is-starred { color: var(--ink); }
.habit-row .tick { width: 13px; height: 13px; }
.habit-text.is-done { text-decoration: line-through; color: var(--ink-soft); }
/* A project step's words are a pen line (stepWords, paper.js) — the
   input wears the row's own smaller hand, and a done step keeps its
   strike through the pen line too. */
.habit-row .goal-text { font-size: 14px; }
.habit-row.is-done .goal-text {
  color: var(--ink-soft);
  text-decoration: line-through;
  text-decoration-color: var(--done);
}

/* ---------- calendar mode: the app's month grid ---------- */

/* The calendar wears the season OF THE MONTH BEING LOOKED AT, not the
   one being lived in — walk back to March from a summer page and the
   grid turns spring with you. renderCalendar writes data-cal-season;
   the accents and washes are the app calendar's own pairs. */
.days-calendar {
  --cal-accent: var(--accent);
  --cal-wash:   var(--bg-page);
  --cal-motif:  '✦';
  flex: 1 1 auto;
  overflow-y: auto;
  position: relative;
  padding: 14px 14px 20px;
  background: var(--bg-page);
  border: 2px solid var(--cal-accent);
  border-radius: 8px;
  box-shadow: 1px 1px 0 var(--cal-accent);
}
/* Dusted alongside the season palette above, by the same rule and in
   both lights — the grid is a big flat sheet of one colour, so a bright
   accent shows here more than anywhere. Summer's, again, moves furthest. */
.days-calendar[data-cal-season="spring"] { --cal-accent: #c9919d; --cal-wash: #faeef1; --cal-motif: '✿'; }
.days-calendar[data-cal-season="summer"] { --cal-accent: #c6a953; --cal-wash: #f4efe0; --cal-motif: '✦'; }
.days-calendar[data-cal-season="autumn"] { --cal-accent: #ac604d; --cal-wash: #f5e9d7; --cal-motif: '❦'; }
.days-calendar[data-cal-season="winter"] { --cal-accent: #7969ab; --cal-wash: #ece8f2; --cal-motif: '❉'; }
/* In the dark, each accent lifts and each wash dims, so cells read as
   faintly lit paper rather than bright squares — the app's own pairs. */
body[data-dark] .days-calendar[data-cal-season="spring"] { --cal-accent: #deb2bc; --cal-wash: #3f2d38; }
body[data-dark] .days-calendar[data-cal-season="summer"] { --cal-accent: #dfcb90; --cal-wash: #3f362c; }
body[data-dark] .days-calendar[data-cal-season="autumn"] { --cal-accent: #cc9583; --cal-wash: #3f302a; }
body[data-dark] .days-calendar[data-cal-season="winter"] { --cal-accent: #b0a7d3; --cal-wash: #342d48; }
.days-calendar[hidden] { display: none; }
.days-calendar::after {              /* the corner motif, per season */
  content: var(--cal-motif, '✦');
  position: absolute; bottom: 8px; right: 12px;
  font-size: 1.6rem;
  color: var(--cal-accent);
  opacity: .35;
  pointer-events: none;
  font-family: 'Pixelify Sans', 'Segoe UI Symbol', sans-serif;
  line-height: 1;
}

/* the header, centred the way the app sets it: motif, season, a dot,
   then the month — words in the season's colour, not a plate */
.days-calendar-header {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 8px;
  margin-bottom: 8px;
}
.cal-season-chip {
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .9rem;
  color: var(--cal-accent);
  letter-spacing: .02em;
}
.cal-season-chip::before {
  content: var(--cal-motif);
  margin-right: 6px;
}
.cal-sep { color: var(--cal-accent); opacity: .55; }
.cal-label {
  font-family: 'Pixelify Sans', sans-serif;
  font-size: 1.1rem;
  letter-spacing: .03em;
}

/* the controls beneath: arrows to walk the months, a word to come home */
.days-calendar-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}
.cal-nav { display: flex; gap: 6px; }
.cal-actions { display: flex; gap: 6px; }
.cal-today { padding: 3px 10px; }

/* ---------- the mark-a-day box ---------- */

.mark-swatches, .mark-symbols { display: flex; flex-wrap: wrap; gap: 6px; }
.mark-swatch {
  width: 26px; height: 26px; padding: 0;
  border: 2px solid var(--ink);
  border-radius: 4px;
  cursor: pointer;
}
.mark-swatch.chosen {
  box-shadow: 2px 2px 0 var(--ink);
  transform: translate(-1px, -1px);
}
.mark-symbol {
  min-width: 30px; height: 30px; padding: 0 4px;
  font-family: 'Pixelify Sans', 'Segoe UI Symbol', sans-serif;
  font-size: 1rem;
  line-height: 1;
  color: var(--ink);
  background: var(--bg-page);
  border: 2px solid var(--ink);
  border-radius: 4px;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.mark-symbol:hover { background: var(--highlight); }
.mark-symbol.chosen { background: var(--accent); }
/* Clear is the quieter of the two doors out */
.mark-clear { background: var(--bg-page); }
.day-arrow {
  font-family: 'Pixelify Sans', sans-serif;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--ink);
  background: var(--bg-page);
  border: 2px solid var(--ink);
  border-radius: 4px;
  box-shadow: 2px 2px 0 var(--ink);
  min-width: 28px; min-height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
  padding: 0;
}
.day-arrow:hover { background: var(--highlight); }
.day-arrow:active { transform: translate(1px,1px); box-shadow: 1px 1px 0 var(--ink); }

.cal-grid-head {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
  margin-bottom: 4px;
}
.cal-weekday {
  text-align: center;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .72rem;
  color: var(--ink-soft);
  padding: 2px 0;
}
.cal-grid { display: flex; flex-direction: column; gap: 4px; }
.cal-row {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
  padding-left: 6px;
  margin-left: -6px;
  border-left: 3px solid transparent;
  border-radius: 4px 0 0 4px;
}
/* a week that filed three or more days earns its seasonal gutter */
.cal-row.is-active { border-left-color: var(--cal-accent); }
.cal-cell {
  position: relative;
  /* tall enough that a date AND a mark's sign fit inside the floor —
     so a marked week stands level with a plain one */
  min-height: 40px;
  padding: 3px 5px;
  border: 2px solid var(--ink);
  border-radius: 4px;
  background: var(--cal-wash);
  color: var(--ink);
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .85rem;
  cursor: default;
  /* number at the top, a mark's sign at the bottom — laid out in FLOW,
     not stacked, so however small the pane or big the text, the two
     can never sit on each other; the cell (and its week's row) simply
     grows to hold both. */
  display: flex;
  flex-direction: column;
}
.cal-cell--pad { border-color: transparent; background: transparent; }
.cal-cell-num { display: block; }
.cal-cell.has-entry { cursor: pointer; }
.cal-cell.has-entry:hover { background: var(--highlight); }
.cal-cell.has-entry:active { transform: translate(1px, 1px); }
/* the entry dot — skipped on marked days, whose own decoration already
   fills the cell (a dot on top reads as clutter) */
.cal-cell.has-entry:not(.is-marked)::after {
  content: '';
  position: absolute; top: 4px; right: 4px;
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--cal-accent);
}
.cal-cell.is-today { outline: 2px dashed var(--cal-accent); outline-offset: 2px; }
.cal-cell.is-future { opacity: .55; border-style: dashed; border-color: var(--cal-accent); background: transparent; }

/* a marked day: its chosen colour for a plate, its sign tucked in the
   bottom-right corner — the date keeps its own spot and its full ink
   (a sign over the number made the day unreadable, 12 Aug 2026) */
.cal-cell.is-marked {
  background: var(--mark-color, var(--cal-wash));
  cursor: pointer;
}
.cal-cell-mark {
  margin-top: auto;        /* sinks to the cell's floor */
  align-self: flex-end;    /* and over to its right corner */
  font-size: 1rem;
  line-height: 1;
  color: var(--ink);
  pointer-events: none;
}
/* a marked day still to come keeps its full colour — noting a birthday
   or a holiday AHEAD is the whole point of a mark. The dashes stay, so
   it still reads as not-yet-written. */
.cal-cell.is-future.is-marked {
  opacity: 1;
  background: var(--mark-color, var(--cal-wash));
}

/* ---------- the voice, and the foot ---------- */

/* ---------- NARRATE MODE ----------

   The room's narration, and the ONE place words are allowed near the
   pixels *(6 Aug 2026, from the user: "it should have a pop up web
   modal look not so quiet in the corner", then "or more like kingdom
   hearts: in the center?", then "change the voice to 'narrate mode'")*.

   It began as a handwritten murmur at the foot of the page — easy to
   miss, which for the room's only speech is the wrong kind of quiet.
   Now it sits where subtitles sit: bottom-centre of the game screen, a
   dark translucent plate with cream lettering, the JRPG dialogue box
   this room's whole lineage comes from.

   THE PIXELS ARE STILL WORDLESS. This box is an OVERLAY — it floats on
   top of the room the way subtitles float on a film, never drawn into
   the 240×232 buffer. The rule that words at pixel size are unreadable
   is intact; what changed is that the narrator now stands in front of
   the picture instead of whispering from the next room.

   Translucency is the one soft thing allowed here — the house is
   hard-edged everywhere else, but a subtitle plate that hides the room
   behind it is a subtitle plate in the way. No blur, no glow.

   A BUBBLE, NOT A PLATE *(8 Aug 2026, from the user: "give them a softer,
   rounder vibe", and then, when I took it for a note about the writing:
   "I just meant the design should be bubblier")*.

   It was an 8px-cornered rectangle with a hairline border — a HUD panel,
   which is the one register this room is not in. Everything else in the
   booth is hard-edged because it is furniture: plates, cards, a bezel,
   things with a thickness. The voice is not furniture. It is the only
   thing on screen that is neither the room nor the paper, and it should
   look like speech.

   So it is a speech bubble: rounded all the way to a pill at its ends,
   with generous room round the words so the line sits in the middle of
   something rather than being framed by it, and a soft warm rim instead
   of a drawn border. It also RISES a little further as it comes in, which
   is the small thing that makes it read as spoken rather than switched
   on.

   THE RULE ABOUT NO BLUR AND NO GLOW STILL HOLDS. Round is not soft-edged
   — every edge here is still exactly one pixel of nothing becoming
   something. What changed is the SHAPE, which is allowed to be as round
   as it likes, and the shadow underneath, which lifts it off the picture
   the way the house's own plates are lifted off the page. */
#narrateLine {
  position: absolute;
  /* left / bottom / max-width are measured onto it by placeNarration
     in paper.js — see the note there. These are the fallbacks for the
     instant before the first measurement. */
  left: 50%;
  bottom: 90px;
  transform: translateX(-50%) translateY(8px) scale(.97);
  max-width: min(460px, 82%);
  padding: 13px 26px;
  /* A fifth less opaque than it was *(8 Aug 2026, from the user: "and 20
     percent less opaque")*. It reads as glass over the room rather than a
     card laid on top of it, and the picture keeps going behind the words
     — which is the whole reason this was ever translucent. */
  background: rgba(24, 18, 13, .72);
  border: 2px solid rgba(240, 230, 216, .22);
  /* A pill. The radius is larger than the box can ever be tall, so the
     ends are always perfect half-circles however the line wraps. */
  border-radius: 999px;
  box-shadow: 0 4px 0 rgba(0, 0, 0, .28);
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.45;
  letter-spacing: .01em;
  text-align: center;
  color: #f6ecdd;
  opacity: 0;
  pointer-events: none;              /* it narrates; it never catches a finger */
  transition: opacity .25s ease, transform .28s cubic-bezier(.2, .9, .3, 1.2);
  z-index: 40;
}
/* Two lines of text in a 999px pill would bow out into an oval, so the
   moment it wraps it settles to a fat rounded rectangle instead — still
   bubble, no longer balloon. Rare: the lines are short now. */
@media (max-width: 900px) {
  #narrateLine { border-radius: 22px; padding: 12px 20px; }
}
#narrateLine.showing { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }

.paper-foot { display: flex; justify-content: flex-end; }
#configTab {
  border: none;
  background: none;
  padding: 4px 6px;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .72rem;
  letter-spacing: .14em;
  text-transform: lowercase;
  color: var(--ink-soft);
  opacity: .7;
  cursor: pointer;
}
#configTab:hover { opacity: 1; }

/* ---------- the modal: the app's box, for weather and config ---------- */

/* ---------- flair: the little celebration ----------
   Thrown by flairBurst (paper.js) from any completed row's tick, or
   from the CONFIG picker demoing itself. Fixed to the viewport, above
   the modals (z-index 100), untouchable, and gone when the animation
   ends. `both` matters: a bit with a stagger delay must hold its 0%
   frame (invisible, small) while it waits, not flash full-size at the
   tick. flair-fly is the default throw; confetti tumbles and bubbles
   rise on their own keyframes, set per-kind from the FLAIRS table. */
.flair-bit {
  position: fixed;
  z-index: 120;
  pointer-events: none;
  font-family: 'Pixelify Sans', sans-serif;
  line-height: 1;
  transform: translate(-50%, -50%);
  animation: flair-fly .7s ease-out both;
}
@keyframes flair-fly {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.4); }
  15%  { opacity: 1; }
  100% { opacity: 0;
         transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(1); }
}
/* Confetti: the same throw, spinning as it goes. */
@keyframes flair-tumble {
  0%   { opacity: 0; transform: translate(-50%, -50%) rotate(0deg) scale(.4); }
  15%  { opacity: 1; }
  100% { opacity: 0;
         transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy)))
                    rotate(var(--rot)) scale(1); }
}
/* Bubbles: no throw at all — a slow rise with a sway, the way a real
   bubble wanders on its way up, fading as it goes. */
@keyframes flair-rise {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.5); }
  15%  { opacity: .9; }
  45%  { transform: translate(calc(-50% + var(--dx) + 3px), calc(-50% + var(--dy) * .45)) scale(.85); }
  75%  { transform: translate(calc(-50% + var(--dx) - 3px), calc(-50% + var(--dy) * .8)) scale(1); }
  100% { opacity: 0;
         transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(1); }
}

/* THE ROOM DIMS, IT DOESN'T SLAM *(16 Aug 2026, from the user: "there
   should be some fade in when modal pops up … it is too jarring", then
   "yes fade the close too")*. The dark behind a pop-up used to arrive in
   a single frame, so the whole screen went down like a light switch
   while the box hurried in over .15s — and going the other way it
   simply blinked out. Now the shade comes up on its own gentle curve
   with the box settling inside it, and dissolves the same way when the
   box is put away.

   IT IS ONE FADE, NOT TWO. Fading the overlay takes the box with it,
   because the box is inside it — the dark and the paper leave together,
   which is why there is no separate exit for the box below.

   WHY A TRANSITION AND NOT AN ANIMATION on the way out. `hidden` is
   what every part of the app reads to know whether a box is open, and
   it has to become true the instant the reader closes it — no waiting
   on a fade. allow-discrete lets the browser hold the box on screen for
   the length of the fade while the attribute is already set, so the
   code (and the checks) see a closed box immediately and the eye sees
   it leave. @starting-style is the matching half: without it, coming
   back from display:none has nothing to fade FROM, and the box would
   snap in. */
.modal-overlay {
  position: fixed; inset: 0;
  background: rgba(45, 40, 35, .55);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  padding: 20px;
  opacity: 1;
  transition: opacity .26s ease, display .26s allow-discrete;
}
@starting-style {
  .modal-overlay { opacity: 0; }
}
/* On its way out it stops catching fingers at once — a box that is
   half-gone must not still answer a press. */
.modal-overlay[hidden] {
  display: none;
  opacity: 0;
  pointer-events: none;
}
/* EVERY POP-UP IS THE READER'S *(16 Aug 2026, from the user: "be sure
   all modals are optimal size on screen for reading and can be
   moved/expanded so there is symmetry for user experience")*. What
   started on the practice box, then the card box, then the goal
   planner is now the house manner: every box in the app carries by its
   header, sizes by the corner grip, and opens where it was left. So
   the frame lives here, on the base, and each box below says only how
   big it opens.

   position: relative is what the corner grip hangs off — without it a
   grip would find the page's corner instead of its own box's. */
.modal-box {
  width: min(400px, calc(100vw - 32px));
  max-height: calc(100vh - 32px);
  max-height: calc(100dvh - 32px);
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--bg-page);
  border: 4px solid var(--ink);
  border-radius: 6px;
  box-shadow: 8px 10px 0 var(--ink);
  animation: modal-pop .3s cubic-bezier(.2, .8, .3, 1) both;
}
/* The header and the grip are the two hands; the body is what gives
   when a box is pulled taller, and scrolls when there is more in it
   than there is box. */
.modal-box > .modal-body { flex: 1 1 auto; overflow: auto; min-height: 0; }
.modal-overlay .modal-header {
  flex: 0 0 auto;
  cursor: grab;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}
.modal-box.is-carried .modal-header { cursor: grabbing; }
/* A rise and a settle, not a snap: the box comes up a few pixels and
   is fully readable well before it stops moving, so the eye never
   waits on it. Anyone who has asked their machine for less movement
   gets the box with no travel at all — it simply fades. */
@keyframes modal-pop {
  from { transform: translateY(8px) scale(.985); opacity: 0; }
  60%  { opacity: 1; }
  to   { transform: none; opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .modal-box { animation: modal-appear .2s ease-out both; }
}
/* A HAND-OFF IS NOT AN ARRIVAL — NOR A DEPARTURE. When the card box
   turns into a practice run — same seat, same size, the box "turning
   into the run rather than vanishing while a second window appears" —
   the room must not brighten and go dark again in between, and the box
   being replaced must not hang about fading over the one that replaced
   it. Both halves of the swap wear this mark, and it comes off once the
   swap is done, so the next ordinary opening and closing fade like
   everything else. */
.modal-overlay.is-handoff,
.modal-overlay.is-handoff .modal-box { animation: none; transition: none; }
@keyframes modal-appear {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.modal-header {
  background: var(--accent);
  border-bottom: 3px solid var(--ink);
  padding: 10px 14px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.modal-title {
  font-family: 'Pixelify Sans', sans-serif;
  font-size: 1rem;
  letter-spacing: .03em;
  background: var(--bg-page);
  border: 2px solid var(--ink);
  border-radius: 4px;
  padding: 4px 12px;
  box-shadow: 3px 3px 0 var(--ink);
}
.modal-close {
  width: 28px; height: 28px;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: 1.2rem;
  line-height: 1;
  background: var(--bg-page);
  border: 2px solid var(--ink);
  border-radius: 4px;
  box-shadow: 2px 2px 0 var(--ink);
  cursor: pointer;
  color: var(--ink);
}
.modal-close:hover { background: var(--highlight); }
/* CONFIG'S HEADER IS A HANDLE *(14 Aug 2026, from the user: "config
   menu needs to be draggable")* — every picker in the box demos itself
   on the room behind it, so the box can be carried aside by its header
   to watch the room answer. It was the first box to be carried, and
   for a while the only one; since 16 Aug every box is, so the handle
   itself is described on the base rule and only the SIZE is config's
   own business. It is a long column of switches, so it wants width for
   the row and a ceiling to scroll under. */
#configModal .modal-box {
  width: min(clamp(460px, 32vw, 560px), calc(100vw - 32px));
  max-height: min(720px, calc(100vh - 32px));
  max-height: min(720px, calc(100dvh - 32px));
}
.modal-body { padding: 16px 18px; display: flex; flex-direction: column; gap: 12px; }
.modal-label {
  align-self: flex-start;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .8rem;
  background: var(--highlight);
  border: 2px solid var(--ink);
  border-radius: 3px;
  padding: 3px 10px;
  box-shadow: 2px 2px 0 var(--ink);
}
.modal-select, .modal-input {
  font-family: 'Nunito', sans-serif;
  font-size: 1rem;
  color: var(--ink);
  background: var(--highlight);
  border: 2px solid var(--ink);
  border-radius: 4px;
  padding: 8px 12px;
  box-shadow: 3px 3px 0 var(--ink);
  outline: none;
}
/* High and low, side by side — one thought, two boxes. They share the
   row evenly and each keeps its own label above it. */
.modal-pair { display: flex; gap: 12px; }
.modal-field {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.modal-field .modal-input { width: 100%; box-sizing: border-box; }
.modal-actions {
  border-top: 2px solid var(--ink);
  margin-top: 4px;
  padding-top: 12px;
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
.modal-submit {
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .95rem;
  color: var(--ink);
  background: var(--accent);
  border: 2px solid var(--ink);
  border-radius: 4px;
  padding: 8px 18px;
  box-shadow: 2px 2px 0 var(--ink);
  cursor: pointer;
}
.modal-submit:hover { transform: translate(-1px,-1px); box-shadow: 3px 3px 0 var(--ink); }
.modal-submit:active { transform: translate(1px,1px); box-shadow: 1px 1px 0 var(--ink); }

.config-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  font-size: .9rem;
}
.switch {
  width: 34px; height: 18px;
  padding: 0;
  border: 2px solid var(--ink);
  border-radius: 99px;
  background: var(--bg-page);
  cursor: pointer;
}
.switch i {
  display: block;
  width: 10px; height: 10px;
  margin: 2px;
  border-radius: 50%;
  background: var(--ink);
  transition: transform .2s ease;
}
.switch.on { background: var(--accent); }
.switch.on i { transform: translateX(16px); }
/* Which degrees you read in — a small pair beside the numbers, in the
   chrome's own plate-and-hard-shadow language. Right-aligned, because it
   is an aside to the boxes above it rather than a field of its own. */
/* A row of small plates where exactly one is chosen — the degrees in the
   weather box, the page's light in CONFIG. One style, both places. */
/* wrap: six flair choices don't fit the box on one line, and a picker
   that grows a second row should grow it tidily, flush right */
.unit-toggle, .pick-toggle {
  display: flex; gap: 4px; align-self: flex-end;
  flex-wrap: wrap; justify-content: flex-end;
}
.unit-toggle button, .pick-toggle button {
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .72rem;
  padding: 3px 9px;
  border: 2px solid var(--ink);
  border-radius: 3px;
  background: var(--bg-page);
  color: var(--ink);
  box-shadow: 2px 2px 0 var(--ink);
  cursor: pointer;
}
.unit-toggle button:hover, .pick-toggle button:hover { background: var(--highlight); }
.unit-toggle button.chosen, .pick-toggle button.chosen { background: var(--accent); }

/* What the box says when the two numbers can't both be true. Ink-red,
   the app's own, and it takes no room at all until there is something
   to say. */
.modal-note {
  font-family: 'Nunito', sans-serif;
  font-size: .82rem;
  color: #b0432f;
  margin: 0;
}
.modal-note[hidden] { display: none; }
.modal-submit:disabled {
  opacity: .45;
  box-shadow: 1px 1px 0 var(--ink);
  cursor: not-allowed;
}

/* ---------- the words' size ----------
   CONFIG's "the text" — for eyes the 15px page is too small for. The
   page, the calendar, every station and the pop-up boxes step up
   together; the top row, the mode tabs and the room hold still, so the
   pane's floor (measured off the top row) stays honest. `zoom` rather
   than font-size so every row, box and tick keeps its proportions —
   the page is simply nearer. */
body[data-text="big"] .today-book,
body[data-text="big"] .days-calendar,
body[data-text="big"] .habit-log,
body[data-text="big"] .shopping-list,
body[data-text="big"] .modal-box { zoom: 1.2; }

body[data-text="bigger"] .today-book,
body[data-text="bigger"] .days-calendar,
body[data-text="bigger"] .habit-log,
body[data-text="bigger"] .shopping-list,
body[data-text="bigger"] .modal-box { zoom: 1.45; }

/* ---------- narrow screens: the booth stacks ----------
   Two ways in, one shape. A narrow window at any pointer, OR a phone
   turned sideways — finger-driven, no hover, and short even though it
   is wide *(15 Aug 2026, from the user: "make it so mobile mode is
   always stacked, never side by side")*. This covers the moment before
   applySplit runs; isPhoneShaped in paper.js is the same test in
   script, and it is what keeps the shape through a turn of the hand. */

@media (max-width: 700px),
       (pointer: coarse) and (hover: none) and (max-height: 700px) {
  body { flex-direction: column; }
  #paper { flex: 0 0 50%; max-width: none; min-width: 0; }
  /* A half-height pane is often shorter than the page's furniture, and
     a card that shrinks below its content lets the habit strip bleed
     through its own border *(12 Aug 2026, from the user: "be sure
     habits stays inside boundaries")*. The card keeps its natural
     height and the pane scrolls instead — everything stays inside
     the lines. */
  .today-book { flex-shrink: 0; }
  /* No choice to offer here: the booth is the only shape a phone can
     hold, so the row that offers the other one steps off the panel
     rather than sitting there lit and doing nothing. */
  #cfgLayoutRow { display: none; }
  /* THE TOP ROW LIES DOWN IN TWO *(15 Aug 2026)*. The clock, the name
     plate and the weather chip together want about 400px, and a phone
     has 390 — so the row hung eighteen pixels off the edge and the
     whole page slid sideways after it, carrying the day's card and its
     forward arrow half out of sight. The plates still never squeeze
     (see .paper-top); on a screen this narrow the row simply takes a
     second line instead, and nothing moves but the chip. */
  .paper-top { flex-wrap: wrap; }
  /* A FINGER IS NOT A CURSOR. The little ‹ › and the pen's ＋ are drawn
     small on purpose and stay exactly as drawn — but on a phone each
     one carries an invisible collar of touchable space, so the day
     turns when you mean it to: a 24px arrow answers to a 36px press.
     The collar is measured from the arrow's INSIDE (an absolute box
     hangs off the padding edge, so the 2px border eats 2 of the 8),
     which is what keeps it clear of the date plate 8px away — a tap
     meant for the calendar is never caught by the arrow beside it. */
  .paper-date-row .day-arrow,
  .pen-plus { position: relative; }
  .paper-date-row .day-arrow::after,
  .pen-plus::after { content: ''; position: absolute; inset: -8px; }
  /* THE CARD TAKES THE WHOLE WIDTH *(15 Aug 2026, from the user:
     "arrows sit on the card's edges")*. Standing beside it, the two
     arrows ate eighty pixels of a three-hundred-and-fifty-pixel
     screen, and the card — which is meant to be the one big thing in
     front of you, a 3×5 held up — came out a third of the box. On a
     phone they step ONTO the card's own left and right edges instead:
     still in plain sight, still pressable, taking nothing. The card
     then runs the full width and grows taller with it (5 by 3, as
     ever), and its side padding steps back so no word is ever written
     underneath an arrow.

     Centred by margin, not by transform — the arrows keep the one
     pixel of travel every plate in the app has when pressed, and a
     transform here would have eaten it. */
  .practice-drift { position: relative; }
  .practice-drift .deck-face { flex: 1 1 100%; padding-left: 38px; padding-right: 38px; }
  #practicePrev, #practiceNext {
    position: absolute;
    top: 0; bottom: 0;
    height: 28px;
    margin: auto 0;
    z-index: 2;
  }
  #practicePrev { left: 4px; }
  #practiceNext { right: 4px; }
  /* And no keys on a keyless screen: the answer buttons teach the
     letters n / s / y to a hand that has a keyboard. A phone has
     none, so the buttons there just say the words. */
  .key-hint { display: none; }
  /* (The phone's zoom-on-tap is handled at the window's own scale — see
     the viewport line in index.html — and NOT by growing the lines to
     sixteen pixels here. Sixteen was tried first and broke a standing
     law: a clipped row's words are sized to themselves, so the wider
     letters left no slack, and the moment the tools stepped out the
     paperclip slid ten pixels left, out from under the finger already
     going for it.) */
}

/* ---------- the stacked booth, chosen on purpose ----------
   CONFIG's "the booth" — the same shape the phone gets, at any size:
   paper above, room below, halves even to the eye *(12 Aug 2026, from
   the user: "50/50 appearance to user for top and bottom")*. For
   keeping the game in a narrow window standing beside other work.
   applySplit pins the paper's height when the seam has been dragged;
   fitScreen gives the room the biggest whole size its half truly
   holds. (A stage that hugged the room and handed the paper the rest
   was tried the same day and read as "more list than room" — the even
   halves are the point, black bezel and all.) */
body[data-stacked] { flex-direction: column; }
body[data-stacked] #paper { flex: 0 0 50%; max-width: none; min-width: 0; }
body[data-stacked] #stage { flex: 1 1 auto; min-height: 0; }
body[data-stacked] .today-book { flex-shrink: 0; }   /* same as the phone rule above */
/* The seam lies down but keeps working: drag it up and the room takes
   the space the paper gives up — the stacked booth's large view of the
   room. Same grip, same double-tap reset, turned on its side. */
body[data-stacked] #splitter {
  /* display set here on purpose: the narrow media query says `none`
     for the moment before applySplit runs, and this heavier selector
     is what turns the seam back on once the flag is up — narrow
     windows keep their slider *(12 Aug 2026, from the user: "slide
     should be available even in narrow mode")*. */
  display: flex;
  flex: 0 0 10px;
  flex-direction: row;
  justify-content: center;
  padding-top: 0;
  border-left: none;
  border-right: none;
  border-top: 1px solid rgba(0,0,0,.5);
  border-bottom: 1px solid rgba(0,0,0,.5);
}
body[data-stacked] #splitGrip {
  flex-direction: row;
  padding: 3px 12px;
  cursor: row-resize;
}

/* ============================================================
   THE CONSOLE SHELL
   ============================================================
   The game screen sits in a bezel like a handheld's: dark plastic,
   rounded, and nothing else. A power light and a name badge were tried
   here and cut — the lamp is visible in the room itself, and a second
   indicator repeating what the picture already shows is noise. Every
   element earns its place or goes. */

.console-shell {
  display: flex;
  flex-direction: column;
  background: linear-gradient(to bottom, #3a3230 0%, #2a2422 60%, #1e1a18 100%);
  border: 3px solid #0d0a07;
  border-radius: 14px;
  padding: 14px;
  box-shadow:
    inset 0 2px 0 rgba(255,255,255,.08),
    inset 0 -3px 0 rgba(0,0,0,.4),
    0 16px 44px rgba(0,0,0,.65);
}
.console-shell #screen {
  border-radius: 4px;
  box-shadow: 0 0 0 3px #0d0a07, inset 0 0 18px rgba(0,0,0,.4);
}


/* ---------- the divider ---------- */

/* The seam itself is dead wood — it neither drags nor changes the
   cursor. Only the grip listens. */
#splitter {
  flex: 0 0 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding-top: 4vh;
  background: #241a13;
  border-left: 1px solid rgba(0,0,0,.5);
  border-right: 1px solid rgba(0,0,0,.5);
}
/* The grip: the one small handle that resizes, up at the top of the
   seam — clear of the desk, the goals and everything hands actually
   work near. */
#splitGrip {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 12px 3px;
  cursor: col-resize;
  touch-action: none;
}

/* ---------- the pane folded away ----------
   *(16 Aug 2026, from the user: "i need a way to collapse left side so
   it is just pixel room on screen")*. The pane goes; the seam does NOT.
   It stands against the left edge as the one thing that knows the way
   back — brighter than usual, because a seam with nothing beside it has
   to read as a handle rather than as the window's own border. */
body[data-paper-shut] #paper { display: none; }
body[data-paper-shut] #splitter { background: #2e2117; }
body[data-paper-shut] #splitGrip { cursor: e-resize; }
body[data-paper-shut] #splitGrip i { background: rgba(244, 230, 200, .5); }
#splitGrip i {
  width: 4px; height: 4px;
  border-radius: 50%;
  background: rgba(244, 230, 200, .28);
}
#splitGrip:hover i, #splitter.dragging i { background: rgba(244, 230, 200, .8); }

/* The same two ways in as the stacking block above — the seam stands
   down only for the moment before applySplit runs, and then lies down
   and goes back to work. */
@media (max-width: 700px),
       (pointer: coarse) and (hover: none) and (max-height: 700px) {
  #splitter { display: none; }
}

/* ------------------------------------------------------------
   THE DECK STATION — the card box, opened
   Three screens in the log station's own chrome; only what no other
   station has is styled here: the card's big face, the answer row,
   and a deck's name worn as a button. */
.deck-head { justify-content: flex-start; align-items: center; }
.deck-head .strip-label { flex: 1 1 auto; text-align: center; }
.deck-name {
  display: block;
  width: 100%;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}
.deck-name:hover { color: var(--ink); }
.deck-counts { margin: 2px 0 12px; }
.deck-title { flex: 1 1 auto; font-weight: 700; text-align: center; }
.deck-practice { display: block; margin: 0 auto 16px; }
.deck-card-row { gap: 6px; }
.deck-card-row .goal-text { flex: 1 1 0; min-width: 0; }
.deck-card-add { flex-wrap: wrap; }
.deck-card-add .goal-text { flex: 1 1 40%; min-width: 0; }
/* The card itself, held up to be read — dressed like the app's own
   index card *(13 Aug 2026, from the user: "make the index cards look
   more like app's did")*: cream, faintly ruled, an ink border with a
   dropped-paper shadow, and a slight tilt of its own (set per card in
   renderPractice) that leans the other way when the card is turned.
   The answer sits under a dashed rule, and once it shows, the front
   steps back so the answer gets the attention. */
.deck-face {
  position: relative;
  /* A real index card is 5 by 3, and stays 5 by 3 no matter how much
     is written on it — the words scroll sooner than the card
     stretches. `safe` keeps a long text's top reachable: plain
     centring would push it off the card's edge with no way back.
     The width is said outright: as a size container (below) the card
     may not learn its width from its contents or its flex line, so
     an auto width collapses — 100% keeps it definite. */
  width: 100%;
  aspect-ratio: 5 / 3;
  overflow-y: auto;
  padding: 28px 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: safe center;
  gap: 10px;
  text-align: center;
  background: var(--bg-page);
  background-image: repeating-linear-gradient(
    to bottom,
    transparent 0 27px,
    color-mix(in srgb, var(--accent) 30%, transparent) 27px 28px
  );
  /* A card this size carries a real outline — the thin line it wore
     at first read as dainty against the box's own 4px frame *(13 Aug
     2026, from the user: "the outline of the card itself is too
     small")*. */
  border: 3px solid var(--ink);
  border-radius: 10px;
  box-shadow: 5px 6px 0 color-mix(in srgb, var(--ink) 25%, transparent);
  transform: rotate(var(--card-tilt, -0.25deg));
}
#practiceBody .deck-face:not(.is-flipped) { cursor: pointer; }
/* In the pop-up the card fills the window instead of the list's own
   300px cap — growing the box grows the card. */
#practiceBody .deck-face { max-width: none; }
.deck-face.is-flipped {
  transform: rotate(calc(var(--card-tilt, -0.25deg) * -1));
  animation: card-turn 0.3s ease;
}
@keyframes card-turn {
  from { transform: rotateY(65deg); opacity: 0.4; }
  to   { transform: rotateY(0deg) rotate(calc(var(--card-tilt, -0.25deg) * -1)); opacity: 1; }
}
/* The faces are sized BY THE CARD, not in fixed pixels: the card is
   a container (container-type just below) and the words are a
   fraction of its width — the app's own proportions — so a card
   brought closer with the grip reads bigger, exactly like a real
   card in the hand. The clamps keep a tiny demo card legible and a
   wall-sized one sane. */
.deck-face { container-type: inline-size; }
.deck-face-front {
  margin: 0;
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: clamp(20px, 8cqw, 60px);
  font-weight: 700;
  color: var(--write);
  overflow-wrap: anywhere;
}
.deck-face.is-flipped .deck-face-front { font-size: clamp(14px, 4.2cqw, 34px); }
.deck-face-back {
  margin: 0;
  width: 100%;
  padding-top: 10px;
  border-top: 1.5px dashed color-mix(in srgb, var(--ink) 45%, transparent);
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: clamp(13px, 4.2cqw, 32px);
  color: var(--ink);
  overflow-wrap: anywhere;
}
.deck-answers { justify-content: center; border-top: 0; }
/* THE WRITER'S CARD — the same drawn card, with the pen on it. The
   two inputs wear the faces' own type so the card being written
   looks exactly like the card that will be dealt; the card itself
   is not pressable, so no pointer cursor and no flip. */
.writer-face { cursor: default; }
.writer-face input {
  width: 100%;
  border: none;
  background: transparent;
  outline: none;
  text-align: center;
  overflow-wrap: anywhere;
}
.writer-front {
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: clamp(18px, 6cqw, 44px);
  font-weight: 700;
  color: var(--write);
}
.writer-back {
  padding-top: 10px;
  border-top: 1.5px dashed color-mix(in srgb, var(--ink) 45%, transparent) !important;
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: clamp(13px, 4.2cqw, 30px);
  color: var(--ink);
}
.writer-face input::placeholder {
  color: color-mix(in srgb, var(--ink) 40%, transparent);
}
.writer-row { justify-content: center; border-top: 0; flex-wrap: wrap; }
.writer-deck { max-width: 46%; }
.writer-tally { text-align: center; margin: 6px 0 0; }
/* The writer card follows the practice card's chosen hand and ink —
   the same values the dealt card wears (see the SRS config block). */
.writer-face[data-font="pixel"] input { font-family: 'Pixelify Sans', monospace; }
.writer-face[data-font="type"] input { font-family: 'Courier New', Courier, monospace; }
.writer-face[data-ink="black"] input { color: #221f1c; }
body[data-dark] .writer-face[data-ink="black"] input { color: var(--ink); }
.writer-face[data-ink="blue"] input { color: #3a6ea5; }
.writer-face[data-ink="red"] input { color: #b3533a; }
.writer-face[data-ink="green"] input { color: #4a7c59; }
body[data-dark] .writer-face[data-ink="blue"] input { color: #8fb8e8; }
body[data-dark] .writer-face[data-ink="red"] input { color: #e8938a; }
body[data-dark] .writer-face[data-ink="green"] input { color: #93c9a4; }
/* The drift row: the card between its two arrows. A quiet arrow
   keeps its seat invisibly so the card never shifts under the hand —
   nothing a cursor aims at may move. */
.practice-drift {
  display: flex;
  align-items: center;
  gap: 8px;
}
.practice-drift .deck-face { flex: 1 1 auto; min-width: 0; }
.practice-drift .day-arrow { flex: 0 0 auto; }
.practice-drift .day-arrow:disabled { visibility: hidden; }
/* Each answer button wears its own key, small and set back — the
   buttons teach the keys, so the two can never drift apart. */
.key-hint {
  margin-left: 7px;
  padding: 0 5px;
  border: 1.5px solid currentColor;
  border-radius: 4px;
  font-size: 11px;
  opacity: 0.55;
}

/* Rest and wake: one small word at the row's end. A rested card's
   faces dim but stay readable — set aside, not gone. */
.deck-rest {
  flex: 0 0 auto;
  border: 2px solid var(--ink-soft);
  border-radius: 4px;
  background: none;
  color: var(--ink-soft);
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: 11px;
  font-weight: 700;
  padding: 2px 6px;
  cursor: pointer;
}
.deck-rest:hover { background: var(--highlight); color: var(--ink); }
.deck-card-row.is-resting .goal-text { opacity: .45; }

/* ---------- the practice pop-up: the box's own little game ----------
   Practice pops up over the whole booth (see THE PRACTICE POP-UP,
   paper.js) in the modal's own frame. The game dress is quiet: a
   track of pips for the stack — empty is still to come, ink is got,
   the season's accent is missed, the highlight ring is where you
   stand — and a tally said big in the pixel hand at the finish. */
/* The window is the reader's (paper.js, THE WINDOW IS THE READER'S):
   it drags by its header, sizes by the corner grip, and when a height
   is pinned the spare room goes to the body, which scrolls.

   It opens LARGE *(13 Aug 2026, from the user: "card should take up
   majority of minigame screen, should feel like 3x5 card in front of
   you", then "it should be a large item on the screen to make it the
   obvious way to pay attention")* — the card, which fills the body's
   width, is the one big thing on the screen; the track and the
   answers are trim beside it. Never taller than the screen: past
   that the body scrolls. */
.practice-box {
  width: min(720px, calc(100vw - 32px));
  max-height: calc(100vh - 32px);
  /* On a phone, vh is measured as if the browser's bars weren't there,
     which let the box run under them; dvh is what is actually on
     screen. The vh line above stays as the fallback for anything that
     doesn't know the newer unit. */
  max-height: calc(100dvh - 32px);
  position: relative;
  display: flex;
  flex-direction: column;
}
/* The card box opens SQUARER than it did *(15 Aug 2026, from the
   user: "box should be more square")*. It had grown to 860px wide for
   three columns that are no longer there — the card options went
   behind the ⚙ and My decks' door came off — so a long letterbox now
   held two columns and a lot of air. Narrower, with a floor under its
   height, it reads as a box on a desk rather than a banner. The
   reader's own saved seat still wins over it (save.practiceWin), and
   the height is left to what is standing inside — a floor here would
   fight the corner grip, and the window is the reader's. */
#boxModal .practice-box { width: min(680px, calc(100vw - 32px)); }
.practice-box .modal-body { flex: 1 1 auto; overflow: auto; min-height: 0; }
/* The grip is the house's, not the practice box's *(16 Aug 2026: every
   pop-up carries and sizes now)* — it was born here and kept the name
   `practice-grip` until it was worn by eight boxes and the name was
   simply wrong. The header's grab cursor moved up to the base rule
   with it; only the grip's own face is still described here, where it
   was drawn. */
.box-grip {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 0;
  background: none;
  cursor: nwse-resize;
  touch-action: none;
}
/* The grip's face: a small striped corner, the classic. */
.box-grip::before {
  content: '';
  position: absolute;
  inset: 4px 3px 3px 4px;
  clip-path: polygon(100% 0, 100% 100%, 0 100%);
  background: repeating-linear-gradient(135deg,
    var(--ink-soft) 0 2px, transparent 2px 5px);
}
/* The modal body is already a flex column with its own gap, so the
   rows here bring no margins of their own. */
.practice-head {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 28px;
}
.practice-steps {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 4px;
}
.practice-step {
  width: 9px; height: 9px;
  border: 2px solid var(--ink-soft);
  border-radius: 3px;
  background: none;
}
.practice-step.is-got    { border-color: var(--ink); background: var(--ink); }
.practice-step.is-missed { border-color: var(--accent); background: var(--accent); }
/* A so-so'd card: touched, not settled — the missed colour as a
   hollow ring, until a real answer fills it. */
.practice-step.is-soso   { border-color: var(--accent); background: none; }
.practice-step.is-here   { border-color: var(--ink); background: var(--highlight); }
.practice-count {
  flex: 1;
  margin: 0;
  text-align: center;
  font-size: 13px;
  color: var(--ink-soft);
}
.practice-tally {
  margin: 8px 0 0;
  text-align: center;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: 30px;
  color: var(--write);
}
.practice-tally-note {
  margin: 0;
  text-align: center;
  font-size: 13px;
  color: var(--ink-soft);
}
.practice-done { display: block; margin: 4px auto 0; }

/* The paste fold: a box big enough for a whole list. */
.deck-paste-box {
  display: block;
  width: 100%;
  min-height: 76px;
  resize: vertical;
  border: 2px solid var(--ink-soft);
  border-radius: 4px;
  padding: 6px;
  margin: 6px 0 8px;
}
.deck-paste-note { margin-top: 8px; }

/* ---------- the card box's own furniture ----------
   The box wears the practice game's box exactly; only what stands
   inside it differs. A drawer's row and a deck's row are the same row
   on purpose — both answer the same question, so both read the same
   way, and the eye never has to learn a second shape halfway down. */

/* THE BOX BREATHES *(15 Aug 2026, from the user: "more padding between
   card box modal edges and interior content … too close to edge")*.
   Everything inside — the helper line, the list's bar, the framed
   shelf, the drawn decks — stood a modal's ordinary 18px from the
   border, which is right for a small form and mean for a room you
   stand in. The header's own words get the same air, so the title
   plate and the two word buttons line up over the content below them
   rather than sitting further out than it. */
.box-box .modal-body { padding: 20px 26px 24px; }
.box-box .modal-header { padding: 12px 20px; }
.box-box .modal-title { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; }
/* The breadcrumb: the parts you can go back to are pressable and
   quiet; where you actually are is plain ink and stays put. */
.box-crumb {
  font: inherit;
  background: none;
  border: 0;
  padding: 0;
  color: var(--ink-soft);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-style: dotted;
  text-underline-offset: 2px;
}
.box-crumb:hover { color: var(--ink); }
.box-here { color: var(--ink); }

/* ---------- THE MENU *(13 Aug 2026, from the user: "I want all the
   card options to be laid out like a video game menu in the pop up
   screen like a super nintendo vibe")* ----------

   A console menu, in the house's own materials: the cream plate and
   the 2px ink border are already the app's, so the SNES is in the
   PROPORTIONS — a heavy frame with a second frame inside it, rows the
   full width of the panel, no gaps between them, a solid ► in the
   gutter, and the chosen row filled solid rather than merely tinted.
   No new palette: a menu that arrived in someone else's colours would
   be a screenshot of another game sitting on this desk. */
.snes-menu {
  border: 3px solid var(--ink);
  /* The inner frame — the doubled border every console menu has. */
  box-shadow: inset 0 0 0 2px var(--bg-page), inset 0 0 0 4px var(--ink),
              4px 4px 0 var(--ink);
  background: var(--bg-page);
  padding: 6px;
  margin: 0 0 12px;
}
/* THE SECTION HEADINGS *(13 Aug 2026, from the user: "make srs modal
   feel more like a program and less cute with titles … I want it well
   organized like a legal librarian would")*. Capitals, spaced wide, a
   hairline under each: a catalogue's own furniture. They are drawn but
   never selectable — the cursor walks past them. */
.snes-head {
  margin: 10px 0 2px;
  padding: 0 8px 3px;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .62rem;
  letter-spacing: .18em;
  color: var(--ink-soft);
  border-bottom: 1px solid var(--ink-soft);
  opacity: .85;
}
/* A heading at the very top used to sit flush against the panel's own
   inner frame, and the frame painted straight through the top of its
   letters — MY SUBJECTS and LANGUAGES both read as half-erased
   *(fixed 15 Aug 2026)*. Pixelify's capitals stand a hair above their
   line, so the first heading keeps a little air. */
.snes-head:first-child { margin-top: 4px; }

.snes-item {
  display: flex;
  align-items: baseline;
  gap: 6px;
  width: 100%;
  border: 0;
  background: none;
  cursor: pointer;
  text-align: left;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .88rem;
  letter-spacing: .02em;
  color: var(--ink);
  padding: 5px 8px;
  /* No forced lowercase here. Everywhere else in the app it is the
     house's quiet voice; on this panel it read as costume, and a
     catalogue entry should be capitalised like the thing it names. */
}
/* The chosen row: filled, not tinted. On a console you always know
   which row is live from across the room. */
.snes-item.is-on {
  background: var(--accent);
  color: var(--ink-on-accent, var(--ink));
}
/* The cursor keeps its seat on every row, empty until it is yours —
   so nothing shifts sideways as the cursor walks down. */
.snes-cursor { flex: 0 0 auto; width: 12px; visibility: hidden; }
.snes-item.is-on .snes-cursor { visibility: visible; }
.snes-label { flex: 1 1 auto; min-width: 0; }
/* What a row is carrying — counts, a state — held at the right end in
   the quieter voice, so the eye can run down either column alone. */
.snes-note {
  flex: 0 0 auto;
  font-size: .72rem;
  opacity: .75;
  font-family: 'Nunito', system-ui, sans-serif;
  text-transform: none;
}
/* A row that leads somewhere says so. */
.snes-into { flex: 0 0 auto; opacity: .6; }
/* Rows that do a thing rather than go somewhere. `go` is the row you
   came here for; `danger` is the one that destroys something and is
   never the row the cursor starts on; `quiet` is a statement, not a
   choice. */
.snes-item--danger { color: var(--ink-soft); }
.snes-item--danger.is-on { background: var(--done); color: var(--bg-page); }
.snes-item--quiet { cursor: default; color: var(--ink-soft); }
.snes-item--quiet.is-on { background: none; color: var(--ink-soft); }
.snes-item--quiet .snes-cursor { visibility: hidden !important; }
/* The + row — My decks' own way to start a subject, a bold glyph
   standing in for a whole word (15 Aug 2026). */
.snes-plus {
  font-weight: 800;
  font-size: 1.1rem;
  line-height: 1;
}

/* The lines you type in sit under the menu, not in it — except the
   + row's own reply, which opens inside the same box its + stands
   in (15 Aug 2026, "should be simpler"). */
.box-add { margin-bottom: 10px; }
.snes-menu .box-add { margin: 4px 0 2px; }

/* Filing a deck into a drawer, or back out loose. */
.deck-file-into { align-items: center; gap: 8px; }
.deck-file-into .log-empty { margin: 0; flex: 0 0 auto; }
.deck-file-into select { flex: 1 1 auto; }

/* ---------- THE DESK *(14 Aug 2026 — the table of piles and the
   drag-to-tray were replanned by eye: "confusing … cramped". One
   drawn stack, one tickable list, no dragging)* ----------

   The stack is the desk's only physical object: one big index card
   with the rest of its pile painted behind it in hard offset layers —
   the house's own shadow language, three cards deep, at the card
   face's own slight lean. Everything under it is catalogue. */
.desk-stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: min(320px, 82%);
  aspect-ratio: 5 / 2.4;
  margin: 8px auto 20px;
  padding: 12px 16px;
  border: 3px solid var(--ink);
  border-radius: 8px;
  background: var(--bg-page);
  background-image: repeating-linear-gradient(
    to bottom,
    transparent 0 21px,
    color-mix(in srgb, var(--accent) 30%, transparent) 21px 22px
  );
  color: var(--ink);
  font-family: 'Pixelify Sans', sans-serif;
  text-align: center;
  cursor: pointer;
  transform: rotate(var(--pile-tilt, -0.4deg));
  box-shadow:
    4px 4px 0 -1px var(--bg-page),  4px 4px 0 0 var(--ink),
    8px 8px 0 -1px var(--bg-page),  8px 8px 0 0 var(--ink),
    12px 12px 0 -1px var(--bg-page), 12px 12px 0 0 var(--ink);
}
/* The console rule: the live choice reads from across the room. */
.desk-stack.is-on {
  background-color: var(--accent);
  transform: rotate(var(--pile-tilt, -0.4deg)) translate(-2px, -2px);
}
.desk-stack-counts {
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: .76rem;
  color: var(--ink-soft);
}
.desk-stack.is-on .desk-stack-counts { color: inherit; opacity: .8; }
.desk-stack-word { font-size: 1rem; letter-spacing: .05em; }

/* The desk's list: a row and its tick, standing in one flex line —
   a button may not live inside a button. */
.study-row { display: flex; align-items: center; gap: 6px; }
.study-row .snes-item { flex: 1 1 auto; width: auto; min-width: 0; }
/* A subject is a heading you can press; its decks stand indented,
   and a deck's piles one step further in. */
.snes-item--subject .snes-label { letter-spacing: .1em; }
.snes-item--indent { padding-left: 32px; }
.snes-item--indent2 { padding-left: 56px; }
/* The tick at a line's end: empty until it joins the session. */
.study-tick {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  margin-right: 6px;
  padding: 0;
  border: 2px solid var(--ink-soft);
  border-radius: 4px;
  background: none;
  color: var(--ink);
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}
.study-tick:hover { border-color: var(--ink); }
.study-tick.is-ticked { border-color: var(--ink); background: var(--highlight); }
/* THE RUN'S CLOCK and the day's own typed clock stood here for a
   morning *(15 Aug 2026)* — a stopwatch on the booth's table with a
   plate to hold it, and a line on the day's page to type the number
   over. Both are gone: "remove timing note from study on today page
   and remove timer altogether". Nothing in the room counts seconds. */

/* The ▲▼ that walk a subject up and down its own section. Quiet as
   the fold, and gone dim where there is nowhere to go. */
.study-move {
  flex: 0 0 auto;
  width: 14px;
  height: 18px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-soft);
  font-size: 9px;
  line-height: 1;
  cursor: pointer;
}
.study-move:hover:not(:disabled) { color: var(--ink); }
.study-move:disabled { opacity: .25; cursor: default; }

/* THE ★ COLUMN down the list's left edge *(15 Aug 2026)* — one press
   puts a whole line into SRS mode. Half-starred reads faint: some of
   these cards are in, not all. */
.study-star {
  flex: 0 0 auto;
  width: 16px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-soft);
  font-size: 13px;
  line-height: 1;
  text-align: left;
  cursor: pointer;
}
.study-star.is-on { color: var(--ink); }
.study-star.is-some { color: var(--ink); opacity: .45; }
.study-star.is-empty { cursor: default; }
.study-star:hover:not(.is-empty) { color: var(--ink); }

/* THE ★ *(15 Aug 2026)* — on the card in the game, and on the card's
   line in its pile. Hollow until it means something, and never in the
   way of the words it shares a card with. */
.deck-face-star {
  position: absolute;
  top: 6px;
  right: 8px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-soft);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  z-index: 2;
}
.deck-face-star.is-on { color: var(--ink); }
.deck-face-star:hover { color: var(--ink); }
/* The ? beside it — the card's third face *(15 Aug 2026)*. Same
   corner, same quiet, one step in from the star. */
.deck-face-ask {
  position: absolute;
  top: 6px;
  right: 28px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-soft);
  font-family: 'Pixelify Sans', sans-serif;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  z-index: 2;
}
.deck-face-ask.is-on, .deck-face-ask:hover { color: var(--ink); }

/* The record itself: a short list, set like a card's own small print
   rather than a table. */
.card-record {
  width: 100%;
  max-width: 240px;
  margin: 0 auto;
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: .78rem;
  color: var(--ink);
}
.card-record-line {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  margin: 3px 0;
  padding-bottom: 2px;
  border-bottom: 1px dotted var(--ink-soft);
}
.card-record-label { color: var(--ink-soft); }
.card-record-value { font-variant-numeric: tabular-nums; }
.deck-card-star {
  flex: 0 0 auto;
  width: 20px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-soft);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}
.deck-card-star.is-on { color: var(--ink); }
.deck-card-star:hover { color: var(--ink); }

/* THE ONE BIG PENCIL *(15 Aug 2026, from the user: "maybe give it one
   big pencil which means edit any text below … rather than each has a
   pencil")* — a pencil on every row made the list shout; this stands
   alone above it, and turns the whole list over. Pressed, it wears the
   highlight, so which mode you are in is never a guess. */
.study-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 4px;
}
/* The two makers on the left, the pencil on the right — everything
   that CHANGES the list, in one bar above it *(15 Aug 2026)*. */
.study-makers { display: flex; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 0; }
.study-makers .plan-add { flex: 1 1 auto; margin: 0; }
.study-make {
  padding: 3px 8px;
  border: 2px solid var(--ink-soft);
  border-radius: 6px;
  background: var(--bg-page);
  color: var(--ink);
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: .72rem;
  cursor: pointer;
}
.study-make:hover, .study-make.is-on { border-color: var(--ink); background: var(--highlight); }
.study-edit {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border: 2px solid var(--ink);
  border-radius: 6px;
  background: var(--bg-page);
  box-shadow: 2px 2px 0 var(--ink);
  color: var(--ink);
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .68rem;
  letter-spacing: .06em;
  line-height: 1;
  cursor: pointer;
}
.study-edit:hover { background: var(--highlight); }
.study-edit.is-on { background: var(--highlight); }
.study-edit-pen { font-size: 1rem; line-height: 1; }

/* The ✕ beside it — the to-do row's own pair. Quiet until pointed at,
   and then plainly the colour of a thing that destroys. */
.study-erase {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-soft);
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
}
.study-erase:hover { color: var(--ink); }

/* THE QUESTION, standing in the line's own seat *(15 Aug 2026)* — no
   pop-up over a pop-up. "Keep it" comes first, nearest the eye. */
.study-row--asking {
  gap: 8px;
  padding: 4px 6px;
  border: 2px dashed var(--ink);
  border-radius: 6px;
}
.study-ask {
  flex: 1 1 auto;
  min-width: 0;
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: .74rem;
  color: var(--ink);
}
.study-answer {
  flex: 0 0 auto;
  padding: 2px 8px;
  border: 2px solid var(--ink);
  border-radius: 6px;
  background: var(--bg-page);
  color: var(--ink);
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: .72rem;
  cursor: pointer;
}
.study-answer.is-on { background: var(--highlight); }
/* The dangerous answer wears the box's own danger colour — the grey
   the menu's Delete rows already use when the cursor is on them, not
   a red imported from outside the palette. */
.study-answer.is-delete { color: var(--ink-soft); }
.study-answer.is-delete.is-on,
.study-answer.is-delete:hover { background: var(--done); border-color: var(--done); color: var(--bg-page); }

/* An editable line, wearing the row's own seat and indent so the name
   does not jump sideways as the list turns over. */
.study-row--editing { padding: 1px 0; }
.study-row--indent { padding-left: 32px; }
.study-row--indent2 { padding-left: 56px; }
.study-row--indent3 { padding-left: 80px; }
.study-rename {
  flex: 1 1 auto;
  min-width: 0;
  width: auto;
  padding: 4px 8px;
  border: 2px solid var(--ink);
  border-radius: 6px;
  background: var(--bg-page);
  color: var(--ink);
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: .8rem;
}

/* An empty list says so inside itself, rather than leaving a heading
   standing over nothing. */
.study-none { margin: 6px 10px 8px; }

/* THE + AT A LINE'S END *(15 Aug 2026)* — one rule all the way down:
   it adds the next thing inside the line it sits on. Quiet until
   wanted, like the fold; the same size as the tick so a row's end
   reads as one piece of furniture, not three. */
.study-plus {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-soft);
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: 15px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}
.study-plus:hover { color: var(--ink); }

/* What the + opens, standing under the row it belongs to and stepped
   in, so it reads as inside that thing. */
.study-add { margin: 2px 0 6px; }
.study-ways { display: flex; flex-wrap: wrap; gap: 8px; margin: 2px 0 6px; }
.study-way {
  padding: 2px 8px;
  border: 2px solid var(--ink-soft);
  border-radius: 6px;
  background: var(--bg-page);
  color: var(--ink);
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: .72rem;
  cursor: pointer;
}
.study-way:hover { border-color: var(--ink); }
.ind0 { padding-left: 0; }
.ind1 { padding-left: 32px; }
.ind2 { padding-left: 56px; }
.ind3 { padding-left: 80px; }
.snes-item--indent3 { padding-left: 80px; }

/* THE LIST'S WINDOW *(15 Aug 2026: "'my subjects' will need a window
   limit and a scroll")*. The height is measured to a whole row in
   capStudyList — never a hardcoded pixel — and the snap keeps it
   whole as it is read. The foot below it never scrolls away. */
/* The scroller inside the frame — see buildDeckListPanel. The frame
   keeps the air; this holds the rows and does the moving.

   AND ITS FIRST HEADING SITS FLUSH. Elsewhere a heading at the top of
   a panel keeps 4px so the panel's own inner frame does not paint
   through its letters; here the frame is a different box entirely and
   already holds the air. Left in, that margin put the first snap point
   4px down the list, and the browser quietly scrolled to it the moment
   the box opened — which stole four pixels off the bottom row and read
   as the list bleeding through the frame *(15 Aug 2026)*. */
.study-scroll { min-height: 0; }
.study-scroll .snes-head:first-child { margin-top: 0; }
.study-scroll.is-capped {
  overflow-y: auto;
  /* PROXIMITY, NOT MANDATORY *(15 Aug 2026)*. Mandatory snapping wants
     a snap point under the top edge at all times, and the first row's
     own top margin is 4px down — so the list quietly scrolled itself
     4px on arrival and the bottom row lost its foot to the frame.
     That was the bleed at the bottom edge. Proximity keeps the tidy
     landing when you scroll and leaves a list at rest where it is. */
  scroll-snap-type: y proximity;
}
.study-scroll.is-capped > * { scroll-snap-align: start; }

/* The ▾ beside a deck's tick — folds its piles out of the list. */
.study-fold {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-soft);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
}
.study-fold:hover { color: var(--ink); }
/* THE TITLE SCREEN'S OWN SPLIT *(15 Aug 2026, from the user: "put deck
   and options side by side when user first clicks open SRS", then
   "still too busy … move the main items side by side, not stacked",
   then: "i said HIDE card settings")* — the stack and MY SUBJECTS
   stand side by side the moment the box opens: press the stack to
   study everything, or open a subject on the right. The cards' own
   settings moved behind the header's ⚙ (boxConfig) and no longer
   stand here at all. Stacks on a narrow screen, same as the booth
   itself does. */
/* THE TWO SIDES END LEVEL *(15 Aug 2026, from the user: "be sure your
   final remade screen looks equally balanced with well set boundaries
   and scrollable lists when needed inside the modal")*. A box with two
   decks in it draws the same framed shelf as a box with twenty: the
   frame is given the height of the three drawn decks beside it, and
   whatever is short of that stays INSIDE the frame instead of the
   frame shrinking to a stub and leaving a hole in the screen.

   THE HEIGHT IS SET IN JS, NOT BY THE FLEX ROW *(and this cost an
   hour)*. Stretching the columns to each other reads well and measures
   terribly: the row's height is the taller column, so asking the
   stacks how tall they are answers "as tall as the list beside me",
   and the window the list was given could then be a few pixels more
   than the frame would allow — which came off the bottom row and read
   as a line bleeding through the frame. One authority: renderTitleScreen
   measures the decks' own run and hands it to the frame and to
   capStudyList together. */
.title-split {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  margin-bottom: 4px;
}
.title-split-col { min-width: 0; }
/* Two stacks now, one under the other: everything, then the ticked
   selection *(15 Aug 2026)*. The gap clears the pile's painted shadow,
   which falls 12px down and right, so the lower card is never sitting
   in the upper one's pile. */
.title-split-stack {
  flex: 2 1 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 22px;
  padding-bottom: 14px;
}
.title-split-stack .desk-stack { width: 100%; margin: 0; }
/* The pick's clear, tucked under the deck it empties. */
.stack-clear { align-self: flex-end; margin-top: -10px; }

/* The ask that fills the paste box — a quoted prompt, set like
   something you would copy out of a book. */
.paste-ask { margin-top: 8px; }
.paste-ask-quote {
  margin: 6px 0;
  padding: 6px 10px;
  border-left: 3px solid var(--ink-soft);
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: .74rem;
  font-style: italic;
  color: var(--ink-soft);
}

/* THE FOOT OF THE STACKS *(15 Aug 2026: "plus new language and plus
   new subject should be smaller and nestled to the left of the how
   this works button … cleaner screen")* — the ways out, in one small
   row under the decks: the two +, then the manual. A line being typed
   into takes the whole row to itself, because a name is worth more
   room than a plus is. */
.desk-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 6px;
  margin-top: 12px;
}
.desk-foot .snes-item {
  width: auto;
  flex: 0 1 auto;
  padding: 2px 8px;
  font-size: .72rem;
}
.desk-foot .snes-item .snes-cursor { display: none; }
.desk-foot .snes-item .snes-note { font-size: .66rem; }
.desk-foot .box-add { flex: 1 1 100%; }

/* THE MANUAL'S OWN BOX *(15 Aug 2026: "give 'how it works' its own
   box")* — a small drawn box at the end of that row. Flatter than a
   card on purpose: it is a note pinned to the desk, not something you
   deal from, so it takes the border and none of the pile. */
.desk-note {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  margin-left: auto;
  padding: 4px 10px;
  border: 2px dashed var(--ink-soft);
  border-radius: 8px;
  background: none;
  color: var(--ink);
  font-family: 'Pixelify Sans', sans-serif;
  text-align: center;
  cursor: pointer;
}
.desk-note:hover, .desk-note.is-on { border-color: var(--ink); border-style: solid; }
.desk-note.is-on { background: var(--accent); }
.desk-note-word { font-size: .72rem; letter-spacing: .05em; }
.desk-note-line {
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: .62rem;
  color: var(--ink-soft);
}
.desk-note.is-on .desk-note-line { color: inherit; opacity: .8; }
.title-split-deck {
  flex: 3 1 0;
  display: flex;
  flex-direction: column;
}
/* The framed list takes whatever the stacks beside it leave, and its
   own scroll happens inside that frame.

   AND IT KEEPS ITS MARGINS *(15 Aug 2026, from the user: "center list
   text on left box more inside so it doesnt bleed to box edges … or
   give a better boundary on the bottom")*. The rows ran right up to
   the drawn frame on all four sides, so the names read as printed ON
   the border rather than standing inside a box. The air is set here
   and NOT touched by the capped state — a padding that changed when
   the scrollbar arrived would shift every row sideways the moment a
   list grew long enough to scroll. The bottom is the deepest of the
   four: it is the edge the eye uses to tell a windowed list from a
   finished one, and capStudyList reads this very number when it
   decides where to cut, so the last row shown ends with the same air
   under it as the first has above. */
.title-split-deck .study-list {
  flex: 1 1 auto;
  margin-bottom: 0;
  padding: 10px 14px 14px;
  display: flex;
  flex-direction: column;
}
/* The scroller is exactly as tall as its content or as tall as the
   window the cap gave it — never squeezed by the frame around it. */
.title-split-deck .study-scroll { flex: 0 0 auto; }
@media (max-width: 700px) {
  .title-split { flex-direction: column; }
  .title-split-stack { align-items: center; }
  .title-split-stack .desk-stack { width: min(320px, 82%); margin: 0 auto; }
  .stack-clear { align-self: center; }
}

/* ---------- THE MINI GAME'S DRESSING (14 Aug 2026) ----------

   The ‹ in the box's header — the date arrows' own button, standing
   before the title plate. hidden on the title screen, where back
   means close. */
.box-back { flex: 0 0 auto; margin-right: 8px; }
.box-back[hidden] { display: none; }

/* The streak, worn up top the way a game wears its high score. */
.box-streak {
  margin: 0;
  text-align: center;
  font-family: 'Pixelify Sans', monospace;
  font-size: .95rem;
  letter-spacing: .06em;
  color: var(--ink);
}

/* One plain sentence under a screen's head — the standing explanation
   CONFIG can take off. */
.box-help {
  margin: 0 4px;
  text-align: center;
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: .78rem;
  line-height: 1.45;
  color: var(--ink-soft);
}

/* HOW IT WORKS — the manual's paragraphs, plain words in the menu's
   frame. */
.box-manual-text {
  margin: 2px 4px 10px;
  padding: 0 6px;
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: .8rem;
  line-height: 1.5;
  color: var(--ink);
}

/* The quiet text buttons — the session row's clear. */
.tray-clear {
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: .72rem;
  background: none;
  border: 0;
  padding: 0;
  color: var(--ink-soft);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-style: dotted;
  text-underline-offset: 2px;
}
.tray-clear:hover, .tray-clear.is-on { color: var(--ink); }

/* The picker for widening a session mid-run. */
.practice-add-deck { margin-top: 10px; }
.practice-add-deck select { flex: 1 1 auto; }

/* The subject's one-card-at-a-time line: front, back, which deck. */
.subject-card-add { flex-wrap: wrap; }
.subject-card-add .subject-card-deck { flex: 0 1 auto; }

/* ---------- THE STUDY STRIP — the page's third part *(13 Aug 2026
   evening; and later that day: "give a solid line and its own section
   to scroll for study, similar to habits")*: the day's goal and its
   notes, its own strip at the foot of the card. The solid rule above
   it closes it off from the day, the way the habits' rule closes them
   off below; the fold is the habits' own <details>. ---------- */
.study-strip {
  flex: 0 0 auto;
  margin-top: 10px;
  padding-top: 6px;
  border-top: 2px solid var(--ink);
}
/* Positioned so rows measure from the list, not the strip — see the
   note over .habit-list. */
.study-body { position: relative; }
/* Past its share of the card the body stops growing and scrolls, the
   height MEASURED to a row's top edge by capStudyBody (paper.js) so
   the cut lands between rows, never through one. Snap settles the
   scroll on whole rows the same way. */
.study-body.is-capped {
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: 2px;
  scroll-snap-type: y mandatory;
}
.study-body.is-capped > * { scroll-snap-align: start; }
.study-line {
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: 14px;
  color: var(--ink);
  margin: 2px 0;
}
.study-note {
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: 13px;
  color: var(--ink-soft);
  margin: 1px 0 1px 10px;
}

/* THE PROJECT FOLD'S BODY *(14 Aug 2026)*: every starred project, each
   a small fold of its own — the name is the fold's head, the steps
   hang underneath, indented a step so the names read as the spine.
   The cap is measured by capProjectBody (paper.js) to a line's top
   edge — name lines and step rows both count as lines — so the cut
   never slices through one; snap settles the scroll the same way. */
.project-body { position: relative; }
.project-body.is-capped {
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: 2px;
  scroll-snap-type: y mandatory;
}
.project-body.is-capped .proj-fold { scroll-snap-align: start; }
.proj-fold-name { margin: 2px 0; }
.proj-fold[open] > .proj-fold-name::before { transform: rotate(90deg); }
/* The fold's step indent is PADDING, not margin — it doubles as the
   lane the seat arrows stand in (see .goal-move), and padding stays
   inside a capped list's clip. */
.proj-fold .habit-list { padding-left: 14px; }
/* The shopping list's own store folds *(15 Aug 2026)* — the projects
   page gives its folds room through the rows around them; here, on
   plainer ground, each fold needs its own breath. */
.shopping-list-body .proj-fold, .shopping-list .proj-fold { margin: 4px 0 2px; }
/* THE TWO SECTIONS, "to buy" and "buy-deas" (15 Aug 2026) — a store's
   fold nests one level inside a section's, and wears the same step
   the projects page's own steps do, so the eye reads section, then
   store, then item without a second glance. */
.shop-section { margin: 6px 0; }
.shop-section > .proj-fold,
.shop-section > .habit-list,
.shop-section > .plan-add { margin-left: 14px; }


/* THE SRS CONFIG, behind the ⚙ on the game's own header. The two
   plates share the header's right end; the ⚙ is the × twice over in
   every way but its letter, so it simply wears the same plate. */
.practice-plates { display: flex; align-items: center; gap: 6px; }
.practice-config-btn {
  width: 28px; height: 28px;
  font-size: 1rem;
  line-height: 1;
  background: var(--bg-page);
  border: 2px solid var(--ink);
  border-radius: 4px;
  box-shadow: 2px 2px 0 var(--ink);
  cursor: pointer;
  color: var(--ink);
  padding: 0;
}
.practice-config-btn:hover { background: var(--highlight); }
/* The line the "own" plate becomes — sized to a number, wearing the
   same plate shape it replaces so the row keeps its width *(15 Aug
   2026)*. */
.goal-own {
  width: 4.5em;
  padding: 2px 6px;
  border: 2px solid var(--ink);
  border-radius: 4px;
  background: var(--bg-page);
  color: var(--ink);
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .74rem;
  text-align: center;
}
.goal-own:focus { outline: none; background: var(--highlight); }

/* THE CARD BOX'S TWO WORDS *(15 Aug 2026, from the user: "add how it
   works as a button in top bar near settings gear (change to word
   -settings)")* — the same plate as the ⚙ beside them, sized to its
   words instead of to a square. A word cannot be misread the way a
   picture can. */
.box-plate-word {
  height: 28px;
  padding: 0 8px;
  background: var(--bg-page);
  border: 2px solid var(--ink);
  border-radius: 4px;
  box-shadow: 2px 2px 0 var(--ink);
  color: var(--ink);
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .72rem;
  letter-spacing: .04em;
  line-height: 1;
  cursor: pointer;
}
.box-plate-word:hover { background: var(--highlight); }
/* The rows of plates, and the sample card that demos them the moment
   a plate is pressed. The sample is held to a postcard rather than
   given the practice card's full width: at full width it pushed every
   row it exists to explain off the bottom of the box, and a picker
   you have to scroll past its own sample to reach is a worse picker.
   It is the same card, read at arm's length. */
#practiceBody .config-row { padding: 2px 0; }
.deck-face-demo { margin: 0 auto; width: min(300px, 100%); max-width: none; }
.practice-config-note {
  margin: 2px 0 0;
  text-align: center;
  font-size: 13px;
}

/* THE THROW: a turned card carried sideways under the finger. The
   shift is a variable rather than a transform of its own — the
   transform is already saying the card's tilt, and both have to be
   true at once. `pan-y` leaves a long back scrollable by touch while
   still handing the sideways travel to us. */
.deck-face.is-flipped { touch-action: pan-y; }
.deck-face.is-thrown {
  animation: none;
  transform: translateX(var(--card-shift, 0px))
             rotate(calc(var(--card-tilt, -0.25deg) * -1 + var(--card-lean, 0deg)));
}
/* Past the line the card says which way it is going, in the answers'
   own two colours, before the finger lets go. The word sits at the
   card's TOP CENTRE, not out at the edge it is heading for: the card
   fills the box, so whichever edge it leans toward is the one about
   to pass under the frame, and a label there is a label you can't
   read at exactly the moment you need it. */
.deck-face[data-throw] { border-width: 4px; }
.deck-face[data-throw="yes"] { border-color: #4a7c59; }
.deck-face[data-throw="no"]  { border-color: var(--accent); }
.deck-face[data-throw]::after {
  position: absolute;
  top: 6px;
  left: 50%;
  transform: translateX(-50%);
  font-family: 'Pixelify Sans', sans-serif;
  font-size: 13px;
  background: var(--bg-page);
  border: 2px solid currentColor;
  border-radius: 4px;
  padding: 1px 8px;
}
.deck-face[data-throw="yes"]::after { content: 'yes'; color: #4a7c59; }
.deck-face[data-throw="no"]::after  { content: 'no'; color: var(--accent); }

/* THE PRACTICE CHART: the habit log's own grid, read off the practice
   log. The cells' colours are written by paper.js from the same WEEK
   palette the wall chart uses — a met day in its weekday's pastel, a
   day practiced short of the goal in a wash of it. Everything else
   (the blank day, the day not yet lived, the day before the first
   card) is already said by .log-cell. */
.deck-chart { margin-top: 8px; }
.deck-chart-note { margin-top: 8px; }
/* The chart's cells are BUTTONS here — a finger has no hover, so the
   day's count has to be pressable *(13 Aug 2026, from the user: "the
   tooltip count should show on phones too")*. A button brings its own
   border, padding and font, all of which would break the grid, so they
   come straight back off; everything else about a cell is .log-cell's
   business, exactly as on the habit chart. */
.deck-chart .log-cell {
  padding: 0;
  border: 0;
  font: inherit;
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
}
/* The day being read wears the same firm ring today wears — one thing
   the eye is being pointed at, said one way. */
.deck-chart .log-cell.is-read { box-shadow: inset 0 0 0 2px var(--ink); }

/* The dress itself — worn as data- attributes by the practice card
   and the sample alike (dressCardFace, paper.js). Sizes step the
   words up; the hand swaps whose lettering; the ink recolours both
   faces, in pen colours that hold on the dark page too. */
.deck-face[data-size="big"] .deck-face-front    { font-size: clamp(24px, 10cqw, 72px); }
.deck-face[data-size="big"] .deck-face-back     { font-size: clamp(15px, 5cqw, 38px); }
.deck-face[data-size="bigger"] .deck-face-front { font-size: clamp(28px, 12cqw, 84px); }
.deck-face[data-size="bigger"] .deck-face-back  { font-size: clamp(17px, 6cqw, 44px); }
/* Turned, the front steps back — a shade past the back's size, the
   app's own proportion — in every dress. */
.deck-face[data-size="big"].is-flipped .deck-face-front    { font-size: clamp(16px, 5.2cqw, 40px); }
.deck-face[data-size="bigger"].is-flipped .deck-face-front { font-size: clamp(18px, 6.2cqw, 46px); }
.deck-face[data-font="pixel"] .deck-face-front,
.deck-face[data-font="pixel"] .deck-face-back { font-family: 'Pixelify Sans', monospace; }
.deck-face[data-font="type"] .deck-face-front,
.deck-face[data-font="type"] .deck-face-back { font-family: 'Courier New', Courier, monospace; }
/* BLACK is the plain pen, and the default *(13 Aug 2026, from the
   user: "replace 'seasonal ink' with black")* — the season's writing
   blue used to hold this place. On the dark page it becomes the
   page's own ink: black on near-black is not a colour, it is an
   absence, and an option nobody can read is not an option. */
.deck-face[data-ink="black"] .deck-face-front,
.deck-face[data-ink="black"] .deck-face-back { color: #221f1c; }
body[data-dark] .deck-face[data-ink="black"] .deck-face-front,
body[data-dark] .deck-face[data-ink="black"] .deck-face-back { color: var(--ink); }
.deck-face[data-ink="blue"] .deck-face-front,
.deck-face[data-ink="blue"] .deck-face-back  { color: #3a6ea5; }
.deck-face[data-ink="red"] .deck-face-front,
.deck-face[data-ink="red"] .deck-face-back   { color: #b3533a; }
.deck-face[data-ink="green"] .deck-face-front,
.deck-face[data-ink="green"] .deck-face-back { color: #4a7c59; }
body[data-dark] .deck-face[data-ink="blue"] .deck-face-front,
body[data-dark] .deck-face[data-ink="blue"] .deck-face-back  { color: #8fb8e8; }
body[data-dark] .deck-face[data-ink="red"] .deck-face-front,
body[data-dark] .deck-face[data-ink="red"] .deck-face-back   { color: #e8938a; }
body[data-dark] .deck-face[data-ink="green"] .deck-face-front,
body[data-dark] .deck-face[data-ink="green"] .deck-face-back { color: #93c9a4; }

/* The shelf station: each book one line, its give-away at the far end —
   the projects head's own row shape. Since 15 Aug 2026 a row is two
   lines: the name, which is the door into the notebook, and under it
   what the notebook is about. */
.shelf-book-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
/* 1 1 0, not 1 1 auto: a long title must WRAP inside its own share of
   the row rather than grow the row and shove "remove" onto a line of
   its own, which left the buttons in a ragged column. */
.shelf-book-row .log-habit-name {
  flex: 1 1 0;
  min-width: 0;
  margin: 0;
  white-space: normal;
}
/* The season's own volume reads down the same left edge as the books
   under it — a centred line at the head of a left-aligned list looks
   like a title for the list rather than a row in it. */
#bookBody .log-card > .log-habit-name,
#bookBody .log-card > .log-empty { text-align: left; }
/* THE NAME IS THE DOOR — a button wearing the name's own clothes, so
   nothing about the row says "control" until the pointer is on it. */
.shelf-book-open {
  border: none;
  background: transparent;
  padding: 0;
  text-align: left;
  cursor: pointer;
  font-family: 'Nunito', system-ui, sans-serif;
}
.shelf-book-open:hover { color: var(--accent); }
.shelf-book-note {
  flex: 1 1 100%;
  margin: 0;
  text-align: left;
  font-size: 11px;
}
/* Naming a book and saying what it is about are one act, so the kind
   sits in the add row beside the name rather than a screen away. */
.shelf-add { flex-wrap: wrap; }
.shelf-kind {
  flex: 0 1 auto;
  cursor: pointer;
  font-size: 13px;
  color: var(--ink-soft);
}
/* A book that has not been named yet still has to be pressable — the
   words that open it are the only door it has. Quiet and italic, so it
   reads as a description of the book rather than as its name. */
.shelf-book-open.is-unnamed {
  font-style: italic;
  font-weight: 600;
  color: var(--ink-soft);
}

/* ---------- a book pops up over the booth ----------
   *(15 Aug 2026, from the user: "we need a book modal. not on the to do
   list left panel")*. The plan modal's own box, and A FIXED HEIGHT
   rather than a shrinking one: a book you turn leaves in must not
   change size under your hand as one leaf runs longer than the last.
   The body scrolls inside it; the turners stand under the body. */
/* AND IT OPENS AS BIG AS THE PLANNER *(16 Aug 2026: "optimal size on
   screen for reading")*. A book is the app's other writing surface —
   the one place you sit and put sentences down — so 560 was the
   narrowest it should ever have been. It grows with the desk the way
   the planner does, between the same two walls, and the pinned height
   above still holds: turning leaves must not change the box under
   your hand. */
.book-modal .modal-box {
  width: min(clamp(680px, 56vw, 940px), calc(100vw - 32px));
  height: min(clamp(680px, 78vh, 900px), calc(100vh - 32px));
  height: min(clamp(680px, 78dvh, 900px), calc(100dvh - 32px));
}
.book-modal .modal-body { gap: 10px; }
/* A NAME CAN RUN LONG *(15 Aug 2026, her ruling: "'a book' to
   changeable title")*. The bar wears whatever the book is called, and a
   book can be called anything — so it takes the room there is and trims
   the tail, and the × keeps its corner however long the name is. */
.book-modal .modal-header { gap: 10px; }
.book-modal .modal-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.book-modal .modal-close { flex: 0 0 auto; }
/* The modal body is a flex column, and every written line in the house
   carries `flex: 1 1 auto` — so a leaf's boxes stretched to fill the
   whole leaf, and the book's name stood ninety pixels tall. On a leaf,
   a box is as tall as what it holds. */
#bookBody > * { flex: 0 0 auto; }
/* ---------- the plot leaf's way to one more box ----------
   *(15 Aug 2026, from the user: "allow user to make multiple boxes, so
   it can be a box per chapter")*. It stands at the foot of the run,
   where the box it makes will appear — a plate like every other plate,
   and the only control on the leaf. */
.book-another {
  align-self: flex-start;
  margin-top: 14px;
  padding: 6px 12px;
  border: 2px solid var(--ink);
  border-radius: 5px;
  background: rgba(255, 255, 255, .30);
  box-shadow: 2px 2px 0 var(--ink);
  color: var(--ink);
  cursor: pointer;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .8rem;
  letter-spacing: .06em;
  text-transform: lowercase;
}
body[data-dark] .book-another { background: rgba(255, 255, 255, .06); }
.book-another:hover { background: var(--accent); }
.book-another:active { transform: translate(1px, 1px); box-shadow: 1px 1px 0 var(--ink); }

/* The line above a plot box for what that bit is called. It sits where
   a question's words sit on every other leaf — same place, same size,
   same grey — because here what the box is called IS the question. */
.book-part-name {
  width: 100%;
  padding: 0 0 4px;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .74rem;
  letter-spacing: .08em;
  text-transform: lowercase;
  color: var(--ink-soft);
}
.book-part-name::placeholder { color: rgba(45, 58, 72, .3); }
body[data-dark] .book-part-name::placeholder { color: rgba(240, 230, 216, .3); }

/* ---------- the foot of the page: the way on ----------
   *(15 Aug 2026, her ruling: "maybe even like scrolling left and right
   in pages … like a real book, because this is quite dense")*. It sits
   outside the scroll, so the way to the next leaf is in the same place
   on every leaf however long the leaf runs — and it says where in the
   book you are standing, because a turner with nothing named beside it
   is a button you press to find out what happens. */
.book-turn-bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 14px;
  border-top: 2px solid var(--ink);
  background: rgba(45, 58, 72, .05);
}
body[data-dark] .book-turn-bar { background: rgba(255, 255, 255, .04); }
.book-turn { flex: 0 0 auto; }
/* Where you are, said once and said plainly, standing between the two
   arrows that move it. It used to be headed on the leaf as well, five
   hundred pixels above — the same words twice. */
.book-turn-name {
  flex: 1 1 auto;
  text-align: center;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: 1rem;
  letter-spacing: .08em;
  text-transform: lowercase;
  color: var(--ink);
}

/* Throwing one out came from the all-books list into the book itself,
   and stands alone at the foot of the cover — far from the pen, quiet
   until you look for it, blunt when you find it. */
.book-bin {
  align-self: flex-start;
  margin-top: 22px;
  background: transparent;
  opacity: .55;
}
.book-bin:hover { opacity: 1; }

/* FILING AWAY STANDS ABOVE THROWING OUT, and stands PLAINLY *(16 Aug
   2026)* — a full-strength button with a line under it saying what
   keeping means, where the bin is a quiet one you have to look for.
   The two are the same size and the same shape on purpose: the
   difference between them is the words, not the weight. */
.book-away {
  align-self: flex-start;
  margin-top: 26px;
}
.book-away-note {
  margin: 6px 0 0;
  max-width: 46ch;
  font-size: .82rem;
  line-height: 1.45;
  opacity: .7;
}

/* ---------- the archive, under the floor ----------
   *(16 Aug 2026)* An index, so it reads like one: a count at the top, a
   two-word switch for the order, then one row per book — its name in
   the house's hand, its facts small beneath, and the one move it can
   make on the right. */
/* The way into a long index. Full width, because what you type in it is
   a sentence's worth of remembering, not a keyword. */
.archive-find {
  width: 100%;
  margin: 0 0 10px;
}
.archive-count {
  margin: 0 0 10px;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .95rem;
  letter-spacing: .06em;
  color: var(--ink);
  opacity: .8;
}
.archive-order {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
}
.archive-order-btn {
  font: inherit;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .95rem;
  letter-spacing: .06em;
  padding: 3px 10px;
  color: var(--ink);
  background: transparent;
  border: 2px solid var(--ink);
  cursor: pointer;
  opacity: .55;
}
.archive-order-btn:hover { opacity: .85; }
/* The order you are in is not a hover state — it stays pressed. */
.archive-order-btn[data-on] {
  opacity: 1;
  background: var(--ink);
  color: var(--bg-page);
}
/* The swap's question, asked inside the list it is about. */
.archive-asking {
  margin: 0 0 6px;
  font-size: .9rem;
  line-height: 1.45;
  color: var(--ink);
}
.archive-cancel { margin-bottom: 10px; }

.archive-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
/* A filed-away book is not a lesser book — it is somewhere else. One
   step back in the ink, and the left edge marks the ones downstairs. */
.archive-row[data-filed] { border-left: 3px solid var(--ink); padding-left: 7px; }
.archive-row[data-filed] .archive-title { opacity: .8; }
/* The name is a door, so it is a button — but it wears no button, the
   way the words of a checklist do. */
.archive-name {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: left;
  font: inherit;
  padding: 2px 0;
  background: transparent;
  border: 0;
  cursor: pointer;
  color: var(--ink);
}
.archive-title {
  font-family: 'Pixelify Sans', sans-serif;
  font-size: 1rem;
  letter-spacing: .04em;
}
.archive-name:hover .archive-title { text-decoration: underline; }
.archive-facts {
  font-size: .78rem;
  opacity: .65;
}
/* Nothing found: a plain line where the rows would be, in the list's
   own place, so the eye does not have to go looking for the answer. */
.archive-none {
  list-style: none;
  padding: 10px 2px;
  font-size: .9rem;
  opacity: .7;
}
.archive-move { flex: 0 0 auto; }
/* A row that is not part of the question being asked keeps its width
   and says nothing. */
.archive-move.is-quiet { display: block; width: 1px; }

/* ---------- a leaf of questions ----------
   *(15 Aug 2026, her rulings: "give things more outlines and clear
   affordances", "it needs to be simple enough a kid can do it", and
   "it should still feel pleasant to look back through")*

   Two to five questions to a leaf, every one of them open. Each is a
   plate: the question in the house's pixel hand across the top, the
   box under it drawn as a box, with the border and the hard offset
   shadow every other written thing in the house wears. An empty one
   reads as somewhere to write; a full one keeps its question standing
   above the words, so a book you filled in months ago still tells you
   what it was answering. */
.book-prompts { display: flex; flex-direction: column; gap: 14px; }
.book-field {
  border: 2px solid var(--ink);
  border-radius: 5px;
  background: rgba(255, 255, 255, .30);
  box-shadow: 2px 2px 0 var(--ink);
  padding: 8px 10px 6px;
}
body[data-dark] .book-field { background: rgba(255, 255, 255, .05); }
/* AN ANSWERED QUESTION WEARS NOTHING *(15 Aug 2026, from the user:
   "double clicking a book section highlighted it or something, it
   should do nothing … keep it flat without color")*. A box you had
   written in used to grow an accent stripe down its left edge. Coming
   and going under the hand, it read as the page highlighting what you
   had just typed — and it was the second mark on the same fact after
   the ✓ was taken off that morning. The words in the box already say
   the question is answered. Every plate is the same plate now, full or
   empty. */
/* THE QUESTION IS THE HANDLE *(15 Aug 2026, from the user: "make it so
   user can make box wider or collapse it entirely … only inner")*. The
   words fold their own box, the way the words press the tick everywhere
   else in the house — so there is nothing extra on the plate to aim at,
   and the ▾ says what the words will do before you touch them. It is a
   button drawn as the label it always was: no plate, no border, the
   whole line pressable. */
.book-prompt-label {
  display: block;
  width: 100%;
  margin: 0;
  padding: 0 0 4px;
  border: 0;
  background: none;
  box-shadow: none;
  text-align: left;
  cursor: pointer;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .74rem;
  letter-spacing: .08em;
  text-transform: lowercase;
  color: var(--ink-soft);
}
.book-prompt-label:hover { color: var(--ink); }
.book-fold-mark { font-size: .68rem; opacity: .75; }
/* Folded: the question stands alone and its box waits under it. The
   padding goes with the box, so a shut plate is a line, not a line in
   a room of its own. */
.book-field.is-folded { padding-bottom: 2px; }
.book-field.is-folded .book-prompt-label { padding-bottom: 0; }
.book-field.is-folded .book-section { display: none; }
/* It grows with what is written in it, so a notebook you have thought
   in opens at its full length. It is a written page that takes the pen,
   not a textarea *(15 Aug 2026: bold and italic)*, so its height is
   simply the height of what is on it — nothing measures it. */
.book-section {
  display: block;
  width: 100%;
  min-height: 3.4em;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 600;
  outline: none;
  /* PULL IT AS BIG AS YOU NEED IT *(15 Aug 2026, from the user: "make
     it so user can make box wider or collapse it entirely")*. A box
     stands as tall as its words until the hand says otherwise, and then
     the hand wins — drag the corner and that is the height of it, kept
     with the book *(renderBookGroup)*. Only downward: the width of a
     leaf is the width of the book, and the book is not resizable —
     "they must exit the modal to go back to window, affirming we can
     only put attention to one thing at a time". */
  resize: vertical;
  overflow: auto;
}
/* A page that takes the pen has no placeholder of its own, so an empty
   one wears its question in the same grey a placeholder would. */
.book-section.is-blank::before {
  content: attr(data-hint);
  color: rgba(45, 58, 72, .38);
  pointer-events: none;
}
body[data-dark] .book-section.is-blank::before { color: rgba(240, 230, 216, .38); }
/* Weight, slant, line — and the two colours — drawn as they are
   written. The marks carry no colour of their own in the save (see
   cleanRich in save.js); these five lines are the whole of what yellow
   and red mean, so both can be changed here for every word anybody has
   ever marked. */
.book-section b { font-weight: 800; }
.book-section i { font-style: italic; }
.book-section u { text-decoration: underline; text-underline-offset: 2px; }
.book-section mark {
  background: var(--pen-hilite);
  color: #23252a;                    /* the highlighter is always dark-on-yellow */
  border-radius: 2px;
  padding: 0 1px;
}
.book-section .ink-red { color: var(--pen-red); }
/* Bullets *(15 Aug 2026, from the user: "add ability to add bullet
   points too")*. Indented just far enough that the dots sit inside the
   box's edge rather than under it, and no tighter than the lines around
   them — a list in a notebook is thinking laid out, not a table. */
.book-section ul {
  margin: 4px 0;
  padding-left: 1.15em;
  list-style: disc outside;
}
.book-section li { margin: 0 0 2px; }

/* ---------- the pen: B and I ----------
   *(15 Aug 2026, from the user: "give user ability to add bold and
   italics into this … so they can bold quotes they like for example")*
   Over the leaf, acting on whatever box you are writing in. ⌘B and ⌘I
   do the same for a hand that knows them; these two are for the hand
   that does not. */
.book-pen {
  display: flex;
  gap: 6px;
  align-self: flex-end;
}
.book-pen-btn {
  width: 30px;
  height: 26px;
  border: 2px solid var(--ink);
  border-radius: 4px;
  background: rgba(255, 255, 255, .30);
  box-shadow: 2px 2px 0 var(--ink);
  color: var(--ink);
  cursor: pointer;
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: 14px;
  line-height: 1;
}
body[data-dark] .book-pen-btn { background: rgba(255, 255, 255, .06); }
.book-pen-btn.is-bold { font-weight: 800; }
.book-pen-btn.is-italic { font-style: italic; font-weight: 700; }
.book-pen-btn.is-under { text-decoration: underline; text-underline-offset: 2px; }
.book-pen-btn.is-bullet { font-size: 17px; }
/* EACH KEY WEARS WHAT IT DOES. B is bold, I is slanted, U is
   underlined — so the two colours are the same letter painted the way
   they paint: an A sitting on the yellow, an A in the red. Nothing here
   needs a word to say what it is, which is the test the whole tray had
   to pass *(the rule: it should be simple enough a kid can do it)*. */
.book-pen-btn.is-hilite { background: var(--pen-hilite); color: #23252a; }
.book-pen-btn.is-red { color: var(--pen-red); font-weight: 800; }
.book-pen-btn:hover { background: var(--accent); }
/* The highlighter's key keeps its yellow under the hand — a key that
   turns a different colour when you reach for it stops being a swatch. */
.book-pen-btn.is-hilite:hover { background: var(--pen-hilite); }
.book-pen-btn:active { transform: translate(1px, 1px); box-shadow: 1px 1px 0 var(--ink); }

/* ---------- the cover ----------
   Every notebook is a book, whatever it is about *(15 Aug 2026, from
   the user: "a book in your heart about the love of that movie or
   game")*, so this leaf has one shape and the kind only changes what
   it asks. The name at the top in the hand you write in, then what it
   is, then the facts — each one a labelled box, because a fact with
   its question worn as grey placeholder text loses the question the
   moment it is answered. */
.shelf-back { margin-right: 8px; }
.book-title {
  width: 100%;
  font-size: 20px;
  font-weight: 700;
  padding: 7px 10px;
  border: 2px solid var(--ink);
  border-radius: 5px;
  background: rgba(255, 255, 255, .30);
  box-shadow: 2px 2px 0 var(--ink);
}
body[data-dark] .book-title,
body[data-dark] .book-kind,
body[data-dark] .book-fact { background: rgba(255, 255, 255, .05); }
.book-kind {
  width: 100%;
  cursor: pointer;
  font-size: 14px;
  font-weight: 600;
  color: var(--write);
  padding: 6px 10px;
  border: 2px solid var(--ink);
  border-radius: 5px;
  background: rgba(255, 255, 255, .30);
  box-shadow: 2px 2px 0 var(--ink);
  margin-bottom: 6px;
}
.book-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 12px;
}
.book-fact-cell {
  flex: 1 1 40%;
  min-width: 130px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
/* A fact that asks a whole question rather than naming a thing takes a
   line to itself — clipped to half a row, "how it came to you" read as
   "how it came to y…". */
.book-fact-cell.is-wide { flex: 1 1 100%; }
.book-fact-label {
  font-family: 'Pixelify Sans', sans-serif;
  font-size: .68rem;
  letter-spacing: .06em;
  text-transform: lowercase;
  color: var(--ink-soft);
}
.book-fact {
  width: 100%;
  font-size: 14px;
  font-weight: 600;
  padding: 5px 8px;
  border: 2px solid var(--ink);
  border-radius: 4px;
  background: rgba(255, 255, 255, .30);
  box-shadow: 2px 2px 0 var(--ink);
}

/* The leaf of notes that stood at the back of a book — a ruled page
   with a margin, a note per entry, a line to add tonight's — was taken
   down the evening it went up *(15 Aug 2026, her ruling: "remove
   'notes' section … the entire thing is notes")*. Its rules went with
   it; what anybody wrote on it is still in the save. */

/* ---------- the week's recap: a page in the season's book ----------
   It wears the day page's own clothes — same paper, same rules, same
   hand — because it IS a page of the same book and not a report about
   it. What differs is that nothing on it takes a tick: the week is
   already spent, and this is the reading of it. */
.recap-sheet {
  flex: 1 1 auto;
  overflow-y: auto;
  min-height: 0;
  padding: 2px 2px 8px;
}
/* `.recap-title` dressed the sheet's own "summer · week 8" heading.
   Gone 15 Aug 2026 with the heading — the date plate says which week
   this is, and said it twice over between them. */
.recap-head { margin: 14px 0 4px; }
.recap-head:first-of-type { margin-top: 0; }
.recap-line {
  margin: 2px 0;
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: 14px;
  color: var(--ink);
}
.recap-line.is-quiet { color: var(--ink-soft); font-size: 13px; }

/* One line per habit or aim: the words, then the week, then the count
   — the count last and hard right, so the eye can run down the column
   of numbers without reading a single name. */
.recap-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 3px 0;
  font-family: 'Nunito', system-ui, sans-serif;
  font-size: 14px;
}
.recap-row-name { flex: 1 1 auto; min-width: 0; color: var(--ink); }
.recap-row-name.is-done {
  text-decoration: line-through;
  text-decoration-color: var(--done);
  color: var(--ink-soft);
}
.recap-row-count { flex: 0 0 auto; font-size: 13px; color: var(--ink-soft); }
/* The week as seven cells — the wall chart's hand, one week wide. */
.recap-week { flex: 0 0 auto; display: flex; gap: 2px; }
.recap-week .log-cell { width: 9px; height: 9px; }

.recap-note {
  display: block;
  width: 100%;
  min-height: 64px;
  resize: vertical;
  margin: 4px 0 10px;
  padding: 6px;
}

/* ------------------------------------------------------------
   THE WELCOME PLATE *(15 Aug 2026)*

   The first thing a new library ever says, sitting on the paper panel
   under the clock row. It is on THIS side because the room holds no
   words — and, more practically, because the room must stay reachable
   while it shows: the whole invitation is to go and open the curtains,
   so a plate that covered them would be asking for something it was
   blocking.

   Built from the same parts as every other plate in the house — page
   ground, two-pixel ink border, the hard offset shadow, a Pixelify
   heading — so it reads as part of the console rather than as a notice
   pasted over it. It takes no press and has no dismiss: it goes when
   the thing it asked for has been done.
   ------------------------------------------------------------ */
#welcomePlate {
  background: var(--bg-page);
  border: 2px solid var(--ink);
  border-radius: 12px;
  box-shadow: 2px 2px 0 var(--ink);
  padding: 10px 14px;
  margin: 0 0 10px;
}
.welcome-title {
  margin: 0 0 6px;
  font-family: 'Pixelify Sans', sans-serif;
  font-size: 0.9rem;
  color: var(--ink);
}
.welcome-line {
  margin: 0 0 6px;
  font-size: 0.86rem;
  line-height: 1.45;
  color: var(--ink-soft);
}
.welcome-line:last-child { margin-bottom: 0; }
