/* ITZA Diary - the app shell.
 *
 * Loaded AFTER diary.css: the design system stays the authority on type,
 * color and the editor; this file is the machinery that makes the product
 * read as an app - a navigation stack of panes, a floating dock, sheets,
 * safe areas, and the suppression of every web tell. Where the two files
 * disagree, this one is later in the cascade on purpose.
 *
 * The iOS field floor lives at the BOTTOM OF THIS FILE and must stay the
 * last font-size rule in the cascade - see the register
 * (docs/rebuild/07-do-not-regress.md).
 */

/* The product's voice: Fraunces, self-hosted from this same origin (the
   one-permitted-remote-origin rule is about cdn.itzalabs.com being the ONLY
   remote; these ship inside our own upload and are precached by the service
   worker). Variable weight, latin subset, ~110KB the pair. */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/fraunces-latin.woff2') format('woff2');
}
@font-face {
  font-family: 'Fraunces';
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/fraunces-latin-italic.woff2') format('woff2');
}
/* THE WORDMARK'S FACE (owner, 2026-09-07): the logo is Roboto - brand,
   not prose. Self-hosted like every other face here, variable weight,
   latin subset, and used by the brand line alone: the chrome keeps
   Instrument Sans and the writing keeps Fraunces. ITZA Tab took the same
   face for its logo on 2026-09-06; one file, one look across the
   platform. */
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/roboto-latin.woff2') format('woff2');
}
/* The UI face (02 sec 2.11): Instrument Sans, variable weight, 30KB. The
   chrome - bars, buttons, labels - stops being whatever the device defaults
   to and becomes the product's own voice. No italic: UI never speaks in it. */
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('../fonts/instrument-sans-latin.woff2') format('woff2');
}

/* THE ENTRY-FONT SHELF. Six more voices a diary can be written in, chosen
   in Settings. Declaring a face costs nothing: the browser only fetches a
   file when text actually renders in that family - so these ride the
   upload but download ON SELECTION, and the service worker keeps whichever
   one was chosen for offline (user rule: sizeable optional things load
   when picked, never at first run). */
@font-face {
  font-family: 'Lora'; font-style: normal; font-weight: 400 700;
  font-display: swap; src: url('../fonts/lora-latin.woff2') format('woff2');
}
@font-face {
  font-family: 'Caveat'; font-style: normal; font-weight: 400 700;
  font-display: swap; src: url('../fonts/caveat-latin.woff2') format('woff2');
}
@font-face {
  font-family: 'Dancing Script'; font-style: normal; font-weight: 400 700;
  font-display: swap; src: url('../fonts/dancing-script-latin.woff2') format('woff2');
}
@font-face {
  font-family: 'Patrick Hand'; font-style: normal; font-weight: 400;
  font-display: swap; src: url('../fonts/patrick-hand-latin.woff2') format('woff2');
}
@font-face {
  font-family: 'Special Elite'; font-style: normal; font-weight: 400;
  font-display: swap; src: url('../fonts/special-elite-latin.woff2') format('woff2');
}
@font-face {
  font-family: 'Atkinson Hyperlegible'; font-style: normal; font-weight: 400;
  font-display: swap; src: url('../fonts/atkinson-latin.woff2') format('woff2');
}

:root {
  /* Fraunces leads both faces; the system serifs remain as the fallback
     and as the whole stack anywhere the font file has not arrived. */
  --serif-display: 'Fraunces', 'Hoefler Text', 'Baskerville Old Face', Baskerville,
    'Times New Roman', serif;
  --serif-body: 'Fraunces', 'Iowan Old Style', 'Palatino Linotype', Palatino, Charter,
    'Bitstream Charter', 'Source Serif Pro', serif;
  --sans: 'Instrument Sans', system-ui, -apple-system, 'Segoe UI', Roboto,
    Helvetica, Arial, sans-serif;

  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  --kb: 0px;                       /* keyboard inset, written by nav.js */

  --bar-h: 62px;                   /* the floating dock */
  --dock: calc(var(--bar-h) + var(--safe-b) + 20px);

  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;
  --r-full: 999px;

  --m-tap: 90ms;
  --m-quick: 160ms;
  --m-enter: 280ms;
  --m-exit: 240ms;
  --ease-enter: cubic-bezier(0.32, 0.72, 0, 1);
  --ease-exit: cubic-bezier(0.4, 0, 1, 1);

  --shadow-card: 0 1px 2px rgba(11, 42, 74, 0.06), 0 8px 24px rgba(11, 42, 74, 0.08);
  --shadow-float: 0 2px 6px rgba(11, 42, 74, 0.14), 0 12px 32px rgba(11, 42, 74, 0.18);

  /* Theme hooks. Phase 3 overrides these per theme; these defaults ARE the
     base chrome, complete on their own. */
  --accent: var(--navy);
  --accent-ink: #ffffff;
  --hero-art: linear-gradient(160deg, #0b2a4a 0%, #14508c 55%, #2e7dc4 100%);
  --surface: var(--paper);
  --frost: rgba(251, 250, 247, 0.82);
}

html[data-theme='dark'] {
  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.5);
  --shadow-float: 0 2px 6px rgba(0, 0, 0, 0.5), 0 12px 32px rgba(0, 0, 0, 0.6);
  --accent: #4d9bff;
  --accent-ink: #081524;
  --frost: rgba(20, 23, 28, 0.82);
}

/* ---------------------------------------------------------------------
   Killing the web tells (02 sec 2.4)
   --------------------------------------------------------------------- */

* { -webkit-tap-highlight-color: transparent; }

html, body {
  height: 100%;
  overflow: hidden;               /* the document never scrolls; panes do */
  overscroll-behavior: none;
  touch-action: manipulation;
  -webkit-font-smoothing: antialiased;
}

body {
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}

/* Entry text, fields and the editor stay selectable - a diary you cannot
   copy from is a cage. */
.selectable, .body, .article p, .article h1, .article h2, .article h3,
.article li, .article blockquote, .diary-title, .edpreview,
input, textarea, [contenteditable], [contenteditable='plaintext-only'] {
  user-select: text;
  -webkit-user-select: text;
  -webkit-touch-callout: default;
}

/* A PHONE SHOWS NO SCROLLBAR because a thumb needs none and the bar would
   only cover the paper. A POINTER IS NOT A THUMB: without a bar there is
   nothing to drag and nothing to say the screen even goes further, which
   left a mouse with no wheel unable to move the page at all (owner report,
   2026-08-14). So the bar comes back wherever a pointer lives - thin, in
   the paper's own ink, never the OS's grey slab. */
.pane { scrollbar-width: none; -ms-overflow-style: none; }
.pane::-webkit-scrollbar { display: none; }

@media (hover: hover) and (pointer: fine) {
  .pane { scrollbar-width: thin; scrollbar-color: var(--rule) transparent; }
  .pane::-webkit-scrollbar { display: block; width: 10px; }
  .pane::-webkit-scrollbar-track { background: transparent; }
  .pane::-webkit-scrollbar-thumb {
    background: var(--rule);
    border: 3px solid transparent;
    background-clip: content-box;
    border-radius: var(--r-full);
  }
  .pane::-webkit-scrollbar-thumb:hover { background: var(--muted); background-clip: content-box; }
}

:focus { outline: none; }
:focus-visible { outline: 2px solid var(--blue-bright); outline-offset: 2px; }
/* The pane holds the keyboard so the arrows can scroll it - it is a
   surface, not a control, and a ring around the whole screen would say
   otherwise. Controls inside it keep their rings. */
.pane:focus, .pane:focus-visible { outline: none; }

/* ---------------------------------------------------------------------
   The shell: screens, view, panes
   --------------------------------------------------------------------- */

.screen { position: fixed; inset: 0; display: none; }
.screen.on { display: block; }

#screen-app { display: none; }
#screen-app.on { display: flex; flex-direction: column; }

#view { position: relative; flex: 1; min-height: 0; }

.pane {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  background-color: var(--paper);
  background-image: var(--bg-pattern, none);
  background-size: 140px 140px;
  padding-bottom: var(--dock);
  /* EVERY PANE IS ITS OWN STACKING CONTEXT. Without this, position:absolute
     with z-index:auto lets a COVERED pane's positioned children (sticky top
     bar, sticky year pill, the relative-positioned cards) paint straight
     through the pane on top of them - the timeline's entries floated over
     the open editor (user screenshot, 2026-08-09). With isolation, DOM
     order alone decides which pane wins, which is exactly the stack. */
  isolation: isolate;
}

/* A covered pane keeps its DOM (scroll memory, decrypted content) but
   cannot be reached or seen through. */
.pane-under { pointer-events: none; }

@keyframes paneIn {
  from { transform: translate3d(100%, 0, 0); }
  to   { transform: translate3d(0, 0, 0); }
}
@keyframes paneOut {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(100%, 0, 0); }
}
@keyframes paneFade {
  from { opacity: 0; transform: translate3d(0, 4px, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

.pane-enter { animation: paneIn var(--m-enter) var(--ease-enter); }
.pane-exit  { animation: paneOut var(--m-exit) var(--ease-exit) forwards; z-index: 5; }
.pane-fade  { animation: paneFade 180ms ease-out; }

html[data-motion='reduce'] .pane-enter,
html[data-motion='reduce'] .pane-exit,
html[data-motion='reduce'] .pane-fade { animation-duration: 1ms; }

/* THE + FAN-OUT, per section: three or four raised icon discs sprung
   over the +. Position classes are fpN-i (N items, i-th). The scrim is
   80; tappable-above-scrim is 81, as everywhere. */
.fab-pop {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--safe-b) + var(--bar-h) + 4px);
  z-index: 81;
  width: 0;
  height: 0;
}
.fab-pill {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 84px;
  height: 84px;
  margin-left: -42px;
  border: 1px solid var(--rule);
  border-radius: 50%;
  background: var(--surface);
  box-shadow: var(--shadow-float);
  color: var(--ink);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  font-family: var(--sans);
  font-size: 0.62rem;
  font-weight: 600;
  line-height: 1.15;
  text-align: center;
  padding: 6px;
  transform: translate(var(--fp-x), var(--fp-y));
  animation: fabPillIn 0.16s ease-out backwards;
}
.fab-pill svg { flex: none; }
.fab-pill:active { transform: translate(var(--fp-x), var(--fp-y)) scale(0.94); }
.fp3-0 { --fp-x: -76px; --fp-y: -58px; }
.fp3-1 { --fp-x: 0px;   --fp-y: -96px; animation-delay: 0.04s; }
.fp3-2 { --fp-x: 76px;  --fp-y: -58px; animation-delay: 0.08s; }
.fp4-0 { --fp-x: -112px; --fp-y: -36px; }
.fp4-1 { --fp-x: -40px;  --fp-y: -88px; animation-delay: 0.04s; }
.fp4-2 { --fp-x: 40px;   --fp-y: -88px; animation-delay: 0.08s; }
.fp4-3 { --fp-x: 112px;  --fp-y: -36px; animation-delay: 0.12s; }
@keyframes fabPillIn {
  from { transform: translate(0, 0) scale(0.4); opacity: 0; }
  to   { transform: translate(var(--fp-x), var(--fp-y)) scale(1); opacity: 1; }
}
html[data-motion='reduce'] .fab-pill { animation-duration: 1ms; }

/* THE JOURNAL'S TODAY CARD - the prompt, a mood, a write button. */
.tl-today { display: block; padding: 16px; margin: 14px 0 4px; }
.tl-today-q { font-family: var(--serif-body); font-size: 1.02rem; line-height: 1.5; margin: 0 0 10px; }
.tl-moods { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 12px; }
.tl-mood {
  border: none;
  background: transparent;
  padding: 4px;
  min-width: 0;
  min-height: 0;
  line-height: 0;
  border-radius: var(--r-full);
  cursor: pointer;
}
.tl-mood:active { transform: scale(0.88); }

/* THE JOURNAL'S MOOD CHARTS in Mine: the weekly line and the pixel
   year. Inline SVG, index-fed, no library. */
.mi-line { width: 100%; height: 68px; display: block; }
.mi-line polyline { fill: none; stroke: var(--accent, #b3261e); stroke-width: 2; }
.mi-line circle { fill: var(--accent, #b3261e); }
.mi-line .mi-line-mid { stroke: var(--rule); stroke-width: 1; stroke-dasharray: 3 4; }
.mi-px {
  display: grid;
  grid-template-rows: repeat(7, 8px);
  grid-auto-flow: column;
  grid-auto-columns: 8px;
  gap: 2px;
  overflow-x: auto;
  padding-bottom: 4px;
}
.mi-px i { border-radius: 2px; }
.mi-px .mi-px-empty { background: var(--rule); opacity: 0.35; }

/* The second book's one-time discovery strip. */
.tl-otherbook {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 10px 0 0;
  padding: 8px 6px 8px 12px;
  border: 1px dashed var(--rule);
  border-radius: var(--r-md);
  font-family: var(--sans);
  font-size: 0.8rem;
  color: var(--muted);
}
.tl-otherbook span { flex: 1; }

.tl-today-done .tl-today-q { display: flex; align-items: center; gap: 8px; color: var(--ink); }

/* Year in review's reading flow. */
.yr-entry { border-top: 1px solid var(--rule); padding: 14px 0; }
.yr-entry h3 { font-family: var(--sans); font-size: 0.82rem; color: var(--muted); margin: 0 0 8px; }
.yr-body { font-family: var(--serif-body); }

/* THE JOURNAL EDITOR OPENS PLAIN - a default, cleared the moment this
   entry picks its own paper. */
.pane.jr-plain { background-image: none; }

/* THE DRAWER'S MODE TABS - the two faces of the wordmark side by side,
   centred over the drawer with real air between them. Legibility comes
   from the SURFACE, not from opacity games: the active face is a raised
   card wearing the full wordmark colours; the inactive one sits flat in
   muted ink - clearly present, clearly not the open book. (Opacity 0.42
   over pale paper made both names nearly invisible - owner screenshot,
   2026-08-22.) */
.drawer-head.mode-tabs {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 18px;
  /* The base .drawer-head carries the notch inset in its padding; this
     override must carry it too, or the tabs slide under the iPhone's
     status bar (owner screenshot, 2026-08-24). */
  padding: calc(var(--safe-t) + 18px) 12px 14px;
}
.mode-tab {
  background: none;
  border: 1px solid transparent;
  padding: 8px 14px;
  border-radius: var(--r-full);
  cursor: pointer;
}
.mode-tab .wm-itza, .mode-tab .wm-prod {
  color: var(--muted);
  font-weight: 600;
}
.mode-tab.on {
  background: var(--surface);
  border-color: var(--rule);
  box-shadow: var(--shadow-card);
}
.mode-tab.on .wm-itza { color: #0c7ffb; }
.mode-tab.on .wm-prod { color: #ad8009; font-weight: 800; }

/* ---------------------------------------------------------------------
   Top bars
   --------------------------------------------------------------------- */

.tbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 4px;
  min-height: 54px;
  padding: calc(var(--safe-t) + 4px) calc(8px + var(--safe-r)) 4px calc(8px + var(--safe-l));
  background: var(--frost);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  backdrop-filter: blur(18px) saturate(1.3);
  border-bottom: 1px solid var(--rule);
  font-family: var(--sans);
}

.tbar.overhero {
  background: transparent;
  border-bottom-color: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  position: absolute;
  left: 0; right: 0;
}

.tbar-title {
  flex: 1;
  text-align: center;
  font-family: var(--serif-display);
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tbtn {
  width: 44px;
  height: 44px;
  padding: 0;
  border: none;
  background: transparent;
  border-radius: var(--r-full);
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex: none;
}
.tbtn svg { width: 22px; height: 22px; }
.tbtn:active { transform: scale(0.94); }

/* A CONTROL A THUMB DOES NOT NEED AND A POINTER CANNOT DO WITHOUT.
   A phone has its own back - system button, edge swipe - so the shelf's
   arrow was clutter and came off (owner, 2026-08-13). A desktop has
   neither, and removing it left no way out at all (owner, 2026-08-14).
   VISIBILITY, not display: the button keeps its 44px either way, so the
   title stays centred exactly as the phone has it today, and hidden it
   is unclickable and out of the reader's path. */
.tbtn-pointer { visibility: hidden; }
@media (hover: hover) and (pointer: fine) {
  .tbtn-pointer { visibility: visible; }
}

/* THE LOGO, WHEREVER IT IS DRAWN - the top bar, the drawer head, the
   mode tabs. One rule, so a wordmark added later cannot quietly come out
   in the wrong face. */
.wm { font-family: 'Roboto', var(--sans); letter-spacing: -0.01em; }
.tbar .wm { font-size: 1.02rem; }

/* ---------------------------------------------------------------------
   The dock - floating bottom bar with the raised centre action
   --------------------------------------------------------------------- */

.dock {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  /* Lower than it was. This is a floating pill, so it clears the home
   * indicator rather than covering it - but safe-b + 12px put it 46px up,
   * which read as hovering (owner, 2026-08-21). Clearing the glyph is what
   * matters, not clearing the whole gesture region. */
  bottom: max(8px, calc(var(--safe-b) - 8px));
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 6px;
  height: var(--bar-h);
  padding: 0 14px;
  border-radius: var(--r-full);
  background: var(--frost);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  backdrop-filter: blur(18px) saturate(1.3);
  border: 1px solid var(--rule);
  box-shadow: var(--shadow-float);
}

.dock-btn {
  width: 58px;
  height: 48px;
  border: none;
  background: transparent;
  border-radius: var(--r-md);
  color: var(--muted);
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  cursor: pointer;
  font-family: var(--sans);
  font-size: 0.62rem;
  padding: 0;
}
.dock-btn svg { width: 23px; height: 23px; }
/* THE SELECTED DOCK BUTTON WENT INVISIBLE. It painted itself in
   var(--accent), which is a FILL colour: the FAB pairs it with
   --accent-ink precisely because nothing guarantees an accent is legible
   as text. Themes may state a near-white accent, and derived ones are
   allowed up to 0.78 lightness - on those, the open pane's own button
   dissolved into the dock and read as an empty slot (owner screenshot,
   2026-08-26, on Mine). Selection now shows as full-strength ink over a
   tint of the accent: the tint may be faint, the ink never is. */
.dock-btn.on {
  color: var(--ink);
  background: color-mix(in srgb, var(--accent) 20%, transparent);
}
.dock-btn:active { transform: scale(0.94); }

.fab {
  width: 56px;
  height: 56px;
  margin: 0 8px;
  border: none;
  border-radius: var(--r-full);
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: var(--shadow-float);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transform: translateY(-14px);
  transition: transform var(--m-tap) ease-out;
}
.fab svg { width: 26px; height: 26px; }
.fab:active { transform: translateY(-14px) scale(0.93); }

/* The dock hides while writing or reading a sheet - and while
   photographing pages: the import wizard's own actions sat UNDER the
   floating dock on a phone (owner screenshot, 2026-08-12), and no one
   needs Calendar mid-shoot. */
html[data-route='write'] .dock,
html[data-route='gate'] .dock,
html[data-route='import'] .dock,
html[data-route='shared'] .dock { display: none; }
/* THE HIDDEN ATTRIBUTE MUST ALWAYS WIN. `.dock { display:flex }` beats
   the UA's [hidden] rule, so a guest opening a shared link watched the
   owner's dock sit there until the gate's route CSS landed - and come
   BACK once they unlocked, where its + button wrecked their stack
   (owner report, 2026-08-12: visible 3s, then a soft-lock). */
.dock[hidden] { display: none !important; }
html[data-route='write'] .pane[data-route='write'] { padding-bottom: 0; }

/* ---------------------------------------------------------------------
   The timeline
   --------------------------------------------------------------------- */

.hero {
  position: relative;
  height: 215px;
  margin-bottom: -36px;
  background-image: var(--hero-art);
  background-size: cover;
  background-position: center;
  will-change: transform;
}
/* A CHARACTER GETS A HERO ITS OWN SIZE. The 215px band leaves ~66pt
   between the top bar and the cards on a notched phone - a 180pt
   creature can only overlap one or the other, and it did both in turn
   (owner screenshots, 2026-08-12). When a character mounts, the hero
   grows to hold it exactly: safe area + the 54px bar + the creature's
   own height + 8px of air + the 36px the cards overlap. The engine
   adds the class; art-only themes keep the painting band. */
.hero.has-char {
  /* 70, not the creature-box arithmetic's 98: the drawings keep ~8% of
     their box empty below the feet (measured 29px at 375w), and the
     honest gap is to the INK, not the box (owner: "larger than
     necessary", 2026-08-12). The box bottom dips ~20px under the year
     chip's row; the drawn creature clears it by ~30px. */
  height: calc(var(--safe-t, 0px) + 70px + min(46vw, 190px));
  /* THE HERO IS A STACKING CONTEXT (will-change: transform), so no
     z-index INSIDE it can climb over the card layer - the equalizer
     bars in the host's dipped bottom edge were painted but unreachable
     (elementFromPoint said so before a phone had to). The hero itself
     rises above the cards (tl-body is 2, the top bar is 20) and stops
     taking pointer events; the creature and the bars re-enable their
     own. Cards behind the transparent overlap keep their taps. */
  z-index: 3;
  pointer-events: none;
  /* AND WITH THE HERO RAISED, NOTHING MAY BE TUCKED UNDER IT. The band
     is pulled up 36px so cards slide under the fade - which works while
     the hero sits BELOW the card layer, because then the cards paint on
     top of the fade. Raised to 3 for the creature, the same 36px became
     a lid: the hero's own art and the opaque end of its fade covered
     whatever led the list, and a draft banner - shorter than the band -
     lost its top half on an iPhone 14 (owner screenshot, 2026-08-15).
     A raised hero therefore takes no bite out of the page; the 36px
     becomes air between the creature and the first row. */
  margin-bottom: 0;
}
.hero::after {
  content: '';
  position: absolute;
  inset: auto 0 0 0;
  height: 110px;
  background: linear-gradient(to bottom, transparent, var(--paper));
}

.tl-body {
  position: relative;
  z-index: 2;
  max-width: 34rem;
  margin: 0 auto;
  padding: 0 16px;
}

.tl-year {
  position: sticky;
  top: calc(var(--safe-t) + 52px);
  z-index: 3;
  display: inline-block;
  font-family: var(--sans);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--muted);
  background: var(--frost);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-radius: var(--r-full);
  padding: 3px 11px;
  margin: 14px 0 2px;
}

.card {
  position: relative;
  display: flex;
  /* THE OPENED ENTRY GOES BELOW THE ROW, NOT INTO IT. A card carries a
     .diary-body that the guest feed reveals in place (scroll view), and
     that body asks for `flex-basis: 100%` - which only puts it on its
     own line if this container WRAPS. Without this it stayed in the row
     as a fourth column, so the title was squeezed to a sliver and set
     one letter per line down the whole card while the body sat beside
     it in 85px (owner's reader, 2026-08-14 - measured: main 128px and
     the body in-row; with wrap, main 229px and the body 317px below).
     List view never showed it because there the card opens a page. */
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 12px;
  width: 100%;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-card);
  padding: 14px 14px 14px 18px;
  margin: 12px 0;
  cursor: pointer;
  overflow: hidden;
  font-family: var(--sans);
  color: var(--ink);
}
.card:active { transform: scale(0.985); }

/* The stained edge: mood colour down the left, bleeding into the corner
   like the stained edge of a page block (02 sec 2.11). One device, used for
   this and nothing else. */
.card::before {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 4px;
  background: var(--edge, var(--rule));
}

.card-date {
  flex: none;
  width: 44px;
  text-align: center;
  font-family: var(--serif-display);
}
.card-day { display: block; font-size: 1.7rem; font-weight: 700; line-height: 1.05; color: var(--ink); }
.card-mon { display: block; font-size: 0.78rem; color: var(--muted); font-family: var(--sans); margin-top: 2px; }

/* THE TEXT COLUMN HAS A FLOOR, AND THE SIDE HAS A CEILING.
   A reader's screenshot showed a title set one letter per line, running
   the whole height of the card while the rest of it sat empty (owner,
   2026-08-14). That shape has one cause whatever squeezed the column:
   `overflow-wrap: anywhere` lets a text box's MIN-CONTENT width fall to
   a single character, so a flex item carrying it can be shrunk to a
   sliver and still "fit". `break-word` breaks a word only when it
   genuinely does not fit and leaves min-content at the longest word, so
   the column can never collapse below a word - and the floor and
   ceiling below mean nothing in the row can take the space to begin
   with. Both belong here: one stops the collapse, the other stops it
   looking like a column of letters if anything ever does. */
.card-main { flex: 1 1 auto; min-width: 0; }
.card-title {
  font-family: var(--serif-body);
  font-size: 1.02rem;
  font-weight: 600;
  color: var(--ink);
  overflow-wrap: break-word;
}
.card-title.locked { color: var(--muted); font-weight: 400; }
.card-preview {
  font-size: 0.85rem;
  color: var(--muted);
  margin-top: 3px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.card-thumbs { display: flex; gap: 6px; margin-top: 8px; }
.card-thumbs img {
  width: 64px; height: 64px;
  object-fit: cover;
  border-radius: var(--r-sm);
  border: 1px solid var(--rule);
}

/* The side carries a thumb, a mood and a menu - fixed things. It may
   never grow past a third of the card whatever lands in it. */
.card-side { flex: 0 1 auto; max-width: 33%; display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
.card-mood { width: 24px; height: 24px; }
.card-menu {
  border: none; background: transparent; color: var(--muted);
  width: 32px; height: 32px; border-radius: var(--r-full);
  cursor: pointer; padding: 0; display: inline-flex; align-items: center; justify-content: center;
}
.card-menu svg { width: 18px; height: 18px; }

.chip-draft {
  display: inline-block;
  font-size: 0.66rem;
  font-weight: 600;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: var(--r-full);
  padding: 1px 8px;
  margin-bottom: 4px;
}

/* Empty timeline: artwork and a quote, not a sad grey box. */
.tl-empty {
  min-height: 55vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 24px;
}
/* THE JOURNAL'S LANDING PROMPT - a real card, already a button. */
.tl-prompt {
  display: block;
  width: 100%;
  text-align: start;
  margin: 18px 0 6px;
  padding: 18px 16px;
  cursor: pointer;
  font-family: var(--serif-body);
}
.tl-prompt-q { font-size: 1.05rem; line-height: 1.5; color: var(--ink); }

.tl-quote {
  font-family: var(--serif-display);
  font-size: 1.25rem;
  line-height: 1.5;
  color: var(--ink);
  max-width: 24rem;
}
.tl-quote-by { font-family: var(--sans); font-size: 0.8rem; color: var(--muted); margin-top: 10px; }
.tl-hint { font-family: var(--sans); font-size: 0.85rem; color: var(--muted); margin-top: 34px; }
.tl-shared { font-family: var(--sans); font-size: 0.82rem; color: var(--muted); margin: 10px 2px 0; }

.skel {
  border-radius: var(--r-md);
  border: 1px solid var(--rule);
  height: 74px;
  margin: 12px 0;
  position: relative;
  overflow: hidden;
  background: color-mix(in srgb, var(--rule) 30%, var(--paper));
}
.skel::after {
  content: '';
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--paper) 70%, transparent), transparent);
  animation: skel 1.2s infinite;
}
@keyframes skel { to { transform: translateX(100%); } }
html[data-motion='reduce'] .skel::after { animation: none; }

/* ---------------------------------------------------------------------
   The reader
   --------------------------------------------------------------------- */

.reader-head {
  position: relative;
  padding: calc(var(--safe-t) + 64px) 16px 18px;
  background-image: var(--hero-art);
  background-size: cover;
  background-position: center;
}
.reader-head::after {
  content: '';
  position: absolute;
  inset: auto 0 0 0;
  height: 70px;
  background: linear-gradient(to bottom, transparent, var(--paper));
}
.reader-date {
  position: relative;
  z-index: 1;
  font-family: var(--serif-display);
  font-size: 1.5rem;
  font-weight: 700;
  color: #fff;
  text-shadow: 0 1px 8px rgba(11, 42, 74, 0.6);
}
.reader-body {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 10px 16px 40px;
}
.reader-body h1 { margin-top: 0.4em; }

.sealed-line {
  font-family: var(--sans);
  font-size: 0.76rem;
  color: var(--muted);
  border-top: 1px solid var(--rule);
  margin-top: 34px;
  padding-top: 12px;
  display: flex;
  align-items: center;
  gap: 6px;
}
.sealed-line svg { width: 13px; height: 13px; flex: none; }

/* ---------------------------------------------------------------------
   Sheets and dialogs
   --------------------------------------------------------------------- */

.scrim2 {
  position: fixed;
  inset: 0;
  z-index: 80;
  background: rgba(11, 42, 74, 0.45);
  opacity: 0;
  transition: opacity var(--m-quick);
}
.scrim2.on { opacity: 1; }

.sheet {
  position: fixed;
  left: 50%;
  transform: translate(-50%, 105%);
  bottom: 0;
  z-index: 81;
  width: min(100%, 30rem);
  max-height: 86vh;
  overflow-y: auto;
  background: var(--surface);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  box-shadow: var(--shadow-float);
  padding: 8px 18px calc(var(--safe-b) + 18px);
  transition: transform var(--m-enter) var(--ease-enter);
  font-family: var(--sans);
}
.sheet.on { transform: translate(-50%, 0); }
.sheet-grab {
  width: 38px; height: 4px;
  border-radius: var(--r-full);
  background: var(--rule);
  margin: 6px auto 12px;
}
.sheet h3 {
  font-family: var(--serif-display);
  font-size: 1.1rem;
  margin: 0 0 10px;
  color: var(--ink);
}
.sheet-row {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  border: none;
  background: transparent;
  text-align: left;
  padding: 13px 2px;
  font-family: var(--sans);
  font-size: 0.95rem;
  color: var(--ink);
  cursor: pointer;
  border-bottom: 1px solid var(--rule);
}
.sheet-row:last-child { border-bottom: none; }
.sheet-row svg { width: 20px; height: 20px; color: var(--muted); flex: none; }
.sheet-row.danger, .sheet-row.danger svg { color: #c62828; }
.sheet-row:active { opacity: 0.6; }

html[data-motion='reduce'] .sheet { transition-duration: 1ms; }
html[data-motion='reduce'] .scrim2 { transition-duration: 1ms; }

/* ---------------------------------------------------------------------
   The drawer
   --------------------------------------------------------------------- */

.drawer {
  position: fixed;
  top: 0; bottom: 0; left: 0;
  z-index: 81;
  width: min(82%, 21rem);
  background: var(--surface);
  box-shadow: var(--shadow-float);
  transform: translateX(-104%);
  transition: transform var(--m-enter) var(--ease-enter);
  overflow-y: auto;
  font-family: var(--sans);
  padding-bottom: calc(var(--safe-b) + 12px);
}
.drawer.on { transform: translateX(0); }
html[data-motion='reduce'] .drawer { transition-duration: 1ms; }

.drawer-head {
  padding: calc(var(--safe-t) + 26px) 20px 22px;
  background-image: var(--hero-art);
  background-size: cover;
}
.drawer-head .wm { font-size: 1.2rem; }
.drawer-head .wm-itza { color: #cfe3fa; }

.drawer-group { padding: 8px 8px 2px; }
.drawer-group + .drawer-group { border-top: 1px solid var(--rule); }

.drawer-row {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  border: none;
  background: transparent;
  text-align: left;
  padding: 12px;
  border-radius: var(--r-md);
  font-family: var(--sans);
  font-size: 0.95rem;
  color: var(--ink);
  cursor: pointer;
}
.drawer-row svg { width: 21px; height: 21px; color: var(--muted); flex: none; }
.drawer-row:active { background: color-mix(in srgb, var(--rule) 40%, transparent); }

/* ---------------------------------------------------------------------
   The editor chrome (the machinery inside is editor.js and diary.css;
   this is only its new coat)
   --------------------------------------------------------------------- */

.pane[data-route='write'] { display: flex; flex-direction: column; overflow: hidden; }

.ed-top {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: calc(var(--safe-t) + 6px) 12px 6px;
  border-bottom: 1px solid var(--rule);
  background: var(--surface);
  font-family: var(--sans);
  flex: none;
}
.ed-top .edbar-state { display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0; justify-content: center; }

.ed-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 12px 16px calc(var(--kb) + var(--safe-b) + 96px);
}

/* AN EDGE-TO-EDGE BAR SITS ON THE EDGE.
 *
 * This used to be bottom: calc(var(--kb) + var(--safe-b)), which lifted the
 * whole bar 34px off the bottom of an iPhone and showed a strip of the
 * writing sliding underneath it. Measured on the device: --safe-b=34px,
 * gap=34px, exactly (owner, installed app, 2026-08-21).
 *
 * The floating .dock pill above is right to sit ABOVE the inset - it is a
 * pill with air around it. This is not a pill: it spans left:0 to right:0,
 * has a border-top and a frosted fill, and anything between it and the
 * bottom of the screen is a hole. So it sits flush and carries the home
 * indicator as PADDING, which keeps the icons clear of it without moving
 * the bar. Only the keyboard moves this. */
.ed-dock {
  position: absolute;
  left: 0; right: 0;
  bottom: var(--kb);
  z-index: 10;
  display: flex;
  flex-wrap: nowrap;              /* one row that scrolls, never wraps */
  align-items: center;
  gap: 4px;
  overflow-x: auto;
  scrollbar-width: none;
  /* NOTHING IS RESERVED FOR THE HOME INDICATOR. Owner's call, made twice
   * and against my suggestion of half the inset (2026-08-21): the band it
   * bought was wider than the labels it protected and read as dead space.
   * The bar still sits FLUSH - bottom: var(--kb) above - so nothing shows
   * through underneath it, which was the original bug and is separate from
   * this. The padding is simply symmetric now, top and bottom. */
  padding: 8px 10px;
  background: var(--frost);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  backdrop-filter: blur(18px) saturate(1.3);
  border-top: 1px solid var(--rule);
}
.ed-dock::-webkit-scrollbar { display: none; }
.ed-dock .btn { flex: none; }
/* Centred when the tools fit, scrollable from the left edge when they
   don't: auto-margin spacers collapse to zero under overflow, where a
   justify-content:center would clip the first tool unreachably. */
.ed-dock::before, .ed-dock::after { content: ''; margin: auto; }

/* The measured toolbar (06 sec 6.4): 20px icons at 44px pitch, each tool
   an icon over a small label; after three sessions the labels retire and
   the icons stand alone. */
.edtl {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-width: 44px;
  padding: 6px 8px;
  border: none;
  background: transparent;
  color: var(--ink);
  font-family: var(--sans);
  cursor: pointer;
  border-radius: var(--r-md);
}
.edtl:active { background: color-mix(in srgb, var(--ink) 9%, transparent); }
.edtl svg { flex: none; }
.edtl-l { font-size: 0.62rem; color: var(--muted); white-space: nowrap; }
.edtl[aria-pressed='true'] { color: var(--accent); }
.edtl[aria-pressed='true'] .edtl-l { color: var(--accent); }
.ed-dock.icons-only .edtl-l { display: none; }
.ed-dock.icons-only .edtl { padding: 10px 8px; }

.btn-save {
  border: none;
  border-radius: var(--r-full);
  background: var(--accent);
  color: var(--accent-ink);
  font-family: var(--sans);
  font-weight: 600;
  font-size: 0.9rem;
  padding: 10px 18px;
  cursor: pointer;
  flex: none;
}
.btn-save:disabled { opacity: 0.5; }
.btn-save:active { transform: scale(0.96); }

/* ---------------------------------------------------------------------
   Sign-in: the existing onboard design in diary.css stays the authority
   (its ob-* token system is complete and deliberate). The shell only has
   to let it scroll now that screens are fixed full-height panes.
   --------------------------------------------------------------------- */

#screen-onboard.on {
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-top: var(--safe-t);
  padding-bottom: var(--safe-b);
}

/* ---------------------------------------------------------------------
   Wider layouts: the dock walks to the left edge (02 sec 2.8)
   --------------------------------------------------------------------- */

@media (min-width: 1024px) {
  .dock {
    left: 24px;
    right: auto;
    transform: none;
    top: 50%;
    bottom: auto;
    translate: 0 -50%;
    flex-direction: column;
    height: auto;
    padding: 14px 8px;
  }
  .fab { transform: none; margin: 8px 0; }
  .fab:active { transform: scale(0.93); }
  .pane { padding-bottom: 24px; }
  .tl-body, .reader-body { max-width: 42rem; }
}

/* ---------------------------------------------------------------------
   Moods
   --------------------------------------------------------------------- */

.mood-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 8px;
  margin: 4px 0 10px;
}
.mood-cell {
  border: 2px solid transparent;
  background: transparent;
  border-radius: var(--r-md);
  padding: 8px 2px 6px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  cursor: pointer;
  font-family: var(--sans);
  font-size: 0.68rem;
  color: var(--muted);
}
.mood-cell.on { border-color: var(--accent); }
.mood-cell:active { transform: scale(0.94); }

.ed-mood {
  width: 44px;
  height: 44px;
  padding: 0;
  border: none;
  background: transparent;
  border-radius: var(--r-full);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}
.ed-mood-empty {
  width: 26px; height: 26px;
  border-radius: var(--r-full);
  border: 2px dashed var(--muted);
  opacity: 0.6;
}
.ed-mood:active { transform: scale(0.92); }

/* ---------------------------------------------------------------------
   The editor's date row
   --------------------------------------------------------------------- */

.ed-date {
  display: flex;
  align-items: baseline;
  gap: 8px;
  border: none;
  background: transparent;
  padding: 2px 0 8px;
  cursor: pointer;
  font-family: var(--sans);
  color: var(--muted);
}
.ed-date-day {
  font-family: var(--serif-display);
  font-size: 1.7rem;
  font-weight: 700;
  color: var(--ink);
  border-bottom: 3px solid var(--accent);
  line-height: 1.1;
}
.ed-date-rest { font-size: 0.9rem; }

/* ---------------------------------------------------------------------
   Calendar - the month grid, the dots, the day below
   --------------------------------------------------------------------- */

.cal-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 0 0;
}
.cal-month {
  min-width: 11rem;
  text-align: center;
  font-family: var(--serif-display);
  font-size: 1.2rem;
  font-weight: 700;
  color: var(--ink);
}

.cal-week {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  max-width: 26rem;
  margin: 8px auto 0;
  padding: 0 12px;
  text-align: center;
  font-family: var(--sans);
  font-size: 0.72rem;
  color: var(--muted);
}

.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  max-width: 26rem;
  margin: 2px auto 0;
  padding: 0 12px;
}
.cal-cell {
  position: relative;
  height: 46px;
  border: none;
  background: transparent;
  border-radius: var(--r-sm);
  font-family: var(--sans);
  font-size: 0.9rem;
  color: var(--ink);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 0;
}
.cal-cell.dim { color: var(--muted); opacity: 0.4; }
.cal-cell.today span:first-child {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-full);
  min-width: 1.35em;
}
.cal-cell.sel { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.cal-cell:active { transform: scale(0.94); }

.cal-dots { display: flex; gap: 2px; align-items: center; height: 5px; }
.cal-dots i { width: 5px; height: 5px; border-radius: 50%; display: inline-block; }
.cal-dots b { font-size: 0.55rem; color: var(--muted); line-height: 1; }

.cal-day { max-width: 26rem; margin: 14px auto 0; padding: 0 16px; font-family: var(--sans); }
.cal-day-title { font-family: var(--serif-display); font-size: 1.05rem; color: var(--ink); margin: 0 0 8px; }
.cal-day-empty { color: var(--muted); font-size: 0.9rem; margin: 0 0 10px; }
.cal-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  border: none;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  padding: 11px 12px;
  margin-bottom: 8px;
  cursor: pointer;
  font-family: var(--sans);
  color: var(--ink);
}
.cal-row:active { transform: scale(0.985); }
.cal-row-main { display: flex; flex-direction: column; min-width: 0; }
.cal-row-title { font-size: 0.95rem; overflow-wrap: anywhere; }
.cal-row-time { font-size: 0.74rem; color: var(--muted); }

/* The date sheet borrows the calendar grid. */
.dsheet-head { display: flex; align-items: center; justify-content: space-between; }
.dsheet-title { font-family: var(--serif-display); font-weight: 700; color: var(--ink); }
.sheet .cal-grid, .sheet .cal-week { max-width: none; padding: 0; }

/* ---------------------------------------------------------------------
   Search
   --------------------------------------------------------------------- */

.se-field {
  flex: 1;
  border: none;
  background: color-mix(in srgb, var(--rule) 35%, transparent);
  border-radius: var(--r-full);
  padding: 9px 16px;
  font-family: var(--sans);
  color: var(--ink);
  margin-right: 8px;
}
.se-body { max-width: 34rem; margin: 0 auto; padding: 14px 16px; font-family: var(--sans); }

/* A SENTENCE TO BE READ IS NOT METADATA.
   --muted is theme FLAVOUR: a theme sets it from its own palette and only
   --ink is defined by contrast (the same trap that made tab labels vanish
   into a dark purple theme, 2026-08-12). A label like "Empty seat" can
   live there - nobody needs to read it twice - but the paragraph that
   explains what a page is FOR must be legible on every theme, and on a
   dark one it was not (owner screenshot, 2026-08-14).
   Softened INK rather than muted: the mix is anchored to the two tokens
   that ARE contrast-defined, so it holds wherever the theme goes, while
   still reading as secondary to the headings around it. The plain --ink
   line before it is the fallback for engines without color-mix - dropped
   at parse time by those that have it. */
.se-note, .setNote, .acctNote, .sh-steps {
  color: var(--ink);
  color: color-mix(in oklab, var(--ink) 76%, var(--paper));
}
.se-note { font-size: 0.8rem; }
.se-label { color: var(--muted); font-size: 0.74rem; letter-spacing: 0.06em; text-transform: uppercase; margin: 18px 0 6px; }
.se-moods { display: flex; flex-wrap: wrap; gap: 6px; }
.se-mood { border: none; background: transparent; padding: 4px; border-radius: var(--r-full); cursor: pointer; }
.se-mood:active { transform: scale(0.9); }
.se-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  border: 1px solid var(--rule);
  background: transparent;
  color: var(--ink);
  border-radius: var(--r-full);
  padding: 6px 14px;
  font-family: var(--sans);
  font-size: 0.85rem;
  cursor: pointer;
}
.chip:active { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.chip.on { background: var(--ink); border-color: var(--ink); color: var(--paper); }

/* A SEGMENTED PAIR'S CHOICE MUST BE VISIBLE. The Default-section row
   toggled an .on class that no rule styled - two identical pills, zero
   feedback (owner screenshot, 2026-08-24). The active half is filled,
   exactly as an active chip is. */
.seg { display: inline-flex; gap: 6px; }
.seg .btn.on {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}
.se-none { color: var(--muted); }
.se-hit { display: flex; }
.se-date { font-size: 0.72rem; color: var(--muted); margin-top: 4px; }

/* ---------------------------------------------------------------------
   Timeline additions: the progress row, on-this-day
   --------------------------------------------------------------------- */

.tl-progress {
  font-family: var(--sans);
  font-size: 0.8rem;
  color: var(--muted);
  text-align: center;
  padding: 8px 0 0;
}

.otd {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  border: 1px solid var(--rule);
  background: color-mix(in srgb, var(--gold) 9%, var(--surface));
  border-radius: var(--r-md);
  padding: 11px 14px;
  margin: 12px 0 2px;
  cursor: pointer;
  font-family: var(--sans);
  font-size: 0.85rem;
  color: var(--ink);
}
.otd span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.otd:active { transform: scale(0.985); }

.reader-mood { position: absolute; right: 16px; bottom: 14px; z-index: 1; }

/* ---------------------------------------------------------------------
   Themes - the shelf, the miniatures, the veil
   --------------------------------------------------------------------- */

/* The tabs sit over the same centred column as the grid they filter. */
.thm-tabs { padding: 10px 16px 0; justify-content: center; }
.thm-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  max-width: 30rem;
  margin: 0 auto;
  padding: 14px 12px;
}
.thm-item { display: flex; flex-direction: column; gap: 6px; align-items: center; }
.thm-card {
  border: none;
  background: transparent;
  padding: 0;
  cursor: pointer;
  width: 100%;
}
.thm-card:active { transform: scale(0.96); }
.thm-mini {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 5px;
  width: 100%;
  aspect-ratio: 1 / 1.85;
  border-radius: 10px;
  border: 1px solid var(--rule);
  background-size: cover;
  background-position: center;
  padding: 8px;
  position: relative;
  overflow: hidden;
}
.thm-mini-plain { background: var(--paper); }
.thm-mini-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.thm-mini-img ~ .thm-mini-card { position: relative; }
.thm-mini-card {
  height: 16%;
  border-radius: 5px;
  background: color-mix(in srgb, #ffffff 82%, transparent);
  border: 1px solid rgba(11, 42, 74, 0.12);
}
.thm-mini-card.short { width: 72%; }
.thm-mini-dot {
  position: absolute;
  right: 8px;
  top: 8px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, 0.8);
}
.thm-apply {
  border: 1px solid var(--rule);
  background: transparent;
  color: var(--ink);
  border-radius: var(--r-full);
  padding: 5px 14px;
  font-family: var(--sans);
  font-size: 0.78rem;
  cursor: pointer;
}
.thm-apply.on {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: var(--accent);
}

.thm-veil {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: var(--paper);
  opacity: 0;
  transition: opacity 160ms ease-out;
  pointer-events: none;
}
.thm-veil.on { opacity: 1; }

/* ---------------------------------------------------------------------
   Templates
   --------------------------------------------------------------------- */

.tpl-list { max-width: 30rem; margin: 0 auto; padding: 12px 16px; font-family: var(--sans); }
.tpl-card {
  display: flex;
  align-items: center;
  gap: 12px;
  border-radius: var(--r-md);
  padding: 14px 16px;
  margin: 10px 0;
  color: #21344a;
}
.tpl-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.tpl-name { font-family: var(--serif-display); font-weight: 700; font-size: 1rem; }
.tpl-note { font-size: 0.8rem; opacity: 0.75; }
.tpl-use { flex: none; }
.tpl-dot { width: 14px; height: 14px; border-radius: 4px; display: inline-block; flex: none; }

/* The prompt line in the editor */
.ed-prompt {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  border: none;
  background: transparent;
  padding: 0 0 10px;
  cursor: pointer;
  font-family: var(--serif-body);
  font-style: italic;
  font-size: 0.95rem;
  color: var(--muted);
}
.ed-prompt-x {
  margin-left: auto;
  flex: none;
  width: 26px; height: 26px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-full);
  font-style: normal;
}

/* ---------------------------------------------------------------------
   First run
   --------------------------------------------------------------------- */

.fr {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: calc(var(--safe-t) + 30px) 26px calc(var(--safe-b) + 30px);
  max-width: 26rem;
  margin: 0 auto;
  font-family: var(--sans);
  position: relative;
}
.fr-art {
  width: 130px;
  height: 130px;
  border-radius: 50%;
  background-image: var(--hero-art);
  background-size: cover;
  box-shadow: var(--shadow-float);
  margin-bottom: 8px;
}
.fr-head {
  font-family: var(--serif-display);
  font-size: 1.6rem;
  line-height: 1.25;
  color: var(--ink);
  text-align: center;
  margin: 0;
}
.fr-sub { color: var(--muted); text-align: center; margin: 0 0 8px; }
.fr-skip {
  position: absolute;
  top: calc(var(--safe-t) + 12px);
  right: 14px;
  border: none;
  background: transparent;
  color: var(--muted);
  font-family: var(--sans);
  cursor: pointer;
  padding: 8px;
}
.fr-slide { min-height: 9rem; display: flex; flex-direction: column; justify-content: center; gap: 10px; }
.fr-dots { display: flex; gap: 8px; }
.fr-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  border: none;
  background: var(--rule);
  padding: 0;
  cursor: pointer;
}
.fr-dot.on { background: var(--accent); }
.fr-prog { display: flex; gap: 6px; position: absolute; top: calc(var(--safe-t) + 20px); }
.fr-prog span { width: 34px; height: 4px; border-radius: var(--r-full); background: var(--rule); }
.fr-prog span.on { background: var(--accent); }
.fr-q { font-size: 1.3rem; }
.fr-opts { display: flex; flex-direction: column; gap: 8px; width: 100%; }
.fr-opt {
  border: 1.5px solid var(--rule);
  background: var(--surface);
  color: var(--ink);
  border-radius: var(--r-md);
  padding: 13px 16px;
  font-family: var(--sans);
  font-size: 0.95rem;
  text-align: left;
  cursor: pointer;
}
.fr-opt.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--surface)); }
.fr-shelf {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  width: 100%;
  padding: 6px 2px 10px;
  scrollbar-width: none;
}
.fr-shelf::-webkit-scrollbar { display: none; }
/* The shelf's desktop arrows: swiping is not a thing a mouse does, so a
   hover-capable pointer gets paging buttons. Touch keeps its swipe and
   never sees them. */
.fr-shelfwrap { display: flex; align-items: center; gap: 4px; width: 100%; min-width: 0; }
.fr-shelfwrap .fr-shelf { flex: 1; min-width: 0; }
.fr-arrow {
  display: none;
  flex: none;
  width: 36px;
  height: 64px;
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: var(--surface);
  color: var(--ink);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
}
.fr-arrow:disabled { opacity: 0.3; cursor: default; }
@media (hover: hover) and (pointer: fine) {
  .fr-arrow { display: block; }
}
.fr-thm {
  flex: none;
  width: 118px;
  border: 2px solid transparent;
  border-radius: 12px;
  background: transparent;
  padding: 3px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: center;
  font-family: var(--sans);
  font-size: 0.78rem;
  color: var(--muted);
}
.fr-thm.on { border-color: var(--accent); color: var(--ink); }
.fr-row { display: flex; gap: 10px; }

/* ---------------------------------------------------------------------
   Mine
   --------------------------------------------------------------------- */

.mi-body { max-width: 30rem; margin: 0 auto; padding: 10px 16px; font-family: var(--sans); }

.mi-account {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 2px 14px;
}
.mi-avatar {
  width: 44px; height: 44px;
  /* The family shape: a square with small round corners, as in Chess
   * and Chat - 18% is Chess's own value. */
  border-radius: 18%;
  background: color-mix(in srgb, var(--accent) 14%, var(--surface));
  color: var(--accent);
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--rule);
}
.mi-who { display: flex; flex-direction: column; }
.mi-who b { color: var(--ink); }

.mi-streak {
  position: relative;
  border-radius: var(--r-lg);
  background-image: var(--hero-art);
  background-size: cover;
  background-position: center;
  min-height: 120px;
  padding: 20px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 2px;
  overflow: hidden;
  box-shadow: var(--shadow-card);
}
.mi-streak::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(10, 18, 30, 0.55), transparent 70%);
}
.mi-streak > * { position: relative; }
.mi-streak-n {
  font-family: var(--serif-display);
  font-size: 2.6rem;
  font-weight: 700;
  line-height: 1;
  color: #fff;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.4);
}
.mi-streak-w { color: #fff; font-size: 0.9rem; text-shadow: 0 1px 6px rgba(0, 0, 0, 0.4); }
.mi-streak-zero { font-family: var(--serif-display); font-size: 1.3rem; font-weight: 700; }
.mi-total { color: rgba(255, 255, 255, 0.85); font-size: 0.78rem; text-shadow: 0 1px 6px rgba(0, 0, 0, 0.4); }
.mi-streak .btn { align-self: flex-start; margin-top: 8px; }

.mi-nudge {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  background: var(--surface);
  padding: 12px 14px;
  margin-top: 12px;
  font-size: 0.9rem;
  color: var(--ink);
}
.mi-nudge span { flex: 1; min-width: 10rem; }

.mi-h {
  font-family: var(--serif-display);
  font-size: 1.05rem;
  color: var(--ink);
  margin: 24px 0 8px;
}

.mi-bars {
  display: flex;
  align-items: flex-end;
  gap: 4px;
  min-height: 110px;
  padding: 6px 0;
}
.mi-bar {
  flex: 1;
  border: none;
  background: transparent;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  cursor: pointer;
  padding: 0;
}
.mi-bar-fill { width: 70%; border-radius: 4px 4px 0 0; min-height: 2px; }
.mi-bar:active { transform: scale(0.95); }
.mi-none { color: var(--muted); font-size: 0.85rem; }

.mi-insights {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 6px 0;
}
.mi-ins-text { font-size: 0.85rem; color: var(--muted); line-height: 1.5; }
.mi-ins-text b { color: var(--ink); }

.mi-badges {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
}
.mi-badge {
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  background: var(--surface);
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.mi-badge-ring {
  width: 38px; height: 38px;
  border-radius: var(--r-full);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted);
  background:
    conic-gradient(var(--accent) calc(var(--p) * 1%), color-mix(in srgb, var(--rule) 60%, transparent) 0);
  -webkit-mask: radial-gradient(circle at center, transparent 54%, #000 56%);
  mask: radial-gradient(circle at center, transparent 54%, #000 56%);
  margin-bottom: 4px;
}
.mi-badge.done .mi-badge-ring {
  background: var(--gold);
  -webkit-mask: none;
  mask: none;
  color: #5a4404;
}
.mi-badge-name { font-weight: 600; font-size: 0.88rem; color: var(--ink); }
.mi-badge.done .mi-badge-name { color: var(--ink); }
.mi-badge-crit { font-size: 0.74rem; color: var(--muted); }

.mi-unlock { text-align: center; }
.mi-unlock svg { color: var(--gold); }

.mi-week { display: flex; gap: 8px; justify-content: space-between; padding-bottom: 20px; }
.mi-day {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  font-size: 0.72rem;
  color: var(--muted);
}
.mi-day b {
  width: 34px; height: 34px;
  border-radius: var(--r-full);
  border: 1.5px solid var(--rule);
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 600;
  color: var(--ink);
}
.mi-day.has b { background: color-mix(in srgb, var(--accent) 14%, transparent); border-color: var(--accent); }
.mi-day.today i { color: var(--accent); font-weight: 700; }

/* ---------------------------------------------------------------------
   The diary lock
   --------------------------------------------------------------------- */

.lockgate {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: var(--paper);
  background-image: var(--hero-art);
  background-size: cover;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.lock-card {
  background: var(--surface);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-float);
  padding: 26px 22px;
  max-width: 21rem;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  font-family: var(--sans);
  color: var(--ink);
}
.lock-card > svg { color: var(--gold); }
.lock-title { font-family: var(--serif-display); font-size: 1.15rem; font-weight: 700; margin: 0; }
.lock-dots { display: flex; gap: 10px; min-height: 14px; }
.lock-dots i {
  width: 12px; height: 12px;
  border-radius: 50%;
  border: 1.5px solid var(--muted);
  display: inline-block;
}
.lock-dots i.on { background: var(--accent); border-color: var(--accent); }
.lock-dots.err i { border-color: #c62828; }
.lock-pad {
  display: grid;
  grid-template-columns: repeat(3, 64px);
  gap: 10px;
  justify-content: center;
}
.lock-key {
  height: 54px;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  background: transparent;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 1.2rem;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.lock-key:active { transform: scale(0.94); background: color-mix(in srgb, var(--accent) 10%, transparent); }
.lock-note { font-size: 0.74rem; color: var(--muted); text-align: center; margin: 4px 0 0; }

/* ---------------------------------------------------------------------
   Shared with
   --------------------------------------------------------------------- */

.gu-body { max-width: 30rem; margin: 0 auto; padding: 12px 16px; font-family: var(--sans); }
.gu-alone {
  font-family: var(--serif-display);
  font-size: 1.05rem;
  color: var(--ink);
  margin: 14px 0 6px;
}

/* The shared-diaries page explains itself where it is empty: numbered
   because these things happen in an order, and roomy because this is the
   one screen a visitor reads rather than scans. */
/* THE FRAME FOLLOWS THE CLIP, NEVER THE OTHER WAY ROUND. A full-width
   box with object-fit: contain gives a portrait video two black pillars
   (owner screenshot, 2026-08-15). Left to size itself under a ceiling,
   the element IS the video: no bars, no black, and the paper shows
   around it. */
.recview {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 42vh;
  margin: 10px auto;
  border-radius: var(--r-md);
  background: transparent;
}
/* A DISPLAY RULE DEFEATS THE hidden ATTRIBUTE, which is how the dead
   viewfinder and a stopped timer stayed on screen after recording (same
   screenshot; the voice meter had shown itself before Start for the same
   reason since it was written). Both are put back under hidden's
   authority here, where the display rules are. */
.recview[hidden], .recmeter[hidden] { display: none; }
.recflip { display: flex; justify-content: center; margin: 0 0 8px; }
.recflip[hidden] { display: none; }

/* A RECORDING THAT HAS NOT BEEN FETCHED. Its own first frame with a play
   control over it - the entry opens at once and the megabytes wait to be
   asked for. */
/* BLOCK, NOT inline-block. As an inline-block it sat IN the line box, so
   the caption wrapped alongside it - a portrait clip left "My Layla"
   stranded up the right-hand side on its own (owner, Android, 2026-08-21).
   width:fit-content keeps the frame hugging the still, which is the rule
   the .recview comment above states: the frame follows the clip. */
.body-media-wait {
  position: relative;
  display: block;
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
}
.media-poster {
  display: block;
  max-width: 100%;
  max-height: 60vh;
  border-radius: var(--r-md);
}
.media-plate {
  display: block;
  width: min(24rem, 100%);
  height: 6rem;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--ink) 8%, transparent);
}
.media-play {
  position: absolute;
  left: 50%; top: 50%;
  translate: -50% -50%;
  width: 64px; height: 64px;
  border: none;
  border-radius: var(--r-full);
  background: rgba(20, 22, 28, 0.62);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.media-play:active { transform: scale(0.94); }
.media-play:disabled { opacity: 0.5; cursor: default; }
/* A triangle drawn in CSS: no icon file to fetch for a control whose
   whole purpose is fetching nothing yet. */
.media-play-glyph {
  width: 0; height: 0;
  margin-left: 4px;
  border-left: 20px solid #fff;
  border-top: 13px solid transparent;
  border-bottom: 13px solid transparent;
}
.body-media-wait.loading .media-play-glyph {
  width: 20px; height: 20px;
  margin: 0;
  border: 3px solid rgba(255, 255, 255, 0.35);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
html[data-motion='reduce'] .body-media-wait.loading .media-play-glyph { animation: none; }

/* A money column names its unit once, in its heading, over the figures
   it belongs to - so the rows below can be bare numbers instead of
   "1,000 iDiary" crushed together on a phone. */
.billhead { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
/* THE MONEY'S NAME IS SPELLED THE WAY IT IS SPELLED. The heading class
   uppercases its text, which turned iDiary into IDIARY - a name is not
   a label to restyle (owner, 2026-08-15). */
.billunit { letter-spacing: 0.04em; text-transform: none; }
/* The same idea on a single row: the unit belongs to the LABEL side,
   where it has room, not pressed against the number it describes. */
.rowunit {
  display: block;
  font-size: 0.72rem;
  color: var(--muted);
  font-family: var(--sans);
  letter-spacing: 0.04em;
}

/* The import chooser's doors: one per line, full width, in the order a
   writer is likeliest to want them. */
.dimp-doors { display: flex; flex-direction: column; gap: 10px; margin: 16px 0 18px; }

/* Colour comes from the prose rule above - these are sentences to read. */
.sh-steps {
  margin: 0;
  padding-left: 1.3em;
  font-family: var(--sans);
  font-size: 0.92rem;
  line-height: 1.55;
}
.sh-steps li { margin: 0 0 0.6em; }
.sh-steps li::marker { color: var(--accent); font-weight: 600; }
/* The username is a thing to read out and type, not body prose. */
.sh-name {
  display: block;
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 600;
  color: var(--ink);
  word-break: break-all;
}
.gu-row {
  display: flex;
  align-items: center;
  gap: 12px;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  background: var(--surface);
  padding: 12px 14px;
  margin: 10px 0;
}
.gu-row.empty { border-style: dashed; background: transparent; }
.gu-avatar {
  width: 38px; height: 38px;
  border-radius: 10px;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--sans);
  font-weight: 700;
  color: var(--paper);
  background: var(--muted);
  flex: none;
}
.gu-row.empty .gu-avatar { background: transparent; color: var(--muted); border: 1.5px dashed var(--rule); }
.gu-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.gu-main b { color: var(--ink); font-size: 0.95rem; }
.gu-sub { color: var(--muted); font-size: 0.76rem; }

/* The short-address card and the link row's button pair. The chosen name
   is INK, not muted - it is the one thing on this screen the person is
   here to read back. */
.gu-linkbtns { display: inline-flex; gap: 6px; flex: none; }
.gu-shorturl {
  color: var(--ink);
  font-size: 0.82rem;
  word-break: break-all;
  min-width: 0;
}
.gu-stubcard {
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  background: var(--surface);
  padding: 12px 14px;
  margin: 10px 0;
}
.gu-stubcard b { color: var(--ink); font-size: 0.95rem; }
.gu-stubrow { display: flex; gap: 8px; margin-top: 8px; }
.gu-stubrow input {
  flex: 1;
  min-width: 0;
  font-family: var(--sans);
  font-size: 0.9rem;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 6px;
  padding: 0.5rem 0.7rem;
}
.gu-row.pending { border-color: var(--gold, var(--rule)); }

/* THE TEXT-SIZE CALIBRATOR. The sample flexes and scrolls; the controls
   STAY PINNED at the bottom - arrows that drift apart as the text grows
   walk out of one-handed thumb reach, and the button under a finger must
   not move between taps. The direction glyphs are FIXED PX chrome: they
   encode smaller/bigger and must hold still while everything else moves. */
.tsc { display: flex; flex-direction: column; min-height: 100%; }
.tsc-head { margin: 0 0 0.8rem; }
.tsc-sample {
  flex: 1;
  overflow-y: auto;
  /* The scrim card: the sample is judged over whatever backdrop the boot
     theme brings, because that is where the real legibility risk lives. */
  background: color-mix(in srgb, var(--paper) 86%, transparent);
  border: 1px solid var(--rule);
  border-radius: 10px;
  padding: 1rem 1.1rem;
}
.tsc-sample p {
  font-family: var(--serif-body, serif);
  font-size: var(--body-size);
  font-weight: 400;
  line-height: 1.55;
  margin: 0;
}
.tsc-bar {
  position: sticky;
  bottom: 0;
  padding: 12px 0 calc(env(safe-area-inset-bottom, 0px) + 8px);
  background: var(--paper);
}
.tsc-steps { display: flex; gap: 10px; margin-bottom: 10px; }
.tsc-btn {
  flex: 1 1 0;
  min-height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: var(--surface, transparent);
  color: var(--ink);
  cursor: pointer;
}
.tsc-a { font-size: 14px; line-height: 1; }
.tsc-A { font-size: 24px; line-height: 1; font-weight: 600; }
.tsc-ch { display: inline-flex; }
.tsc-ch.up svg { transform: rotate(180deg); }
/* Dimmed at the end of the ramp, aria-disabled but still focusable. */
.tsc-btn.dim { opacity: 0.35; }
.sr-live {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

/* THE NATIVE DROPDOWN LIST IS THE OS'S, NOT THE THEME'S (owner screenshot,
   2026-08-13, Windows): a <select> popup paints on the OS's own list
   surface, but the OPTIONS inherit the control's themed ink - pale ink on
   a light OS list is invisible text. Options carry their own explicit
   pair, readable on either OS scheme, and never inherit. */
select option {
  color: #1b2030;
  background: #ffffff;
}

/* ---------------------------------------------------------------------
   Polish: the toast rides above the dock; the offline pill; drafts
   --------------------------------------------------------------------- */

#toast {
  bottom: calc(var(--bar-h) + var(--safe-b) + 28px);
  z-index: 90;
}

/* TAPPABLE, AND IT SAYS SO. This pill carried pointer-events: none
   from its read-only days, which made the v61 dismiss handler
   unreachable by any finger - while the synthetic .click() in the test
   dispatched straight past pointer-events and passed (owner report,
   2026-08-12: "how do you dismiss it??"). A tap test that must prove
   tappability checks the computed style, not just the handler. */
.offline-pill {
  position: fixed;
  top: calc(var(--safe-t) + 58px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--frost);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border: 1px solid var(--rule);
  border-radius: var(--r-full);
  color: var(--muted);
  font-family: var(--sans);
  font-size: 0.74rem;
  padding: 5px 8px 5px 14px;
  pointer-events: auto;
  cursor: pointer;
  text-align: start;
}
/* THE HIDDEN ATTRIBUTE MUST ALWAYS WIN - the dock's law, learned twice
   now. Giving this pill display:flex for the x layout silently beat
   the UA's [hidden] rule, so the pill stood on screen ONLINE, on every
   platform, and the x "did nothing" - it worked, invisibly (owner
   reports, 2026-08-12). Any element that pairs a display rule with the
   hidden attribute carries one of these. */
.offline-pill[hidden] { display: none !important; }

.offline-pill .offline-x {
  flex: none;
  width: 20px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: color-mix(in srgb, var(--ink, #333) 10%, transparent);
  font-size: 0.9rem;
  line-height: 1;
}

.tl-draft { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--surface)); }
.tl-draft svg { color: var(--accent); flex: none; }

/* ---------------------------------------------------------------------
   The optimistic save: the sealing card, and THE STAMP
   --------------------------------------------------------------------- */

.card.sealing { --edge: var(--gold); cursor: default; }
.card.sealing:active { transform: none; }
.seal-wait { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); }
.seal-wait svg { color: var(--gold); animation: sealPulse 1.4s ease-in-out infinite; }
@keyframes sealPulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.45; transform: scale(0.85); }
}
html[data-motion='reduce'] .seal-wait svg { animation: none; }

.card.save-failed { --edge: #c62828; border-color: color-mix(in srgb, #c62828 35%, var(--rule)); }

/* The stamp: pressed on once, when the chain confirms - the one place the
   animation budget is spent (02 sec 2.9). */
.seal-stamp {
  position: absolute;
  right: 14px;
  top: 50%;
  translate: 0 -50%;
  width: 44px;
  height: 44px;
  border-radius: var(--r-full);
  background: var(--gold);
  color: #5a4404;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--gold) 55%, transparent);
  animation: sealIn 420ms cubic-bezier(0.34, 1.4, 0.64, 1),
             sealRing 700ms ease-out 120ms,
             sealFade 600ms ease-in 1400ms forwards;
  pointer-events: none;
}
@keyframes sealIn {
  from { transform: scale(1.7); opacity: 0; }
  to   { transform: scale(1); opacity: 1; }
}
@keyframes sealRing {
  from { box-shadow: 0 0 0 0 color-mix(in srgb, var(--gold) 55%, transparent); }
  to   { box-shadow: 0 0 0 22px color-mix(in srgb, var(--gold) 0%, transparent); }
}
@keyframes sealFade { to { opacity: 0; } }
html[data-motion='reduce'] .seal-stamp { animation: sealFade 1ms 800ms forwards; }

/* The sealed thumbnail on a card */
.card-thumb {
  width: 52px;
  height: 52px;
  object-fit: cover;
  border-radius: var(--r-sm);
  border: 1px solid var(--rule);
}

/* Tags */
.rd-tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 2px 0 14px; font-family: var(--sans); }
.tag-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  border: none;
  background: transparent;
  border-bottom: 1px solid var(--rule);
  padding: 14px 4px;
  cursor: pointer;
  font-family: var(--sans);
}
.tag-row:last-child { border-bottom: none; }
.tag-name { color: var(--accent); font-weight: 600; font-size: 0.95rem; }
.tag-count { color: var(--muted); font-size: 0.8rem; }
.tag-row:active { opacity: 0.6; }

/* A picture on its way: the reader's first paint marks where attachments
   will land, in the same shimmer language as every other skeleton. */
.figskel {
  min-height: 200px;
  border-radius: var(--r-md);
  border: 1px solid var(--rule);
  background: color-mix(in srgb, var(--rule) 30%, var(--paper));
  position: relative;
  overflow: hidden;
}
.figskel.media { min-height: 54px; }
.figskel::after {
  content: '';
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--paper) 70%, transparent), transparent);
  animation: skel 1.2s infinite;
}
html[data-motion='reduce'] .figskel::after { animation: none; }

/* The entry-font shelf */
.font-row {
  display: flex;
  flex-direction: column;
  gap: 3px;
  width: 100%;
  text-align: left;
  border: none;
  background: transparent;
  border-bottom: 1px solid var(--rule);
  padding: 13px 4px;
  cursor: pointer;
  position: relative;
  color: var(--ink);
}
.font-row:last-child { border-bottom: none; }
.font-row.on::before {
  content: '';
  position: absolute;
  left: -12px; top: 10px; bottom: 10px;
  width: 3px;
  border-radius: var(--r-full);
  background: var(--accent);
}
.font-sample { font-size: 1.25rem; line-height: 1.3; }
.font-label { font-family: var(--sans); font-size: 0.76rem; color: var(--muted); }
.font-label i { font-style: normal; opacity: 0.8; }
.font-on {
  position: absolute;
  right: 4px; top: 12px;
  font-family: var(--sans);
  font-size: 0.68rem;
  color: var(--accent-ink);
  background: var(--accent);
  border-radius: var(--r-full);
  padding: 2px 9px;
}
.font-row:active { opacity: 0.65; }

/* The theme's companion: upper hero, scrolls away with the artwork.
   Only the creature itself takes a tap - the box around it does not. */
/* CENTRED, and NO CSS FILTER. Centred because the creature is the
   heart of the hero - pushed to a corner its glitter fell off-screen
   and its speech ran off the phone (owner, 2026-08-12). No filter
   because WebKit rasterizes a filtered element into its own layer and
   composites it back with a visible tonal rectangle over gradient
   backdrops - the "slight square around the butterfly" on iPhone. The
   artwork carries its own depth; nothing here may add a box effect. */
.hero-char {
  position: absolute;
  left: 50%;
  /* BELOW THE TITLE, ALWAYS. Centred at top 6% the creature sat on the
     app name on a notched phone (owner screenshot, 2026-08-12). The
     top bar is 54px tall under the safe area, so the creature starts
     under its bottom edge; its lower wings may tuck under the year
     chip, which sits a layer above and reads as depth. */
  top: calc(var(--safe-t, 0px) + 46px);
  transform: translateX(-50%);
  width: min(46vw, 190px);
  aspect-ratio: 1 / 1;
  pointer-events: none;
  /* ABOVE the card layer (z 2): the host's lower edge dips under the
     year row by design, and at z 1 the equalizer bars living there
     were painted but unreachable - elementFromPoint said so before a
     phone had to. The host itself takes no pointer events and the
     creature hit-tests on its PAINTED shape, so cards behind the
     transparent corners still get their taps. */
  z-index: 3;
}
.hero-char svg { width: 100%; height: 100%; }

/* The signature effect's canvas: screen space, riding the viewport.
   Inside the pane's stacking context ON PURPOSE - z-index 10 puts it
   above every card (the person chose a character theme; the effect is
   the point) and below the pane's sticky top bar (z 20) and the dock
   (a sibling above all panes), so dust never settles on the compose
   button - that reads as grime, not magic. */
.chr-sig {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 10;
}

/* The greeting bubble is DOM, never artwork: text drawn into an SVG is
   text in one language. It sits at the anchor the rig gives, in the
   rig's own coordinates, and it may overflow the character's box. */
.chr-speech {
  position: absolute;
  transform: translate(-6%, -100%);
  /* max-content, then clamped: an absolutely-positioned box would
     otherwise shrink-to-fit against the host's own width (~190px minus
     the anchor offset), wrapping a short line into a tall sliver. The
     old single-line rule kept short lines tidy and silently cut every
     long one off the phone screen. The engine's placeSpeech() pulls
     the box back inside the viewport; this rule only decides how wide
     it may dream. */
  width: max-content;
  max-width: min(64vw, 300px);
  white-space: normal;
  overflow-wrap: break-word;
  padding: 6px 11px;
  border-radius: 13px 13px 13px 3px;
  background: var(--paper, #fff);
  color: var(--ink, #1a1520);
  border: 1px solid var(--rule, rgba(0, 0, 0, 0.12));
  font-size: 13px;
  line-height: 1.25;
  box-shadow: 0 4px 14px rgba(13, 3, 26, 0.22);
  animation: chr-pop 220ms cubic-bezier(.34, 1.4, .64, 1) both;
  pointer-events: none;
  z-index: 2;
}
@keyframes chr-pop {
  from { opacity: 0; transform: translate(-6%, -92%) scale(0.86); }
  to   { opacity: 1; transform: translate(-6%, -100%) scale(1); }
}
[data-motion="reduce"] .chr-speech { animation: none; }
/* RTL: the bubble anchors to the upper-INNER corner of the character
   and its tail mirrors - a hardcoded upper-right in Arabic or Hebrew
   points away from the creature it belongs to. */
[dir='rtl'] .chr-speech {
  border-radius: 13px 13px 3px 13px;
  transform: translate(-94%, -100%);
  animation: none;
}

/* The audio bars: a small moving equalizer under the creature while
   its sound is playing - or WOULD be playing, when sound is off, which
   is the invitation to turn it on (owner, 2026-08-12). Bare bars, no
   pill around them - the owner asked for exactly that. */
.chr-eq {
  position: absolute;
  left: 50%;
  bottom: 1%;
  transform: translateX(-50%);
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 16px;
  /* A real button: tapping the bars opens Settings on the sound row.
     The padding buys a finger-sized target around four thin bars -
     44pt-class, because a thumb that misses reads as a dead control
     (owner report, 2026-08-13). touch-action keeps the tap a tap. */
  pointer-events: auto;
  cursor: pointer;
  touch-action: manipulation;
  background: transparent;
  border: 0;
  padding: 12px 18px;
  box-sizing: content-box;
  z-index: 2;
  animation: chr-eq-in 160ms ease-out both;
  /* No filter here either - the same WebKit layer-square that haunted
     the character box would ring these bars. Accent on paper carries. */
}
.chr-eq:active { transform: translateX(-50%) scale(0.9); }
.chr-eq span {
  width: 3.5px;
  height: 100%;
  border-radius: 2px;
  background: var(--accent, #7a5cc5);
  transform-origin: bottom;
  animation: chr-eq-bounce 620ms ease-in-out infinite;
}
.chr-eq span:nth-child(2) { animation-delay: 150ms; }
.chr-eq span:nth-child(3) { animation-delay: 300ms; }
.chr-eq span:nth-child(4) { animation-delay: 90ms; }
@keyframes chr-eq-bounce {
  0%, 100% { transform: scaleY(0.28); }
  50%      { transform: scaleY(1); }
}
@keyframes chr-eq-in {
  from { opacity: 0; transform: translateX(-50%) scale(0.8); }
  to   { opacity: 1; transform: translateX(-50%) scale(1); }
}
/* Reduced motion: the chip still says "sound is playing" - it just
   says it standing still. */
[data-motion="reduce"] .chr-eq { animation: none; }
[data-motion="reduce"] .chr-eq span { animation: none; transform: scaleY(0.6); }

/* ---------------------------------------------------------------------
   The language screen - one pre-answered question, before everything
   --------------------------------------------------------------------- */
.langcard {
  max-width: 30rem;
  margin: 0 auto;
  padding: calc(var(--safe-t) + 28px) 20px calc(var(--safe-b) + 24px);
  display: flex;
  flex-direction: column;
  gap: 14px;
  height: 100%;
  box-sizing: border-box;
}
.langcard h1 { font-size: 1.5rem; margin: 0 0 4px; }
.langlist {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  border: 1px solid var(--rule);
  border-radius: 12px;
  background: var(--surface);
}
.langrow {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 11px 14px;
  border: 0;
  border-bottom: 1px solid var(--rule);
  background: transparent;
  color: var(--ink);
  font-size: 1rem;
  text-align: start;
  cursor: pointer;
}
.langrow:last-child { border-bottom: 0; }
.langrow.on {
  background: color-mix(in srgb, var(--accent, #0b2a4a) 12%, transparent);
  box-shadow: inset 3px 0 0 var(--accent, #0b2a4a);
}
.langflag { flex: none; width: 24px; display: inline-flex; }
.langflag svg { width: 24px; height: 16.5px; border-radius: 2px; display: block; }
.langname { flex: 1; }
.langen { color: var(--muted); font-size: 0.82rem; }

/* ---------------------------------------------------------------------
   The swipe rail - pin / share / delete behind a dragged card
   --------------------------------------------------------------------- */

.card { overflow: hidden; }
.card > .card-date, .card > .card-main, .card > .card-side {
  transform: translate3d(var(--drag, 0px), 0, 0);
  transition: transform 0.22s ease;
}
.card.dragging > .card-date, .card.dragging > .card-main, .card.dragging > .card-side {
  transition: none;
}
.card-rail {
  position: absolute;
  right: 0; top: 0; bottom: 0;
  width: 168px;
  display: flex;
  align-items: stretch;
  transform: translate3d(100%, 0, 0);
  transition: transform 0.22s ease;
}
.card.rail-open .card-rail { transform: translate3d(0, 0, 0); }
.rail-btn {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  border: none;
  cursor: pointer;
  font-family: var(--sans);
  background: color-mix(in srgb, var(--ink) 8%, var(--paper));
  color: var(--ink);
}
.rail-btn i { font-style: normal; font-size: 0.62rem; }
.rail-btn.danger { background: #b3261e; color: #fff; }

/* Multi-select */
.card-pick {
  align-self: center;
  width: 22px; height: 22px;
  margin-right: 4px;
  flex: 0 0 auto;
  border: 2px solid var(--muted);
  border-radius: var(--r-full);
}
.card-pick.on { background: var(--accent); border-color: var(--accent); }
.tl-selbar {
  position: absolute;
  left: 12px; right: 12px;
  bottom: calc(var(--safe-b) + 14px);
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-radius: var(--r-lg);
  background: var(--paper);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.25);
  font-family: var(--sans);
  font-size: 0.85rem;
}
.tl-selbar span { flex: 1; }
.btn.danger { background: #b3261e; color: #fff; border-color: transparent; }

/* Pull to refresh */
.tl-ptr {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  height: 0;
  overflow: hidden;
  padding-bottom: 4px;
  opacity: 0.6;
  transition: none;
}
.tl-ptr.ready { opacity: 1; }
.tl-ptr.busy { height: 48px !important; }

/* Month headers + pins */
.tl-month {
  font-family: var(--sans);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 14px 4px 2px;
}
.tl-pins-h { display: flex; align-items: center; gap: 6px; }
.card-pin {
  display: inline-block;
  vertical-align: baseline;
  margin-right: 4px;
  color: var(--accent);
}

/* ---------------------------------------------------------------------
   Checklists - on the page, and the quiet save bar
   --------------------------------------------------------------------- */

.body-check {
  display: inline-flex;
  align-items: baseline;
  gap: 0.55em;
  width: 100%;
  cursor: pointer;
}
.body-check input {
  width: 1.05em; height: 1.05em;
  accent-color: var(--accent);
  flex: 0 0 auto;
  transform: translateY(0.12em);
}
.body-check.done span { opacity: 0.55; text-decoration: line-through; }
.body-check + br { display: none; }
.rd-checksave {
  position: sticky;
  bottom: calc(var(--safe-b) + 12px);
  z-index: 20;
  margin: 10px 16px 0;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-radius: var(--r-lg);
  background: var(--paper);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.22);
  font-family: var(--sans);
  font-size: 0.85rem;
}
.rd-checksave span { flex: 1; }

/* ---------------------------------------------------------------------
   Editor: the paper tool, the time field, the templates nudge
   --------------------------------------------------------------------- */

.dsheet-time {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-family: var(--sans);
  font-size: 0.85rem;
  color: var(--muted);
  padding: 12px 4px 2px;
  border-top: 1px solid var(--rule);
  margin-top: 10px;
}
.dsheet-time input {
  font-family: var(--sans);
  border: 1px solid var(--rule);
  border-radius: var(--r-sm, 8px);
  padding: 6px 8px;
  background: transparent;
  color: var(--ink);
}
.ed-nudge {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 14px 6px;
  padding: 9px 12px;
  border: 1px dashed var(--rule);
  border-radius: var(--r-lg);
  background: transparent;
  color: var(--muted);
  font-family: var(--sans);
  font-size: 0.8rem;
  cursor: pointer;
  text-align: left;
}
.ed-nudge span:first-child { flex: 1; }

/* Mood style shelf */
.moodstyle-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
  text-align: left;
  border: none;
  background: transparent;
  border-bottom: 1px solid var(--rule);
  padding: 13px 4px;
  cursor: pointer;
  position: relative;
  color: var(--ink);
}
.moodstyle-row:last-child { border-bottom: none; }
.moodstyle-row.on::before {
  content: '';
  position: absolute;
  left: -12px; top: 10px; bottom: 10px;
  width: 3px;
  border-radius: var(--r-full);
  background: var(--accent);
}
.moodstyle-set { display: flex; gap: 4px; flex-wrap: wrap; }
.moodstyle-row .font-on { top: 12px; }

/* ---------------------------------------------------------------------
   Calendar: photos, and the month/year wheel
   --------------------------------------------------------------------- */

.cal-month {
  border: none;
  background: transparent;
  font: inherit;
  color: inherit;
  cursor: pointer;
  padding: 4px 10px;
  border-radius: var(--r-full);
}
.cal-month:active { background: color-mix(in srgb, var(--ink) 8%, transparent); }
.cal-grid.photos .cal-cell.photo {
  background-size: cover;
  background-position: center;
  color: #fff;
}
.cal-grid.photos .cal-cell.photo > span {
  background: rgba(0, 0, 0, 0.45);
  border-radius: var(--r-full);
  padding: 0 5px;
}
.cal-wheel-years {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 6px 0 12px;
}
.cal-wheel-months {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}
.cal-wheel-m {
  font-family: var(--sans);
  font-size: 0.85rem;
  padding: 10px 0;
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}
.cal-wheel-m.on { background: var(--accent); color: var(--accent-ink); border-color: transparent; }

/* ---------------------------------------------------------------------
   The scene: canvas, the light model, the material model
   --------------------------------------------------------------------- */

/* FULL-BLEED THEME ART: the painting is the page. Taller than the pane
   so the parallax has slack; top-anchored cover so the focal zone stays
   put and the quiet zone carries the cards. */
.art-bleed {
  /* FIXED, NOT ABSOLUTE + TRANSLATED. This layer used to be an absolute
   * child that the scroll handler moved down by scrollTop to stay
   * pinned - but a transformed child EXTENDS the pane's scrollable
   * height, so every scroll made room for more scroll: the timeline
   * scrolled forever into blank paint (owner overlay, 2026-08-22:
   * scrollTop 1614 + 1118 art - 241 drift = scrollH 2491, exactly).
   * Fixed is pinned by definition and adds nothing to scroll height;
   * the handler now applies only the parallax drift. */
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 130vh;
  z-index: 0;
  background-image: var(--hero-art);
  background-size: cover;
  background-position: top center;
  pointer-events: none;
  will-change: transform;
}
/* The themes SHELF shows the applied painting too: picking a theme and
   seeing only a colour change reads as the theme not working - the
   artwork was "off screen" until you went back (owner report,
   2026-08-12). Fixed to the viewport so it stands while the grid
   scrolls, dimmed under the tiles so the shelf stays legible. */
.thm-bleed {
  position: fixed;
  inset: 0;
  z-index: 0;
  background-image: var(--hero-art);
  background-size: cover;
  background-position: top center;
  opacity: 0.45;
  pointer-events: none;
}
.pane[data-route='themes'] .thm-tabs,
.pane[data-route='themes'] .thm-grid { position: relative; z-index: 1; }
/* THE BAR KEEPS ITS STICK. Lifting it above the bleed by making it
   `relative` also un-stuck it, so the one screen with no back arrow was
   also the one whose heading scrolled away - on a long shelf you ended up
   nowhere, with nothing on screen to press (owner report, 2026-08-14).
   Sticky carries its own z-index; it never needed to stop sticking. */
.pane[data-route='themes'] .tbar { z-index: 21; }

/* Under full bleed the hero element becomes pure layout - the art layer
   paints the picture, and the fade-to-ground would smear a gradient
   across a painting composed not to need one. */
html[data-bleed='1'] .pane > .hero { background-image: none; }
html[data-bleed='1'] .pane > .hero::after { display: none; }

/* The field's canvas sits between the hero artwork and the card stack -
   motion passes behind the words, never across them. */
.scene-canvas {
  position: absolute;
  top: 0; left: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
  pointer-events: none;
  will-change: transform;
}

/* THE LIGHT MODEL (08 sec 8.3): one number per card, everything else in
   CSS. oklab, because sRGB mixing between warm and cool goes grey through
   the middle and looks like a bug. */
html[data-light='1'] .card {
  background: color-mix(in oklab,
    var(--card-dark) calc((1 - var(--lit, 0)) * 100%),
    var(--card-lit) calc(var(--lit, 0) * 100%));
  border-color: color-mix(in srgb,
    var(--edge-dark) calc((1 - var(--lit, 0)) * 100%),
    var(--edge-lit) calc(var(--lit, 0) * 100%));
  transition: background 0.5s linear, border-color 0.5s linear;
}
/* The specular: a warm line along the top edge, present only in the
   light. ::before is the mood's stained edge; this is ::after. */
html[data-light='1'] .card::after {
  content: '';
  position: absolute;
  left: 6%; top: 0;
  width: 55%;
  height: 1.6px;
  border-radius: var(--r-full);
  background: linear-gradient(to right, var(--edge-lit), transparent);
  opacity: calc(var(--lit, 0) * 0.75);
  pointer-events: none;
}
/* The seal moment, lit: the new card's specular sweeps once. */
@keyframes sealSweep {
  from { transform: translateX(-90%); opacity: 0.95; }
  to { transform: translateX(200%); opacity: 0; }
}
html[data-light='1'] .card.seal-sweep::after {
  animation: sealSweep 700ms ease-out;
}
html[data-motion='reduce'] .card.seal-sweep::after { animation: none; }

/* THE MATERIAL MODEL (08 sec 8.12): light themes take grain, contact
   shadow and overlap - a glow washes out on paper. THE GRAIN COMPOSES
   with the person's chosen background pattern - it must never replace
   it: this rule once painted the texture ALONE, and any material theme
   silently hid the chosen background on every pane (owner report,
   2026-08-13, "shown nowhere"). Layer order: the pattern above, the
   grain beneath. */
html[data-material='1'] .pane {
  background-image: var(--bg-pattern, none), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cg fill='%23705c3c' opacity='0.03'%3E%3Ccircle cx='23' cy='31' r='1'/%3E%3Ccircle cx='87' cy='12' r='0.8'/%3E%3Ccircle cx='151' cy='47' r='1.1'/%3E%3Ccircle cx='54' cy='84' r='0.7'/%3E%3Ccircle cx='121' cy='99' r='1'/%3E%3Ccircle cx='11' cy='133' r='0.9'/%3E%3Ccircle cx='95' cy='158' r='1.1'/%3E%3Ccircle cx='163' cy='140' r='0.8'/%3E%3Ccircle cx='38' cy='170' r='1'/%3E%3Ccircle cx='140' cy='9' r='0.7'/%3E%3C/g%3E%3C/svg%3E");
  background-size: 140px 140px, 180px 180px;
}
html[data-material='1'] .card {
  box-shadow: 0 1px 2px rgba(72, 56, 28, 0.1), 0 10px 22px -10px rgba(72, 56, 28, 0.2);
  border-color: rgba(96, 76, 44, 0.14);
}
html[data-material='1'] .card::after { content: none; }

/* ---------------------------------------------------------------------
   Editor expression: the keyboard-height panels, stickers, the mood
   popover, the format rows
   --------------------------------------------------------------------- */

/* Same rule as .ed-dock, same reason: edge to edge, so it sits on the edge
 * and carries the inset as padding rather than floating above it. */
.ed-panel {
  position: absolute;
  left: 0; right: 0;
  bottom: 0;
  height: var(--panel-h, 280px);
  padding-bottom: var(--safe-b);
  z-index: 9;
  display: flex;
  flex-direction: column;
  background: var(--frost);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  backdrop-filter: blur(18px) saturate(1.3);
  border-top: 1px solid var(--rule);
  overflow: hidden;
}
/* Riding on top of the panel, not on the screen edge - so the home
 * indicator is the panel's problem, not the dock's. */
.ed-panel-open .ed-dock {
  bottom: calc(var(--panel-h, 280px) + var(--safe-b));
  padding-bottom: 8px;
}
.ed-panel-open .ed-scroll { padding-bottom: calc(var(--panel-h, 280px) + 150px); }

.stk-tabs {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 8px 10px 4px;
  overflow-x: auto;
  scrollbar-width: none;
  flex: none;
}
.stk-tabs::-webkit-scrollbar { display: none; }
.stk-tab {
  flex: none;
  width: 40px; height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}
.stk-tab img { width: 26px; height: 26px; }
.stk-tab.on { background: color-mix(in srgb, var(--ink) 10%, transparent); }
.stk-tab.stk-close { margin-left: auto; }
.stk-grid {
  flex: 1;
  overflow-y: auto;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 6px;
  padding: 6px 12px calc(var(--safe-b) + 10px);
  align-content: start;
}
.stk-cell {
  border: none;
  background: transparent;
  border-radius: var(--r-md);
  padding: 6px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.stk-cell img { width: 56px; height: 56px; }
.stk-cell:active { background: color-mix(in srgb, var(--ink) 10%, transparent); }

/* The pack's name under the panel's grid - eleven tabs of tiny artwork
   need a word to say where you are. */
.stk-foot {
  flex: none;
  text-align: center;
  font-family: var(--sans);
  font-size: 0.72rem;
  color: var(--muted);
  padding: 2px 0 calc(var(--safe-b) + 6px);
}

/* The packs screen: a gallery of everything, in the panel's order. */
.stk-pack { padding: 6px 0 14px; border-bottom: 1px solid var(--rule); }
.stk-pack:last-child { border-bottom: none; }
.stk-pack-name {
  font-family: var(--sans);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 14px 0 8px;
}
.stk-pack-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(52px, 1fr));
  gap: 8px;
}
.stk-pack-grid img { width: 100%; height: auto; aspect-ratio: 1 / 1; }

/* Stickers on the page: modest inline blocks, not full-width photos. */
.ed-embed.ed-sticker { width: 84px; padding: 4px; }
.ed-embed.ed-sticker img { width: 72px; height: 72px; }
.body-sticker { margin: 0.35em 0; }
.body-sticker img { width: 96px; height: 96px; }

.body-h1 { font-size: 1.55em; line-height: 1.25; margin: 0.6em 0 0.3em; }

/* The mood popover - two rows of five hanging off the header glyph. */
.mood-pop {
  position: fixed;
  z-index: 95;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-float, 0 10px 34px rgba(0, 0, 0, 0.25));
  padding: 12px 14px 8px;
  max-width: min(320px, calc(100vw - 16px));
}
.mood-pop h3 { font-family: var(--sans); font-size: 0.82rem; margin: 0 0 8px; color: var(--muted); }
.mood-pop-tail {
  position: absolute;
  top: -7px;
  width: 14px; height: 14px;
  background: var(--paper);
  border-left: 1px solid var(--rule);
  border-top: 1px solid var(--rule);
  transform: rotate(45deg);
}
.mood-grid-pop { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; }
.mood-grid-pop .mood-cell { padding: 4px; border-radius: var(--r-md); }
.mood-pop .mlink { margin-top: 6px; }

/* The format panel rows */
.fmt-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px 2px;
  font-family: var(--sans);
  font-size: 0.82rem;
  color: var(--muted);
  flex: none;
}
.fmt-body {
  flex: 1;
  overflow-y: auto;
  padding-bottom: calc(var(--safe-b) + 10px);
}
.fmt-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  flex-wrap: wrap;
}
.fmt-label { font-family: var(--sans); font-size: 0.75rem; color: var(--muted); width: 58px; flex: none; }
.fmt-swatches { display: flex; gap: 8px; flex-wrap: wrap; }

/* The face shelf inside the panel: full rows, each face reading a line
   of the writer's own words. Same rows Settings shows - one shelf. */
.fmt-faces { padding: 4px 14px 8px; }
.fmt-label-block {
  display: block;
  width: auto;
  margin-bottom: 2px;
}
.fmt-faces .font-row { padding: 10px 4px; }
.fmt-faces .font-sample { font-size: 1.05rem; }

/* The account picture in settings */
.profTop { display: flex; align-items: center; gap: 12px; }
.profAv {
  display: inline-block;
  width: 56px; height: 56px;
  flex: 0 0 auto;
  border-radius: 18%;
  background: var(--surface);
  border: 1px solid var(--rule);
}
.avBtns { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }

/* The one-time attention pulse - the install nudge's "Show me" landing.
   Three beats, then still. */
@keyframes pulseAttn {
  0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent); }
  50% { transform: scale(1.06); box-shadow: 0 0 0 9px color-mix(in srgb, var(--accent) 0%, transparent); }
}
.pulse-attn { animation: pulseAttn 900ms ease-in-out 3; }
html[data-motion='reduce'] .pulse-attn { animation: none; outline: 2px solid var(--accent); outline-offset: 2px; }

/* The draft chip's discard x */
.tl-draft { position: relative; padding-right: 44px; }
.tl-draft-x {
  position: absolute;
  right: 8px; top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px; height: 32px;
  border-radius: var(--r-full);
  color: var(--muted);
}
.tl-draft-x:active { background: color-mix(in srgb, var(--ink) 10%, transparent); }

/* The undo toast's one action */
.toast-act {
  margin-left: 10px;
  border: none;
  cursor: pointer;
  font-family: var(--sans);
  font-size: 0.8rem;
  font-weight: 700;
  padding: 4px 14px;
  border-radius: var(--r-full);
  background: var(--gold, #d9a441);
  color: #3a2b04;
}

/* ---------------------------------------------------------------------
   Gratitude - the showcase template (doc 10). Three lights lit at the
   end of a day; the metaphor implies the dark around them, so restraint
   is the entire brief. CSS and inline markup only - no image files.
   --------------------------------------------------------------------- */

.grat {
  max-width: 68ch;
  margin: 0 auto;
  padding: 8px 0 4px;
  /* The paper: warmer and slightly brighter toward the bottom, as if lit
     from below by lamplight. A tint over the surface, never an image. */
  background: linear-gradient(to top,
    color-mix(in oklab, var(--gold, #d9a441) 6%, transparent), transparent 55%);
  border-radius: var(--r-md);
}
.grat-prompt {
  font-family: var(--serif-body);
  font-style: italic;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--muted);
  margin: 0 0 22px;
}
.grat-item {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  margin-bottom: 28px;
}
.grat-nwrap {
  position: relative;
  flex: 0 0 44px;
  text-align: right;
}
/* The numerals are the signature: thin drop figures in the margin, the
   way a printed book sets a chapter opener. Never circles, never bullets,
   never bold. Unlit at 22%; lighting is a 600ms ease-out. */
.grat-n {
  font-family: var(--serif-display);
  font-weight: 300;
  font-size: 2.6rem;
  line-height: 1;
  color: var(--gold, #d9a441);
  opacity: 0.22;
  transition: opacity 600ms ease-out;
  position: relative;
  z-index: 1;
}
.grat-n.lit { opacity: 1; }
/* The bloom: a soft radial swell behind a lighting numeral - dark themes
   only, where light in darkness is the metaphor. */
.grat-bloom {
  position: absolute;
  right: -10px; top: 50%;
  width: 64px; height: 64px;
  margin-top: -32px;
  border-radius: var(--r-full);
  background: radial-gradient(circle, var(--gold, #d9a441), transparent 70%);
  opacity: 0;
  transform: scale(0.6);
  pointer-events: none;
}
html[data-theme='dark'] .grat-item.blooming .grat-bloom {
  animation: gratBloom 700ms ease-out forwards;
}
@keyframes gratBloom {
  0% { opacity: 0; transform: scale(0.6); }
  55% { opacity: 0.5; transform: scale(1); }
  100% { opacity: 0.18; transform: scale(1); }
}
html[data-theme='dark'] .grat-item.breathe .grat-bloom {
  animation: gratBreathe 400ms ease-in-out;
}
@keyframes gratBreathe {
  0% { opacity: 0.18; }
  50% { opacity: 0.28; }
  100% { opacity: 0.18; }
}
/* THE INVERSION (10.7): on light themes, light-on-light is a smudge, so
   the metaphor becomes ink pressed into paper - a warm ink tone and a
   faint letterpress impression instead of a glow. Same structure, same
   timing, opposite physics. */
html[data-theme='paper'] .grat-n, html[data-theme='hc'] .grat-n {
  color: color-mix(in oklab, var(--gold, #b8860b) 55%, var(--ink) 45%);
}
html[data-theme='paper'] .grat-item.blooming .grat-n,
html[data-theme='hc'] .grat-item.blooming .grat-n {
  animation: gratPress 600ms ease-out;
}
@keyframes gratPress {
  0% { transform: scale(1.02); text-shadow: none; }
  100% { transform: scale(1); text-shadow: 0 1px 0 rgba(0, 0, 0, 0.08) inset; }
}
.grat-text { flex: 1; min-width: 0; padding-top: 10px; }
.grat-thing {
  font-family: var(--serif-body);
  font-size: 1.12rem;
  line-height: 1.5;
  color: var(--ink);
  margin: 0;
}
.grat-why {
  font-family: var(--sans);
  font-size: 0.88rem;
  line-height: 1.6;
  color: var(--muted);
  margin: 8px 0 0;
}
.grat-dedwrap {
  text-align: center;
  margin-top: 48px;
  padding-bottom: 12px;
}
.grat-rule {
  display: inline-block;
  width: 96px;
  height: 1px;
  background: var(--rule);
  margin-bottom: 16px;
}
.grat-ded {
  font-family: var(--serif-body);
  font-style: italic;
  font-size: 0.95rem;
  color: var(--ink);
  margin: 0;
}
/* Reduced motion: a 200ms opacity change and no bloom at all. */
html[data-motion='reduce'] .grat-n { transition: opacity 200ms linear; }
html[data-motion='reduce'] .grat-bloom { display: none; }
html[data-motion='reduce'] .grat-item.blooming .grat-n { animation: none; }

/* The Gratitude card in the Templates list: a demo, not a picture.
   Three points of light rising left to right; pressing brightens them in
   the template's own rhythm. */
.tpl-grat {
  position: relative;
  background: linear-gradient(160deg, #2a2138, #16121f 70%);
  overflow: hidden;
}
.tpl-grat .tpl-name, .tpl-grat .tpl-note { color: #f0e9dc; }
.tpl-grat .tpl-note { opacity: 0.7; }
.tpl-lights { position: absolute; inset: 0; pointer-events: none; }
.tpl-lights i {
  position: absolute;
  width: 22px; height: 22px;
  border-radius: var(--r-full);
  background: radial-gradient(circle, #ffd98f, transparent 70%);
  opacity: 0.3;
  transition: opacity 300ms ease-out;
}
.tpl-grat:active .tpl-lights i:nth-child(1) { opacity: 0.95; transition-delay: 0ms; }
.tpl-grat:active .tpl-lights i:nth-child(2) { opacity: 0.95; transition-delay: 100ms; }
.tpl-grat:active .tpl-lights i:nth-child(3) { opacity: 0.95; transition-delay: 200ms; }

/* The written-it-down step at account creation */
.aw-wrote {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-family: var(--sans);
  font-size: 0.85rem;
  line-height: 1.5;
  margin: 14px 2px;
  cursor: pointer;
}
.aw-wrote input { width: 1.1em; height: 1.1em; margin-top: 0.15em; accent-color: var(--gold, #d9a441); }

/* ---------------------------------------------------------------------
   iOS Safari: the floating-app fix - MOVED HERE from diary.css so it stays
   the LAST font-size rule in the whole cascade. Safari zooms the page when
   a focused field's font is under 16px and the zoom sticks after blur; the
   fix is the field, not the viewport, so pinch-zoom stays available to a
   low-vision reader. Scoped to iOS engines only. DO NOT add font-size
   rules below this block, in this file or any stylesheet loaded after it.
   --------------------------------------------------------------------- */
@supports (-webkit-touch-callout: none) {
  input[type="text"],
  input[type="password"],
  input[type="email"],
  input[type="number"],
  input[type="search"],
  input:not([type]),
  textarea,
  select,
  .editor[contenteditable] {
    font-size: max(1em, 16px);
  }
  /* THE FLOOR MUST ALSO OUT-RANK, NOT JUST COME LAST. The onboarding
     styles its fields as `#screen-onboard input { font: inherit }` -
     specificity (1,0,1) - and an ID beats the attribute selectors above
     no matter what order the cascade runs in. So the sign-up screens,
     of all places, kept 13px fields: Safari zoomed on the first focus
     of "your username" and the zoom stuck, which is the app "floating
     around" (owner report, 2026-08-12). Any future ID-scoped screen
     that styles its own fields must be added here at its own weight. */
  #screen-onboard input,
  #screen-onboard textarea,
  #modal input,
  #modal textarea {
    font-size: max(1em, 16px);
  }
}
