/* ============================================================
   Life Base — v6: robustness & depth
   Loaded last, so everything here wins over v1–v3 styling.
   ============================================================ */

/* ---------- 03 §2 — the growth contract ----------
   One invariant: content never renders outside a block's frame. */

/* ---------- an edge, not a shade (running log 2026-07-30 §1) ----------
   A block lives IN a page, on the same paper as the words next to it. It
   used to carry the board's elevation recipe — a 70px shadow — and on
   white paper that reads as a grey smudge spreading out from the app.
   Put a text container beside it and the difference is the whole
   argument: the container sits ON the page, the block hovers OVER it, and
   nobody wants to write next to a smudge.
   Depth is right for an object floating on the board's canvas. It is
   wrong for an object sitting in a document. So a block on a page has
   exactly one thing at its edge: a hairline, which is what tells a white
   app (the insight feed) apart from white paper and nothing more. */
.nb-block-wrap {
  overflow: hidden; border-radius: 10px;
  /* ---- A HAIRLINE, NOT A FRAME (2026-08-20) ----
     This was `rgba(60,50,40,.08)` — warm, and prominent enough that a
     whiteboard read as a boxed thing on the page rather than as part of
     it. Reported on a page, on a board and standalone. The line still has
     one job, and only one: telling a WHITE app apart from white paper. It
     needs to be visible at that edge and invisible everywhere else, which
     is what half the weight and a neutral grey buy. */
  outline: 1px solid rgba(0, 0, 0, .045);
}

/* ---------- one corner, never two (running log 2026-07-30 §3) ----------
   The frame clips the app — that is the growth contract — and it clipped
   at 10px while the app drew its own paper at its own radius (the habit
   tracker at 20px, the wheel of life at 16px). Two arcs meet at the same
   corner: the app's, and the frame's cutting across it. It read as a
   double-rounded corner because that is exactly what it was.
   One radius wins and it is the platform's — 10px, the value the board's
   app-nodes have always used, so an app now has the same corner on both
   surfaces. Said with enough specificity to beat an app's own root rule:
   block CSS is injected after this file and wins on order otherwise. */
.nb-block-wrap > .nb-block-body { border-radius: inherit; }
.b-obj > .b-node-body { border-radius: 10px; }
/* and the corner never MOVES: selecting or arming a block used to snap it
   from 10px to 4px, which is a shape change as a side effect of a click */
.nb-block.sel .nb-block-wrap { border-radius: 10px; }

/* hug — height is content, no ceiling, never scrolls */
.nb-block-wrap.nb-sz-hug { overflow: visible; }
.nb-block-wrap.nb-sz-hug > .nb-block-body { overflow: visible; }

/* grow — content up to a ceiling, then the body scrolls inside the frame */
.nb-block-wrap.nb-sz-grow > .nb-block-body {
  max-height: var(--nb-ceiling, 65vh);
  overflow-y: auto;
  overflow-x: hidden;
  /* `scrollbar-width:thin` was here. Like the `::-webkit-scrollbar` block
     it replaced in styles.css, asking for a scrollbar WIDTH is asking for
     a classic, always-drawn one with a gutter — inside an app, of all
     places. The platform's overlay bar appears while you scroll and gets
     out of the way after, which is the whole of what this wanted. */
}

/* fixed — the frame is the size it was given; content scrolls inside it */
.nb-block-wrap.nb-sz-fixed > .nb-block-body { height: 100%; overflow: auto; }

/* ---------- the same app at every size (block-system.js: applyScale) ----------
   Below the size a scene was drawn for, its percentage layout stops
   meaning what it meant and its parts collide. So instead of re-flowing
   it small, render it AT its design size and shrink the whole thing as
   one piece. The body is inflated by 1/scale and then scaled back down,
   so the app believes it has its full box and the result is the same
   picture, smaller. Only ever a shrink — never a magnify. */
/* the selector has to out-rank `.nb-block-wrap.nb-sz-fixed > .nb-block-body`
   above, which pins height:100% — otherwise the body is never inflated and
   the scaled app leaves a gap under itself */
.nb-block-wrap.nb-sz-fixed > .nb-block-body.nb-scaled,
.nb-block-wrap > .nb-block-body.nb-scaled {
  /* the body is a flex item of .nb-block-wrap (flex:1), and flex sizing
     beats an explicit height — so it has to opt out of flex first, or the
     inflated height is silently ignored */
  flex: none;
  width: calc(100% * var(--nb-inv, 1));
  height: calc(100% * var(--nb-inv, 1));
  transform: scale(var(--nb-scale, 1));
  transform-origin: 0 0;
  overflow: hidden;
}

/* a block at its ceiling says so quietly — a soft edge, not a permanent
   scrollbar. Calm indication that there is more below. */
.nb-block-wrap.nb-sz-grow { position: relative; }
.nb-block-wrap.nb-sz-grow::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 22px;
  background: linear-gradient(to bottom, rgba(255,255,255,0), rgba(255,255,255,.92));
  pointer-events: none; opacity: 0; transition: opacity .18s ease;
}
.nb-block-wrap.nb-sz-grow.at-ceiling::after { opacity: 1; }

/* ---------- 03 §1 — the deletion guardrail ----------
   Arming is the whole design: visible, keyboard-native, no modal. */

.nb-block.armed .nb-block-wrap {
  outline: 2px solid #d9534f;
  outline-offset: 2px;
}
.nb-arm-hint {
  position: absolute; top: -9px; right: calc(var(--nb-side) + 8px); z-index: 8;
  background: #d9534f; color: #fff; border-radius: 3px;
  padding: 2px 7px; font: 10.5px/1.5 -apple-system, "Segoe UI", sans-serif;
  letter-spacing: .01em; pointer-events: none; white-space: nowrap;
  box-shadow: 0 1px 4px rgba(0,0,0,.18);
}

/* ---------- 01 §10 — chrome never covers content ----------
   Links and controls were unreachable because chrome floated over the
   block's corner. The honest fix is geometry, not z-index: chrome keeps
   its own pixels. That rule survives every rewrite below, and controls
   keep whatever position their own design gave them (an earlier z-index
   blanket forced every button in every block to position:relative, which
   is how the habit tracker's absolutely-anchored add button ended up on
   the LEFT). */

/* ============================================================
   THE TOP STRIP — controls inside the app, silent while you use it

   Full history in block-system.js ("THE TOP STRIP"). The short version:
   the controls belong INSIDE the app's top corners, and they appear when
   the pointer is in the app's top 48px. Nothing at all appears while you
   work in the app, and nothing can move away from your hand, because
   every button sits deep inside the very region that reveals it.

   Two invariants. Break either and the old bugs come back:

     1. EVERY CONTROL STAYS INSIDE THE STRIP. The strip is 48px; the
        pills are 26px tall and sit 8px down, so there is slack above and
        below. Moving toward a button moves further INTO the region that
        shows it. Round one had them 34px ABOVE a strip measured around
        the frame, and reaching for one switched it off.
     2. THE STRIP ITSELF TAKES NO CLICKS. It is `pointer-events:none` and
        only the two pills are live, so the app keeps every pixel between
        and below them. A habit tracker's tab row still works.
   ============================================================ */

/* ============================================================
   THE FRAME IS DRAWN ON THE APP, NOT ON ITS SLOT (2026-08-07)

   Every control here — the two pills, the eight handles, the two drag
   strips — hangs off the PLACEHOLDER, because the wrap clips (the growth
   contract) and anything parented to it would be clipped with the app.

   That was fine while the two were the same box. They are not: the
   placeholder takes all the room its width class allows, the app takes
   only what its own layout contract says it stops improving past, and
   with blocks centred again the difference is split down both sides. So
   the handles stood out in open space — 65px on a kanban, ~200px on a
   smaller app — the ✕ floated off the app's shoulder, and grabbing an
   edge meant aiming at nothing. That is the report.

   `--nb-side` is the overhang on ONE side, and every rule below insets
   by it. A percentage inside it resolves against the containing block,
   which for these absolutely-positioned children IS the placeholder — so
   `(100% - --nb-w)/2` is exactly the gap between the slot's edge and the
   app's, computed by the browser on every layout with nothing to keep in
   sync. `--nb-w` is the app's measured width, written by `hugApp` in
   block-system.js.

   WHY NOT JUST SHRINK THE PLACEHOLDER to the app and be done? Tried, and
   it is a circular constraint: the app asks the placeholder how much room
   it has (`availableWidth`) in order to decide its width, so a
   placeholder sized from the app's width pins every app at the reading
   measure forever. The slot must stay the slot. Only the chrome moves.

   Falls back to `100%` — no inset — for a block that has not measured
   itself yet, which is where an unsized block belongs anyway.
   ============================================================ */
.nb-block {
  position: relative;
  --nb-side: calc((100% - var(--nb-w, 100%)) / 2);
}

/* the strip is a place to put things, not a surface */
/* ---- ONE CORNER, NOT TWO (2026-08-13) ----
   This was `justify-content: space-between`, which put Open in the app's
   top-LEFT corner and ✕ in its top-RIGHT. Two corners spent, and the
   left-hand one is the corner apps actually use: the Vision Board's panel
   header lives there, and "Open" landed squarely on the word BOARDS —
   reported as "the Open button gets in the way of the actions for the app".

   Both pills now sit together in the top-right, ✕ outermost. An app has to
   keep ONE corner clear instead of two, and it is the corner that is
   usually a canvas edge rather than a control: the whiteboard's pills
   start 38px down, the desk's two round buttons are below its masthead,
   and the Vision Board's own "+" is inside its panel on the left.

   The reveal region is unchanged — the app's top 48px — so invariant 1
   still holds: every pill is deep inside the strip that shows it, and
   moving toward one moves further in. */
/* Parented to the WRAP now (block-system.js, "THE BAR HANGS OFF THE APP"),
   so its box IS the app's box — `--nb-side` is gone from here and with it
   the whole class of "the controls landed somewhere else" bugs. */
.nb-block .nb-block-bar {
  position: absolute; left: 0; right: 0; top: 0; bottom: auto; height: 48px;
  display: flex; align-items: flex-start; justify-content: flex-end;
  gap: 6px; padding: 8px; border: 0; border-radius: 0;
  background: none; box-shadow: none; z-index: 9;
  opacity: 0; pointer-events: none;
  transition: opacity .13s ease;
}
/* only the buttons are ever live — see invariant 2 */
/* ---- AND ONLY WHILE THE POINTER IS IN THE STRIP (2026-08-13) ----
   `.sel` used to reveal them as well. The board's twin rule had the same
   line and the same consequence: these pills cover the app's own top
   corners, so a selected block wore them for as long as it stayed
   selected — which, now that using an app selects it, is the whole time
   you are using it.

   Selection still SHOWS ITSELF, through the outline and the resize
   handles (below). What it no longer does is put two controls on top of
   the app you are working in. */
.nb-block.nb-top .nb-block-bar { opacity: 1; }
.nb-block.nb-top .nb-open-btn, .nb-block.nb-top .nb-block-del { pointer-events: auto; }

.nb-open-btn {
  display: inline-flex; align-items: center; gap: 6px;
  height: 26px; padding: 0 11px 0 9px; border-radius: 999px;
  background: rgba(255,255,255,.96); backdrop-filter: blur(10px);
  border: 1px solid #e2dfdb; color: #4d4a46; cursor: pointer;
  font: 500 12px/1 -apple-system, "Segoe UI", sans-serif; white-space: nowrap;
  box-shadow: 0 2px 10px rgba(50,40,30,.16);
  pointer-events: none;
  transition: background-color .12s ease, color .12s ease;
}
.nb-open-btn svg { width: 13px; height: 13px; display: block; flex: none; }
.nb-open-btn:hover { background: #fff; color: #2f2d2b; }

.nb-block-del {
  width: 26px; height: 26px; display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px; color: #6f6c68; cursor: pointer;
  background: rgba(255,255,255,.96); backdrop-filter: blur(10px);
  border: 1px solid #e2dfdb; box-shadow: 0 2px 10px rgba(50,40,30,.16);
  pointer-events: none;
  transition: background-color .12s ease, color .12s ease;
}
.nb-block-del svg { width: 14px; height: 14px; display: block; }
.nb-block-del:hover { background: #fdeceb; border-color: #f3cdc9; color: #c0392b; }

/* ---------- the frame: outline and grab handles ----------
   Same trigger as the buttons, because these cover the app's edge too.
   Reaching the top of the app is how you ask for the whole frame. */
.nb-block .nb-rs { opacity: 0; pointer-events: none; transition: opacity .13s ease; z-index: 10; }
.nb-block.nb-top .nb-rs,
.nb-block.sel .nb-rs { opacity: 1; pointer-events: auto; }
.nb-block.nb-top .nb-block-wrap {
  outline: 1px solid rgba(74,155,232,.45); outline-offset: 2px;
}
.nb-block.sel .nb-block-wrap { outline: 1px solid rgba(74,155,232,.85); outline-offset: 2px; }

/* on the edge, never past it: outside the block is the page's, and the
   14px to the LEFT is spoken for (page-ui.js puts the caret there) */
.nb-block .nb-rs-n { top: 0; left: calc(var(--nb-side) + 12px); right: calc(var(--nb-side) + 12px); height: 7px; }
.nb-block .nb-rs-s { bottom: 0; left: calc(var(--nb-side) + 12px); right: calc(var(--nb-side) + 12px); height: 7px; }
.nb-block .nb-rs-e { right: var(--nb-side); top: 12px; bottom: 12px; width: 7px; }
.nb-block .nb-rs-w { left: var(--nb-side); top: 12px; bottom: 12px; width: 7px; }
.nb-block .nb-rs-nw, .nb-block .nb-rs-ne,
.nb-block .nb-rs-se, .nb-block .nb-rs-sw {
  width: 10px; height: 10px;
  background: #fff; border: 1.5px solid #4a9be8; border-radius: 50%;
  box-shadow: 0 1px 3px rgba(0,0,0,.18);
}
.nb-block .nb-rs-nw { top: 0; left: var(--nb-side); right: auto; bottom: auto; }
.nb-block .nb-rs-ne { top: 0; right: var(--nb-side); left: auto; bottom: auto; }
.nb-block .nb-rs-se { bottom: 0; right: var(--nb-side); left: auto; top: auto; }
.nb-block .nb-rs-sw { bottom: 0; left: var(--nb-side); right: auto; top: auto; }

/* ---------- moving the block: two strips, east and south ----------
   They are outside the frame, so they cover nothing and never need to be
   drawn. Plain hover is enough. Not the west side — the page owns that
   (see block-system.js). */
.nb-move-frame {
  position: absolute; top: -14px; bottom: -14px;
  left: calc(var(--nb-side) - 14px); right: calc(var(--nb-side) - 14px);
  z-index: 6; pointer-events: none;
}
.nb-move-frame > div { position: absolute; pointer-events: none; cursor: grab; }
.nb-block:hover .nb-move-frame > div { pointer-events: auto; }
.nb-block:hover .nb-move-frame > div:active { cursor: grabbing; }
.nb-mf-s { bottom: 0; left: 14px; right: 0; height: 14px; }
.nb-mf-e { top: 14px; bottom: 0; right: 0; width: 14px; }
.nb-block.nb-open .nb-move-frame { display: none; }

/* ============================================================
   ONE CHROME RULE, FOR EVERY APP, ON BOTH SURFACES

   The problem this solves, stated plainly: an app has TWO kinds of
   control on it. The system's own (open / move / capture / delete /
   resize) and the app's own (the kanban's Desk-or-Current switch, the
   daily planner's Card-or-Arrows switch, and whatever every future app
   adds). Fixing only the system's half is why this kept coming back:
   the app's half kept appearing over the work while it was being used.

   So there is one rule and one owner. The SYSTEM decides when any
   control shows. An app marks its controls with `data-lb-chrome` and
   stops managing their visibility itself.

   The rule, and note that it is deliberately NOT the same trigger the
   rail above uses: an app's own controls are hidden until the object is
   SELECTED, and they stay put for as long as it is.

   The difference is not inconsistency, it is the whole principle. The
   rail sits outside the app and takes none of its pixels, so it can
   appear on hover and cost nothing. These controls sit INSIDE the app,
   over content, and anything that covers content has to be asked for —
   otherwise it lands under your hand while you are using the thing. So:
   outside the app, hover. Inside the app, selection. One question decides
   it, every time — whose pixels does this control take.

   Selecting is easy now, which is what makes this affordable: press the
   rail, an edge strip, or a corner. It used to mean hitting a 14px ring
   that only woke within a band you could not see, which is why this rule
   arrived feeling like a punishment.

   For a new app the whole contract is one attribute:
       <div class="my-switcher" data-lb-chrome> ... </div>
   Nothing else. It then behaves like every other app, forever.

   These selectors deliberately out-rank an app's own `:hover` rules,
   because app CSS is injected into the document after this file and
   would otherwise win on order.
   ============================================================ */
.nb-block .nb-block-body [data-lb-chrome],
.b-obj .b-node-body [data-lb-chrome] {
  opacity: 0 !important;
  pointer-events: none !important;
  transition: opacity .14s ease;
}
.nb-block.nb-top .nb-block-body [data-lb-chrome],
.nb-block.sel .nb-block-body [data-lb-chrome],
.b-obj.nb-top .b-node-body [data-lb-chrome],
.b-obj.sel .b-node-body [data-lb-chrome],
.b-obj.node-focused .b-node-body [data-lb-chrome] {
  opacity: 1 !important;
  pointer-events: auto !important;
}

/* ---------- full view ----------
   The backdrop is nearly opaque on purpose (was .34). At a third of the
   way to black the page, the sidebar and the writing all stayed legible
   around the app, so the app read as a big card ON the page rather than
   as the only thing on screen. Full view is a room with the lights down,
   and the same near-black the board's own full view uses — one full view,
   two surfaces, one look. */
.nb-open-backdrop {
  position: fixed; inset: 0; z-index: 99980;
  background: rgba(23,20,15,.88); backdrop-filter: blur(3px);
  opacity: 0; transition: opacity .2s ease;
}
.nb-open-backdrop.on { opacity: 1; }
.nb-block.nb-open .nb-block-wrap {
  position: fixed; z-index: 99981; margin: 0;
  box-shadow: 0 40px 110px rgba(30,22,12,.42), 0 8px 26px rgba(30,22,12,.22);
}
.nb-block.nb-open .nb-open-btn,
.nb-block.nb-open .nb-block-bar,
.nb-block.nb-open .nb-rs { display: none; }
/* ---- THE WAY OUT SITS IN A CORNER, NOT ON THE APP ----
   It was centred at the foot of the window, which on a machine that fills
   the window is directly on top of the space bar. A way out that covers
   the thing you opened is in the way for the whole time you are not
   leaving. Top right, quiet until the pointer finds it — the same rule
   the board's toolbar follows. */
.nb-open-exit {
  position: fixed; top: 16px; right: 18px; z-index: 99982;
  display: flex; align-items: center; gap: 7px;
  background: rgba(252,251,249,.94); backdrop-filter: blur(18px);
  border: 1px solid rgba(60,50,40,.12); border-radius: 999px;
  padding: 6px 13px; box-shadow: 0 10px 30px rgba(20,14,6,.28);
  font: 12px/1 -apple-system, "Segoe UI", sans-serif; color: #6b6763; cursor: pointer;
  user-select: none; opacity: 0; transition: opacity .22s ease;
}
.nb-open-exit.on { opacity: .42; }
.nb-open-exit.on:hover { opacity: 1; background: #fff; color: #3a3836; }
.nb-open-exit kbd {
  font: inherit; font-size: 11px; background: rgba(60,50,40,.07);
  border-radius: 4px; padding: 1px 5px; color: #4d4a46;
}

/* ---------- the width is asked for, never assigned (spec 11 §3.3-3.4) ----------
   `--c-max` is gone. It was 60% of the canvas — a PROPORTION OF THE
   WINDOW, which is not a reading measure and is not an app's needs
   either. On a 1900px window it gave a kanban 843px and called it a
   page. The two real answers are named constants on one centre line;
   see styles.css.

   `width` is a property of the node, so a paragraph and a spreadsheet
   can share a page and each be right. It is stored as INTENT — reading /
   wide / full — and never as a pixel number, because a pixel number is
   wrong the moment the same page opens on a phone, in split view, or on
   a 6K monitor. */
.c-body { max-width: var(--lb-read); }
.container { max-width: var(--lb-full, var(--lb-wide)); }

/* ============================================================
   THE PARAGRAPH GAP (2026-08-06)

   A paragraph in this document is a bare <div> — the schema's own
   toDOM writes no class, only an optional inline style for background,
   indent or alignment. That is what `div:not([class])` names, and it is
   the reason this can be pure CSS: every line ALREADY is its own block
   node. We never gave those nodes any space.

   With none, the only way to separate two thoughts was to press Enter
   twice, which spends a whole 24px line to buy a gap. 3px of padding
   plus 1px of margin on each side gives 8px between paragraphs and
   nothing inside a wrapped one — half a line, invisible as a mechanism,
   and most of what reads as "cleaner" on someone else's page.

   The padding is on the LINE, not on a wrapper, so a background color
   or an indent set on that same line still lands on the same box.
   ============================================================ */
.pm-editor > div:not([class]),
.c-body > div:not([class]) {
  padding: var(--lb-blockpad) 0;
  margin: var(--lb-blockgap) 0;
}
/* ---------- A BLANK LINE COSTS HALF A LINE, NOT A WHOLE ONE ----------
   The rule above gives every line 8px of air, which is what makes one
   Enter a real paragraph break. But everyone's hands still type TWO
   Enters, because that is what a paragraph took in every editor that
   had no such air. Two Enters leaves a genuine empty line in the
   document, and an empty line was costing a full 24px line box plus its
   own 8px — so the habit produced a 40px chasm where 8px was already
   enough. It looked like the spacing had gone wrong. It had not; the
   habit and the new rhythm were being charged for twice.

   Shrinking the empty line is the fix rather than shrinking the 8px,
   because those are two different knobs and only one of them is wrong.
   The air between lines and bullets is what makes the page readable and
   it stays exactly as it is. A DELIBERATE blank line is still available
   and still visible — it just reads as one line of white (28px total)
   instead of a page break.

   The trade: the moment you type into an empty line it grows back to
   full height, so content below it hops down 12px once. That is the
   whole cost, it happens on the first keystroke of a line and never
   again, and it buys back the way people actually type.

   Two selectors for one thing: ProseMirror renders an empty textblock
   with a `<br class="ProseMirror-trailingBreak">` inside it, so `:empty`
   alone never matches under the live engine. The bare `:empty` half is
   for the old contenteditable fallback. */
.pm-editor > div:not([class]):empty,
.c-body   > div:not([class]):empty,
.pm-editor > div:not([class]):has(> br:only-child),
.c-body   > div:not([class]):has(> br:only-child) {
  line-height: 1;
  padding-top: 2px;
  padding-bottom: 2px;
}

/* toggles, callouts and quotes are line-level too, so they keep the
   same outer rhythm rather than inventing their own */
.pm-editor > .tg, .c-body > .tg { margin: var(--lb-blockgap) 0; }
/* top and bottom ONLY. The shorthand would take `padding-left:18px` off
   the toggle head (styles-v2.css), and the arrow is absolutely positioned
   in exactly those 18px — the head's words would print on top of it. */
.tg-head, .co-tx > div:not([class]) {
  padding-top: var(--lb-blockpad); padding-bottom: var(--lb-blockpad);
}

/* ---- ONE CENTRE LINE (restored 2026-08-06, supersedes the left edge) ----
   The history, because this has now been decided twice and the second
   answer is the first one:

     · originally an app broke out with EQUAL negative margins, so its
       centre stayed on the paragraph's centre.
     · then it was changed to one LEFT edge, taking extra room to the
       right, on a document argument: the text's left margin is the
       page's alignment line, so an app starting 150px left of every
       sentence looks wrong beside them.

   That argument is about a paragraph. It is not about a MACHINE. An
   app-block is not read left-to-right off a margin — it is used, with
   both hands and the eyes at its middle, and a wide app anchored to the
   text's left edge puts its middle well right of the person sitting at
   the screen. That off-centre feeling is what sent this back.

   So: symmetric breakout, on the page's own centre line. A wide app does
   start to the LEFT of the writing, and that is accepted deliberately —
   the page's axis and the paragraph's axis are the same axis, and it is
   the app that grows around it rather than sliding off it.

   `min()` still matters: the room beside a centred reading column is
   (full - read) / 2 on EACH side, and asking for more than that is how a
   block runs off the page on a narrow window. It takes what is there,
   equally, and stops — so this can never produce an asymmetric result.

   `display:block` still matters: `.nb-block` is inline-block, and
   margins do not stretch an inline-block. */
/* `.pm-editor` as well as `.c-body`: ProseMirror mounts its own div
   INSIDE the body, so the blocks are grandchildren of `.c-body` and a
   `.c-body >` selector silently matched nothing. That is exactly how a
   kanban stayed at the reading measure while everything said it was
   'wide'. The `.c-body >` half stays for the old-engine fallback, where
   the body IS the editable. */
/* `.bl-line >` is the legacy engine's block wrapper (page-ui.js
   wrapBareBlocks). PM renders its blocks as direct children, the old engine
   puts each one in a line div — so without these four selectors an app on a
   legacy-engine page never breaks out at all and is stuck at the 700px
   reading measure while the same app under PM gets 1050. Measured: 686px,
   which is under the Vision Board's own 720px design floor, so it arrived
   scaled to 0.95 for no reason anybody could see. */
.c-body > .lb-w-wide,
.c-body > .lb-w-full,
.c-body > .bl-line > .lb-w-wide,
.c-body > .bl-line > .lb-w-full,
.pm-editor > .lb-w-wide,
.pm-editor > .lb-w-full {
  display: block;
  width: auto;
  max-width: none;
  margin-left: calc(-1 * min(var(--lb-sideroom), (var(--lb-room) - var(--lb-read)) / 2));
  margin-right: calc(-1 * min(var(--lb-sideroom), (var(--lb-room) - var(--lb-read)) / 2));
}
/* the room actually available beside a centred reading column */
.c-body, .pm-editor { --lb-sideroom: calc((var(--lb-full, var(--lb-wide)) - var(--lb-read)) / 2); }
.c-body > .lb-w-wide, .c-body > .bl-line > .lb-w-wide,
.pm-editor > .lb-w-wide { --lb-room: var(--lb-wide); }
.c-body > .lb-w-full, .c-body > .bl-line > .lb-w-full,
.pm-editor > .lb-w-full { --lb-room: var(--lb-full, var(--lb-wide)); }
/* ---- AND CENTRED INSIDE THAT ROOM TOO ----
   A block does not always USE all the room it asked for: the layout
   contract caps it at the size past which it stops getting better (920px
   by default) and writes that as an inline width. Without auto margins
   that capped block sits at the left of the slot it was given, so the
   breakout above centres the ROOM and the app still ends up off-axis
   inside it. Both halves have to agree or neither works. */
.c-body > .lb-w-wide > .nb-block-wrap,
.c-body > .lb-w-full > .nb-block-wrap,
.c-body > .bl-line > .lb-w-wide > .nb-block-wrap,
.c-body > .bl-line > .lb-w-full > .nb-block-wrap,
.pm-editor > .lb-w-wide > .nb-block-wrap,
.pm-editor > .lb-w-full > .nb-block-wrap { margin-left: auto; margin-right: auto; max-width: 100%; }

/* an explicit 'reading' is a real answer, not the absence of one — and a
   reading-width app that asks for less than the column still sits on the
   same axis as one that asks for more */
.c-body > .lb-w-reading,
.pm-editor > .lb-w-reading { display: block; max-width: 100%; }
.c-body > .lb-w-reading > .nb-block-wrap,
.pm-editor > .lb-w-reading > .nb-block-wrap { margin-left: auto; margin-right: auto; max-width: 100%; }

/* ---------- the sidebar folds away (spec 11 §2.1) ----------
   The hamburger gives the page the whole window. Applies to every page
   kind — stacked, board and chat — because "I want to see this thing"
   is not a board idea. Was two columns, is one panel. */
#main.panels-off #nav { display: none; }   /* the two panels and their bar, as one */

/* ---------- 01 §2 — toggle lists ----------
   The head inserts EMPTY; "Toggle list" is a placeholder, never content. */
.tg-head:empty::before {
  content: 'Toggle list';
  color: #a8a5a1;
  pointer-events: none;
}
.tg-arrow { user-select: none; }

/* ---------- 01 §5 — the selection bar ----------
   Additive: the ribbon is untouched. This is the same actions, offered
   where the hands already are. */
#lb-selbar {
  position: fixed; z-index: 99990; display: flex; align-items: center; gap: 2px;
  background: #fff; border: 1px solid #e3e1de; border-radius: 7px;
  box-shadow: 0 4px 16px rgba(0,0,0,.14); padding: 4px 5px;
  font: 12.5px/1 -apple-system, "Segoe UI", sans-serif; color: #2b2a29;
  animation: lb-selbar-in .11s ease-out;
}
@keyframes lb-selbar-in { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }
#lb-selbar button {
  border: 0; background: transparent; cursor: pointer; border-radius: 4px;
  height: 26px; min-width: 26px; padding: 0 6px; color: #2b2a29;
  display: flex; align-items: center; justify-content: center; font-size: 12.5px;
}
#lb-selbar button:hover { background: #f2f0ee; }
#lb-selbar .sb-sep { width: 1px; height: 17px; background: #e3e1de; margin: 0 3px; }
#lb-selbar .sb-sw {
  width: 17px; height: 17px; border-radius: 4px; border: 1px solid rgba(0,0,0,.10);
  cursor: pointer; margin: 0 1px;
}
#lb-selbar .sb-sw.none { position: relative; background: #fff; }
#lb-selbar .sb-sw.none::after {
  content: ''; position: absolute; inset: 3px; border-top: 1px solid #c0392b; transform: rotate(-45deg);
}

/* ---- the bar, now that it is built (spec 11 §1.2) ----
   Everything above this was styled during the v6 surface work and then
   never wired to anything. These are the pieces the live bar needs. */
#lb-selbar button.on { background: #eceae7; }
#lb-selbar .sb-caret { margin-left: 3px; opacity: .5; flex: none; }
#lb-selbar .sb-b b { font-weight: 700; }
#lb-selbar .sb-link { font-size: 12.5px; }
#lb-selbar .sb-ff { font-family: var(--content-font); font-size: 13px; }
#lb-selbar .sb-fs { font-size: 12px; min-width: 14px; text-align: center; }
/* the A over its color bar, exactly as the ribbon drew it */
#lb-selbar .sb-fc {
  font-family: var(--content-font); font-size: 13px; line-height: 1;
  border-bottom: 3px solid #c00000; padding-bottom: 1px;
}
#lb-selbar .sb-hl {
  width: 14px; height: 14px; border-radius: 3px;
  background: linear-gradient(#fdf3c8, #f7e9a8); border: 1px solid #e3d69a;
}

/* ---------- 01 §12 — the page title icon ----------
   The icon sits in the header's LEFT GUTTER, absolutely positioned. It
   must not move the title, resize it, or stretch its underline — the
   title was right as it was, and an optional decoration has no business
   changing it. */
#page-header { position: relative; }

/* ---------- the icon, aligned to the title rather than to a guess ----------
   `top: 22px` was a measured constant, and it stopped being true the
   moment the title's size or leading moved. Reported as sitting off
   centre and too close, and both were correct: the title is 34px at
   line-height 1.2, so its first line is a 40.8px box whose centre is
   ~22px down — while the icon was a 21px box STARTING at 22px, putting
   its centre ~10px below the title's.

   So it is no longer positioned by a number. The icon box is given the
   height of exactly one line of the title and centres itself inside it,
   which stays correct if the title is ever restyled.

   ON WHAT NOTION WOULD DO: Notion puts the icon ABOVE the title, on
   its own line, at ~78px. That is not a spacing tweak — it pushes the
   title down and re-times the whole top of the page, and this header
   is deliberately centred on the writing column (spec 11 §3.3) with
   the icon in the gutter so that an optional decoration cannot move
   the title. Borrowing Notion's PRINCIPLE — the icon is a companion to
   the title, optically aligned with it, given real air — without
   borrowing a layout that would undo a settled decision. */
#title-icon {
  position: absolute;
  left: -46px;
  /* the header's own top padding, plus the title's 2px — see the
     --lb-header-pad-top comment in styles.css for why both are needed.
     Guarded by polish-pass.mjs, which measures the two boxes and fails
     if they drift apart by more than 2px. */
  top: calc(var(--lb-header-pad-top, 20px) + 2px);
  z-index: 2;
  width: 32px;
  height: calc(34px * 1.2);      /* one line of the title, exactly */
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  user-select: none;
  border-radius: 6px;
}
#title-icon:hover { background: #f2f0ee; }
#title-icon.empty { opacity: 0; font-size: 16px; color: #c8c6c4; transition: opacity .13s ease; }
#page-header:hover #title-icon.empty { opacity: 1; }

/* A narrow window has no gutter to put it in. Rather than let it slide
   under the rail or off the canvas, it comes inside and sits above the
   title — which is the one place that is always there. */
@media (max-width: 1100px) {
  #title-icon {
    position: static;
    width: auto; height: auto;
    justify-content: flex-start;
    margin: 0 0 2px;
    padding: 1px 3px;
  }
}
#lb-iconpick {
  position: fixed; z-index: 99991; background: #fff; border: 1px solid #e3e1de;
  border-radius: 9px; box-shadow: 0 8px 28px rgba(0,0,0,.17); padding: 9px;
  width: 268px; max-height: 320px; overflow-y: auto;
}
#lb-iconpick .ip-g { font: 10.5px/1 -apple-system, sans-serif; color: #8a8886;
  text-transform: uppercase; letter-spacing: .05em; margin: 7px 3px 5px; }
#lb-iconpick .ip-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 1px; }
#lb-iconpick .ip-em {
  height: 29px; display: flex; align-items: center; justify-content: center;
  font-size: 18px; cursor: pointer; border-radius: 5px;
}
#lb-iconpick .ip-em:hover { background: #f2f0ee; }
#lb-iconpick .ip-clear {
  width: 100%; margin-top: 8px; border: 0; background: #f6f5f3; border-radius: 5px;
  padding: 6px; cursor: pointer; font: 12px -apple-system, sans-serif; color: #605e5c;
}
.page-row .pg-emoji { font-size: 12px; margin-right: 1px; }

/* ---------- 01 §13 — native page zoom ---------- */
#page-zoom-pill {
  position: absolute; right: 14px; bottom: 12px; z-index: 20; cursor: pointer;
  background: rgba(30,28,26,.68); color: #fff; padding: 6px 11px; border-radius: 999px;
  font: 12px/1 var(--content-font); letter-spacing: .02em;
  opacity: 0; transition: opacity .25s ease; pointer-events: none;
}
#page-zoom-pill.on { opacity: 1; pointer-events: auto; }

/* ---------- 01 §4 — undo / redo in the ribbon ----------
   Side by side now, and styled by .rb-btn like everything else in the
   one-row toolbar. Only the disabled look is special. */
.rb-undo button:disabled:hover { background: transparent; }


/* ---------- 08 — link cards ---------- */
.lb-card {
  display: flex; gap: 10px; align-items: stretch; border: 1px solid #e8e6e3;
  border-radius: 7px; background: #fff; overflow: hidden; margin: 4px 0;
  max-width: 460px; cursor: pointer; text-decoration: none; color: inherit;
}
.lb-card:hover { border-color: #cfccc8; }
.lb-card-thumb { width: 128px; flex: none; background: #f4f2f0; object-fit: cover; }
.lb-card-main { padding: 8px 10px; min-width: 0; display: flex; flex-direction: column; justify-content: center; }
.lb-card-t {
  font: 600 12.8px/1.35 -apple-system, "Segoe UI", sans-serif; color: #2b2a29;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.lb-card-s { font: 11.5px/1.4 -apple-system, sans-serif; color: #7a7876; margin-top: 3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lb-card-play {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  pointer-events: none;
}
.lb-card-thumbwrap { position: relative; width: 128px; flex: none; }
.lb-embed { border: 0; border-radius: 7px; width: 100%; aspect-ratio: 16/9; max-width: 560px; display: block; margin: 6px 0; }

/* ============================================================
   04 — the board
   ============================================================ */

/* §3 — text truly centered in a shape.
   Box-centering is only correct for rectangles and circles. On a speech
   bubble the tail drags the visual mass upward; on a mountain the mass is
   at the base; a block arrow's body sits left of its point. Centering in
   the BOUNDING BOX puts text on the tail or floating above the mountain —
   which is what "centered left-to-right but not top-to-bottom" is.
   Each silhouette gets the inset that centers it optically. */
/* The previous attempt used PERCENTAGE padding, which was the bug and not
   a small one: in CSS, percentage padding resolves against the element's
   WIDTH on all four sides. On a wide, short shape that made the top and
   bottom padding enormous — and because the box is content-box, that
   padding grew the element TALLER than the shape it sits on. Centering
   then happened inside a box bigger than the shape, so the text sat below
   the shape's true middle. Which is precisely "centered left to right but
   not top to bottom."

   Now: border-box, grid centering (which cannot be defeated by a stray
   child element), and optical offsets expressed as a TRANSLATE — a
   percentage of the text's own height, not of the shape's width. */
/* --- and one more step, for the EMPTY shape ---
   Centring the contents of the covering box works the moment there ARE
   contents, and only then. An empty contenteditable has no item to centre,
   so the browser parks the caret at the top of the box: the caret sat high,
   then jumped to the middle on the first keystroke.

   So the covering box and the centred box are now two elements. The wrapper
   covers the shape and carries the optical offsets (percentages that have
   to keep resolving against the SHAPE, which is what they were tuned on).
   The editable is a content-sized box pinned to the wrapper's middle — one
   line tall when empty, taller as it fills, always centred on the same
   line. The caret and the first letter end up in the identical position
   because they are literally the same line box. */
.b-shape-textwrap {
  position: absolute; inset: 0; box-sizing: border-box;
  pointer-events: none;
}
.b-shape-text {
  box-sizing: border-box; display: block;
  position: absolute; left: 0; right: 0; top: 50%; bottom: auto;
  transform: translateY(-50%);
  text-align: center; padding: 6px 10px; line-height: 1.3;
  max-height: 100%; overflow-y: auto; overflow-x: hidden;
}
.b-shape-text > * { max-width: 100%; }

/* optical centering — the visual mass of a silhouette is not its box.
   On the WRAPPER, so these stay percentages of the shape. */
.b-obj[data-shape="speech-bubble"] .b-shape-textwrap { transform: translateY(-9%); }
.b-obj[data-shape="speech-blurb"] .b-shape-textwrap { transform: translateY(-10%); }
.b-obj[data-shape="mountain"] .b-shape-textwrap { transform: translateY(18%); }
.b-obj[data-shape="car"] .b-shape-textwrap,
.b-obj[data-shape="motorcycle"] .b-shape-textwrap { transform: translateY(-8%); }
.b-obj[data-shape="block-arrow"] .b-shape-textwrap { transform: translateX(-9%); }
.b-obj[data-shape="arrow-sign"] .b-shape-textwrap { transform: translateX(-7%); }
.b-obj[data-shape="star"] .b-shape-textwrap { padding: 0 22%; }
.b-obj[data-shape="octagon"] .b-shape-textwrap { padding: 8px 14%; }

/* lists inside board text — the container styles are scoped to .c-body,
   so a sticky's bullets fell back to the browser's 40px indent and got
   clipped by the sticky's overflow */
.b-editable ul, .b-editable ol, .b-shape-text ul, .b-shape-text ol {
  padding-left: 20px; margin: 2px 0; text-align: left;
}
.b-editable li, .b-shape-text li { line-height: 1.45; }

/* §1 — select first, edit deliberately. A non-editing overlay must not
   swallow the pointer, or the shape underneath can never be dragged. */
.b-shape-text:not(.editing), .b-editable:not(.editing) { pointer-events: none; user-select: none; }
.b-shape-text.editing, .b-editable.editing { pointer-events: auto; user-select: text; }

/* §5 — a line is a stroke, not a box.

   The object's element still spans the line's bounding rectangle, because
   that is how everything else on the board is positioned. But a diagonal
   line's rectangle is mostly empty space, and while that rectangle took
   the pointer, the line stole every click that landed anywhere near it —
   including clicks meant for whatever sits in the crook of the diagonal.
   So the element takes no pointer at all, and a single invisible fat copy
   of the stroke takes it back. You click the line by clicking the line. */
.b-obj.b-line { pointer-events: none; cursor: default; }
.b-line .b-line-hit { pointer-events: stroke; cursor: grab; }
.b-line .b-line-svg { overflow: visible; }
.board-host.grabbing .b-line .b-line-hit { cursor: grabbing; }

/* no frame around a line — the ends and the middle ARE the chrome */
.b-selbox.for-line { border: none; border-radius: 0; }
/* the bend handle reads as a different KIND of control than an endpoint:
   an end moves the line, the middle curves it */
.b-hd.b-bend {
  background: #4a9be8; border-color: #fff;
  box-shadow: 0 0 0 1px #4a9be8; cursor: move;
}

/* ============================================================
   Chat — the surface
   ============================================================ */

/* ---------- a reply is text, so it must be selectable ----------
   `body` sets `user-select:none` (styles.css) because the app chrome is an
   app, not a document — the nav, the ribbon and the tabs should not smear
   into a selection when you drag across them. Every DOCUMENT surface then
   opts back in: `.c-body` on a stacked page, `.b-editable` on a board. The
   chat never did, so a reply could be read and not copied — you could drag
   across a whole answer and highlight nothing.

   Opt in the message list only. The composer is a real <textarea> and was
   always fine; the header and the model line stay unselectable, because
   they are chrome. */
.chat-list, .chat-list * {
  user-select: text; -webkit-user-select: text;
}
.chat-list { cursor: auto; }
.chat-text { cursor: text; }
/* the copy button and the Thinking toggle are controls sitting inside that
   selectable region — dragging from one of those means "press", not "select" */
.chat-copy, .chat-think summary, .chat-msgctx {
  user-select: none; -webkit-user-select: none; cursor: pointer;
}

/* ============================================================
   Chat — the model, named (luna-primary.md §6)
   ============================================================ */

/* Bottom-left, aligned with the composer above it rather than the window,
   so it reads as belonging to the chat and not to the app chrome. Quiet
   enough to ignore and legible enough to answer "what am I talking to?" —
   it is a label, not a control, and nothing about it should invite a click. */
.chat-modelline {
  max-width: 700px;
  margin: 7px auto 0;
  font: 11.5px/1 -apple-system, "Segoe UI", sans-serif;
  color: #a19f9d;
  letter-spacing: .01em;
  padding-left: 19px;      /* line up with the text inside the rounded field */
  user-select: none;
  cursor: default;
}
/* The level, beside the name — "GPT-5.6 Luna · High". How hard it is
   thinking is the one setting that most changes what an answer is worth,
   and nothing on screen said what it was. Just the level: the word
   "reasoning" turned a plain fact into a spec sheet, and the person reading
   it already knows what a model does. Same weight as the name, one shade
   darker, so the pair reads as one line rather than a label with a tag
   stapled to it. */
.chat-effort {
  color: #7d7b79;
}
.chat-effort::before {
  content: "·";
  margin: 0 5px;
  color: #cbc9c7;
}

/* §2 — proportional deletion on the board */
.b-obj.armed { outline: 2px solid #d9534f; outline-offset: 3px; border-radius: 3px; }
.b-arm-hint {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 16px; z-index: 30;
  background: #d9534f; color: #fff; border-radius: 999px; padding: 5px 13px;
  font: 12px/1 -apple-system, "Segoe UI", sans-serif; pointer-events: none;
  box-shadow: 0 3px 12px rgba(0,0,0,.2);
}

/* §4.5 — a sticky never spills: it is a fixed square of paper, so long
   writing scrolls inside it.
   A TEXT BOX IS NOT. It has no paper and no edges — it is sized to its
   own words (fitTextHeight in board.js), so there is nothing to spill
   out of and a scrollbar here only ever hid text that should have made
   the box taller. */
.b-sticky-body { overflow-y: auto; }

/* ---------- 03 §4.2 / §4.3 — habit tracker ----------
   A long habit name used to run to (and past) the frame edge. It stays on
   ONE line now, truncated with an ellipsis and the full text on hover —
   not wrapped, because wrapping breaks the row's alignment with the day
   columns, and that alignment is what makes the grid readable at all. */
.htx-card { margin-left: 10px; margin-right: 10px; }
.htx-mid { min-width: 0; flex: 1; }
.htx-name {
  display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 100%;
}
/* a project row reads exactly like a habit row — same height, same rhythm */
.htx-card.htx-project .htx-mid { display: flex; min-height: 24px; }
.htx-projicon { opacity: .75; }

/* ---------- a project is a habit that holds habits (running log 2026-07-30 §2) ----------
   Two things made it read as a different, odder kind of object:

   1. Its name sat CENTRED while every habit name starts at the left.
      `align-items:center` was written as "line it up vertically" — but
      `.htx-mid` is a COLUMN, and in a column that property centres
      HORIZONTALLY. So the one thing a project row had to share with a
      habit row (where its text begins) was the one thing it didn't. The
      row's height is already held by min-height, so the property just
      goes; stretch is what a habit's own `.htx-mid` does.

   2. The habits inside it were pushed 32px right, so their check circles
      missed the column every other check on the page sits in — the
      eye reads that as a different list, not the same list indented. The
      folder glyph already says "these belong to a project"; the layout
      does not need to say it again by breaking the one alignment that
      makes the whole card scannable. Same left edge, same check column,
      same name size as a standalone habit. */
.htx-kids { padding-left: 0; }
.htx-kidrow .htx-name { font-size: 16px; }

/* ---------- 05 §5 — visible thinking ---------- */
.chat-think { margin: 0 0 7px; max-width: 680px; }
.chat-think summary {
  cursor: pointer; list-style: none; display: inline-flex; align-items: center; gap: 5px;
  font: 11.5px/1.6 -apple-system, "Segoe UI", sans-serif; color: #8a8886;
  padding: 1px 8px; border: 1px solid #eae8e5; border-radius: 999px; user-select: none;
}
.chat-think summary::-webkit-details-marker { display: none; }
.chat-think summary::before { content: '›'; transition: transform .15s ease; display: inline-block; }
.chat-think[open] summary::before { transform: rotate(90deg); }
.chat-think summary:hover { color: #605e5c; background: #faf9f8; }
.chat-think-body {
  margin-top: 6px; padding: 9px 12px; border-left: 2px solid #eae8e5;
  font: 12.5px/1.62 -apple-system, "Segoe UI", sans-serif; color: #7a7876;
  white-space: pre-wrap; max-height: 340px; overflow-y: auto;
}

/* ---------- 05 §6 — the chat header (off by default) ---------- */
.chat-header { padding: 10px 0 4px; max-width: 680px; margin: 0 auto; width: 100%; }
.chat-header-add {
  border: 0; background: transparent; color: #b3b0ac; cursor: pointer;
  font: 11.5px/1.35 -apple-system, "Segoe UI", sans-serif; padding: 2px 6px; border-radius: 5px;
  opacity: 0; transition: opacity .15s ease;
}
/* Two conditions, both required: scrolled to the VERY TOP, and the pointer
   actually on the chat. Hover alone was the old rule and it was wrong —
   hover means "I am reading this", which is exactly when nothing should be
   floating above the words. Being at the top is the deliberate act of going
   to look for it. Anywhere else in a conversation: nothing, at all. */
.chat-root.at-top:hover .chat-header-add { opacity: 1; }
.chat-header-add:hover { background: #f4f2f0; color: #605e5c; }
/* while it is open it stays put regardless — you are editing it */
.chat-header.on .chat-header-add { opacity: 1; }

/* CLOSED IS INVISIBLE — even when instructions exist.
   The affordance is hover-only (the `.chat-root:hover` rule above), so a
   conversation in progress has nothing above it at all: no settings area,
   no note, no chrome. It takes no vertical space either, which is the part
   that mattered — a pill parked at the top pushed the conversation down and
   sat in the reading eyeline the whole time.

   Kept in normal flow rather than floated over the list: an absolutely
   positioned affordance would sit ON TOP of the first message, which trades
   one thing in the way for a worse one. In flow it costs a few pixels and
   covers nothing. */
.chat-header:not(.on) { padding: 0; line-height: 0; }

.chat-header.on { border-bottom: 1px solid #f0eeec; padding-bottom: 10px; margin-bottom: 6px; }

/* Done: one press, saves, closes, says nothing further. It replaced a live
   "Saving… / Saved" mark that flickered between the two on every keystroke —
   feedback that never settles is just movement at the edge of your eye. */
.chat-header-done {
  display: block; margin: 6px 0 0 auto;
  border: 1px solid #e2dfdb; background: #fff; cursor: pointer;
  font: 11.5px/1 -apple-system, "Segoe UI", sans-serif; color: #605e5c;
  border-radius: 999px; padding: 5px 14px;
}
.chat-header-done:hover { background: #f4f2f0; color: #242424; border-color: #d9d9d7; }

/* ONE field, always visible when the area is open. It used to be hidden
   behind an "Instructions" toggle inside a row that also held a name and a
   description — three controls to reach one textarea, and an ✕ as the only
   way back out. Now: click to open, type, click away. */
.chat-header-ins {
  display: block; width: 100%; resize: vertical; min-height: 68px;
  border: 1px solid #eae8e5; border-radius: 6px; padding: 8px 10px; outline: none;
  font: 12.5px/1.5 -apple-system, "Segoe UI", sans-serif; color: #3a3836; background: #fdfcfb;
}
.chat-header-ins:focus { border-color: #d9d9d7; background: #fff; }

/* ---------- 01 §1.3 — the save indicator ----------
   At the foot of the sidebar, out of the writing eyeline entirely. Silent
   by default: a turning ring while saving, a quiet check when kept,
   amber only when saving can't keep up. It speaks on hover; a click
   forces a save and names the reason when one exists. */
#save-ind-wrap { margin-left: auto; z-index: 12; }
/* the fallback host (#main) has no footer row to sit in, so it pins */
#main > #save-ind-wrap { position: fixed; right: 14px; bottom: 11px; }
/* Beside Redo, not shoved to the far edge. In the one sidebar it sat at
   the right of a 312px footer, which was still "next to" them; across a
   460px bar it would sit alone in the middle of nowhere, and the three of
   them only read as one promise when they are together. */
#nav-foot > #save-ind-wrap { margin-left: 0; display: flex; align-items: center; }
#save-ind {
  width: 24px; height: 24px; padding: 3px; border: 0; border-radius: 50%;
  background: transparent; cursor: pointer; display: block;
  transition: background .15s ease;
}
#save-ind:hover { background: #efedeb; }
#save-ind svg { display: block; }

/* the parts, and which states show them */
#save-ind .si-ring { stroke: #d9d5d0; }
#save-ind .si-arc { stroke: #a5a29e; opacity: 0; }
#save-ind .si-check { stroke: #7db88f; opacity: 0; }
#save-ind .si-dot { fill: #d98324; opacity: 0; }
#save-ind .si-bang, #save-ind .si-bangdot { stroke: #d98324; fill: #d98324; opacity: 0; }

#save-ind.s-saving .si-arc { opacity: 1; }
#save-ind.s-saving svg { animation: lb-spin 950ms linear infinite; transform-origin: 50% 50%; }
#save-ind.s-saved .si-check { opacity: .55; }
#save-ind.s-saved .si-ring { stroke: #e3e0dc; }
#save-ind.s-offline .si-ring { stroke: #d98324; opacity: .75; }
#save-ind.s-offline .si-dot { opacity: .9; }
#save-ind.s-error .si-ring { stroke: #d98324; }
#save-ind.s-error .si-bang, #save-ind.s-error .si-bangdot { opacity: 1; }

@keyframes lb-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  #save-ind.s-saving svg { animation: none; }
  #save-ind.s-saving .si-arc { animation: si-breathe 1.4s ease-in-out infinite; }
}
@keyframes si-breathe { 0%, 100% { opacity: .25; } 50% { opacity: 1; } }

/* the reason bubble — only ever appears on an explicit click that failed */
#save-ind-bubble {
  position: fixed; z-index: 99999; max-width: 250px;
  background: #fff4e5; color: #7a4a00; border: 1px solid #f0c07a; border-radius: 7px;
  padding: 8px 11px; font: 12px/1.45 -apple-system, "Segoe UI", sans-serif;
  box-shadow: 0 6px 22px rgba(0,0,0,.14);
  opacity: 0; pointer-events: none; transform: translateY(-3px);
  transition: opacity .18s ease, transform .18s ease;
}
#save-ind-bubble.vis { opacity: 1; transform: translateY(0); }

/* ---------- 01 §2.5 — the right-click format menu (swatches & segments) ---------- */
/* The swatch strip and the B/I/U/S segment are gone — the menu is one
   vertical list now (styles.css, ".lb-link-menu"). Their rules went with
   them so nothing can quietly go on rendering in the old shape. */

/* ---------- 05 §5.7 — jump to latest (never scroll for the user) ---------- */
.chat-jump{
  position:absolute;right:18px;bottom:86px;z-index:20;
  border:1px solid #e3e1de;background:rgba(255,255,255,.96);border-radius:999px;
  padding:5px 12px;font:11.5px -apple-system,"Segoe UI",sans-serif;color:#605e5c;
  box-shadow:0 4px 14px rgba(0,0,0,.10);cursor:pointer;
  opacity:0;pointer-events:none;transform:translateY(4px);
  transition:opacity .16s ease,transform .16s ease;
}
.chat-jump.vis{opacity:1;pointer-events:auto;transform:translateY(0)}
.chat-root{position:relative}

/* ---------- the ProseMirror container (phase 2) ----------
   The editor mounts INSIDE .c-body, so the box keeps its own layout and
   every existing query still finds it. The editable is the child. */
.c-body > .pm-editor{outline:none;white-space:pre-wrap;word-break:break-word;min-height:calc(var(--lb-lh) * 1em)}
/* 1.7em, so the indent grows with the reading size — the type-scale suite
   holds this. It is also ONE INDENT STEP: the schema writes the indent
   attribute in the same unit (EM_STEP in editor/schema.js) so a line
   indented by nesting and a line indented by the attribute land on the
   same column. Change one and change the other. */
.pm-editor ul,.pm-editor ol{padding-left:1.7em;margin:0}
.pm-editor li{padding:var(--lb-listpad) 0}
.pm-editor li > ul,.pm-editor li > ol{padding-top:var(--lb-listpad)}
/* a paragraph nested inside a list item is the item's own line — it must
   not add the item's padding a second time */
.pm-editor li > div{margin:0;padding:0}
.pm-editor .tg{position:relative;margin:1px 0}
.pm-editor .tg-content{padding-left:18px}
.pm-editor .tg-head{min-height:20px}
.pm-editor .tg-kids.shut{display:none}   /* holds nothing: PM renders no children into it */
.pm-editor .nb-block.sel{outline:2px solid var(--purple);outline-offset:1px}
.pm-editor .ProseMirror-selectednode{outline:2px solid var(--purple);outline-offset:1px}

/* ---------- the "/" menu (phase 4) ----------
   Plugin state, not markup: the query is highlighted where it was typed
   and nothing is written into the document. */
.lb-slash-live{background:#f1e8f7;border-radius:2px}
.lb-slash-menu{
  position:fixed;z-index:1200;width:260px;max-height:320px;overflow-y:auto;
  background:#fff;border:1px solid #e1e1e1;border-radius:10px;
  box-shadow:0 8px 28px rgba(0,0,0,.16);padding:5px;
  font-family:-apple-system,"Segoe UI",sans-serif;font-size:13px;
}
.lb-slash-row{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:6px 9px;border-radius:6px;cursor:pointer;color:#242424;
}
.lb-slash-row:hover{background:#f4f2f0}
.lb-slash-row.sel{background:#f1e8f7}
.lb-slash-hint{color:#a19f9d;font-size:11.5px}
.lb-slash-empty{padding:9px;color:#a19f9d}

/* the right-click menu, built from the node under the pointer */
.lb-ctx{
  position:fixed;z-index:1300;min-width:190px;background:#fff;
  border:1px solid #e1e1e1;border-radius:9px;box-shadow:0 8px 28px rgba(0,0,0,.16);
  padding:5px;font-family:-apple-system,"Segoe UI",sans-serif;font-size:13px;
}
.lb-ctx-row{padding:6px 10px;border-radius:6px;cursor:pointer;color:#242424;white-space:nowrap}
.lb-ctx-row:hover{background:#f1e8f7}
.lb-ctx-sep{height:1px;background:#ebebeb;margin:4px 6px}

/* ---- the right-click menu, on ProseMirror ----
   The browser stops painting the selection once focus moves to the menu,
   so the menu paints it: a decoration, not markup, so the highlight
   costs nothing on the undo stack. */
.lb-sel-paint{background:#bcd6f2}
/* a format button that is already on reads as pressed, so the menu
   tells you the state of the words rather than only offering to change it */

/* ---- a file being chosen ----
   A placeholder goes into the page the instant you pick Image, and is
   filled in when the file arrives. It exists so that nothing has to be
   held in a variable while the file dialog is open — see the schema note
   on `pending`. It is also what stops an otherwise-empty container being
   pruned out from under the picture. */
img.nb-pending,video.nb-pending,audio.nb-pending{
  min-width:160px;min-height:96px;display:block;border-radius:4px;
  border:1px dashed #c8c6c4;background:
    repeating-linear-gradient(-45deg,#f7f6f4 0 10px,#f1efec 10px 20px);
}
/* what you actually see while a file is being chosen: a calm box that
   says what is happening, not a broken-image icon */
.nb-pending-box{
  display:flex;align-items:center;justify-content:center;
  min-height:88px;margin:2px 0;padding:0 16px;max-width:520px;
  border:1px dashed #c8c6c4;border-radius:4px;background:#faf9f8;
  color:#8a8886;font-size:12.5px;letter-spacing:.01em;user-select:none;
  animation:nb-pending-pulse 1.6s ease-in-out infinite;
}
span.file-chip.nb-pending{min-width:0;min-height:0;display:inline;border:0;background:none;opacity:.55}
@keyframes nb-pending-pulse{0%,100%{opacity:.75}50%{opacity:1}}
@media (prefers-reduced-motion:reduce){.nb-pending{animation:none}}

/* ---------- SIDE BY SIDE (spec 11 §4) ----------
   The pair is `lb-w-wide` in the schema's own toDOM, so two things beside
   each other get the app measure rather than the reading one — half of
   750px is 375px, which is narrower than anything worth putting beside
   something else.

   `min-width: 0` on the column is the flexbox rule that is always
   forgotten: without it a flex item refuses to shrink below its content,
   so one wide block pushes the other off the page instead of sharing. */
/* the pair carries `lb-w-wide` from the schema's own toDOM, and that rule
   sets `display:block` at higher specificity — so the flex has to be said
   at the same level or the two columns quietly stack */
.lb-cols,
.c-body > .lb-cols,
.pm-editor > .lb-cols {
  display: flex;
  align-items: flex-start;
  gap: 22px;
  margin: 10px 0;
}
.lb-col {
  flex: 1 1 0;
  min-width: 0;
}
/* a block inside a pair fills its half — it is already in the room it
   was given, so it must not break out of it again */
.lb-col > .nb-block,
.lb-col > .lb-w-wide,
.lb-col > .lb-w-full {
  display: block;
  width: auto;
  max-width: 100%;
  margin-left: 0;
  margin-right: 0;
}
.lb-col > .nb-block > .nb-block-wrap { width: 100%; max-width: none; }

/* where a drop would land: the outer third of a top-level block, either
   side. ProseMirror's own drop cursor still draws the ordinary case. */
.lb-cols:empty { display: none; }


/* ============================================================
   AN APP AS A PAGE (2026-08-20)
   ============================================================
   Not an app sitting on a page — "/" already makes that, inline in the
   writing, and it is a different thing. This is the page: you click it in
   the panel and the app is what is there. No title, no date stamp, no
   reading column, and none of the chrome a block wears so it can be
   moved, resized or removed inside a document. A page is none of those
   things; the way to remove it is to delete the page. */
#page-body.lb-app-page-host {
  height: 100%; min-height: 100%; max-width: none;
  display: flex; align-items: center; justify-content: center;
  padding: 0; margin: 0;
}
/* ---- RAW. NO CARD UNDER IT. ----
   `.nb-block-wrap` draws a 10px radius and a hairline outline so a block
   reads as an object sitting IN a document. On a page of its own there is
   no document for it to sit in, and that frame is a white card showing
   around the app on all four sides — the exact "you slapped it on a page"
   this kind exists to stop being. The app draws its own edges. */
.lb-page-app .nb-block-wrap {
  outline: none; border-radius: 0; overflow: visible;
  background: transparent; box-shadow: none;
}
/* the signal an app answers. Set by mountAppPage; see the note there. */
.lb-page-app[data-standalone] .nb-block-body { --lb-standalone: 1; }
.lb-page-app .nb-block-wrap > .nb-block-body { border-radius: 0; }
/* the canvas stops being a centred reading column for this one kind */
#canvas:has(> #page-body.lb-app-page-host) { padding: 0; align-items: stretch; }

.lb-page-app { margin: 0; display: flex; align-items: center; justify-content: center; }
.lb-page-app .nb-block-wrap { margin: 0; }
/* No Open, no ✕, no resize handles, no move frame, no arming hint. Every
   one of those exists so a block can be moved, resized or removed INSIDE
   a document. A page is none of those things — the way to remove it is to
   delete the page. */
.lb-page-app .nb-block-bar,
.lb-page-app .nb-open-btn,
.lb-page-app .nb-block-del,
.lb-page-app .nb-rs,
.lb-page-app .nb-move-frame,
.lb-page-app .nb-arm-hint { display: none !important; }
.lb-page-app.sel .nb-block-wrap,
.lb-page-app .nb-block-wrap { box-shadow: none; }

.lb-app-page-missing {
  margin: auto; font: 14px/1.5 -apple-system, "Segoe UI", sans-serif; color: #a5a29e;
}


/* ============================================================
   Habit Tracker — "Vesperia" skin (2026-08-21)

   A pure re-skin: not one rule below touches layout, spacing,
   positioning or structure — only colour, texture, border and
   type. The light theme in styles-v2.css stays intact underneath;
   this sheet loads last, so these rules win the cascade.

   The world: a JRPG equipment menu. Deep walnut darkness with a
   warm hearth-glow at the centre; aged parchment cards outlined in
   dark leather-brown; engraved gold serifs; HP/TP gauge bars; a
   single gold ornament line under the header.

   Notes on technique:
   - The block's JS paints some colours as SVG PRESENTATION
     attributes (fill=/stroke=). Any CSS rule outranks those, so
     they are re-pigmented here without touching the JS.
   - The performance rows carry their win-rate colour as an inline
     style="" — the one thing CSS cannot ordinarily outrank. The
     three possible values are fixed strings, so attribute-substring
     selectors + !important map them onto the world's palette.
   ============================================================ */

/* palette ------------------------------------------------------------------
   walnut deep #1a0f07   hearth glow #523420   leather line #5d4126
   gold dim #b98d4f      gold bright #e6c27a   cream #f2e4c4
   parchment hi #f1e2bd  parchment lo #d9c091  ink #3a2a16
   gem green #3d9e5f     gauge red #c75044     gauge teal #4f9e8f
--------------------------------------------------------------------------- */

.htx{
  background:
    radial-gradient(120% 90% at 50% 18%, rgba(122,78,40,.55) 0%, rgba(122,78,40,0) 55%),
    radial-gradient(140% 120% at 50% 40%, #4a2e18 0%, #2e1c0d 52%, #1a0f07 100%);
  border:1px solid #6b4a24;
  box-shadow:
    inset 0 0 0 1px rgba(230,194,122,.14),
    inset 0 0 70px rgba(0,0,0,.55),
    0 10px 34px rgba(0,0,0,.45);
  color:#f2e4c4;
  font-family:Georgia,'Palatino Linotype',Palatino,'Times New Roman',serif;
}
.htx button{font-family:inherit}

/* ---------- header: engraved gold title + ornament rule ---------- */
.htx-gtitle{
  color:#eed39a;letter-spacing:.06em;font-weight:600;
  font-variant:small-caps;font-size:24px;
  text-shadow:0 1px 0 rgba(0,0,0,.85),0 0 14px rgba(230,194,122,.25);
}
.htx-gtitle::before{content:'✦';margin-right:9px;font-size:15px;color:#c9a252;
  text-shadow:0 0 8px rgba(230,194,122,.5)}
.htx-head{
  border-bottom:1px solid transparent;
  border-image:linear-gradient(90deg,rgba(185,141,79,0),rgba(185,141,79,.85) 18%,rgba(230,194,122,.95) 50%,rgba(185,141,79,.85) 82%,rgba(185,141,79,0)) 1;
  padding-bottom:10px;
}
.htx-tabs{
  background:rgba(20,11,5,.6);border:1px solid #5d4126;border-radius:8px;
  box-shadow:inset 0 1px 3px rgba(0,0,0,.6);
}
.htx-tab{
  color:#b99c6b;letter-spacing:.05em;font-variant:small-caps;font-size:12.5px;
}
.htx-tab:hover{color:#f2e4c4}
.htx-tab.sel{
  background:linear-gradient(180deg,#eacd8f,#c49a52);color:#2e1c09;
  box-shadow:inset 0 1px 0 rgba(255,246,220,.6),0 1px 3px rgba(0,0,0,.5);
  text-shadow:none;
}

/* ---------- date navigation ---------- */
.htx-chev{
  border:1px solid #5d4126;border-radius:8px;
  background:linear-gradient(180deg,#38220f,#241407);
  box-shadow:inset 0 1px 0 rgba(230,194,122,.18),0 1px 3px rgba(0,0,0,.45);
}
.htx-chev:hover{background:linear-gradient(180deg,#452b14,#2c1a0a)}
.htx-chev svg path{stroke:#d9b677}
.htx-datebtn{border-radius:8px}
.htx-datebtn:hover{background:rgba(185,141,79,.14)}
.htx-datetext{color:#f2e4c4;letter-spacing:.05em;text-shadow:0 1px 0 rgba(0,0,0,.8)}
.htx-todaypill{
  background:rgba(212,175,55,.14);color:#e6c27a;border:1px solid rgba(201,162,82,.55);
  letter-spacing:.12em;font-variant:small-caps;font-size:10.5px;
}

/* ---------- parchment cards ---------- */
.htx-card{
  background:
    radial-gradient(130% 150% at 28% 0%, rgba(255,248,224,.5) 0%, rgba(255,248,224,0) 55%),
    linear-gradient(180deg,#f1e2bd 0%,#e6d2a6 58%,#dbc292 100%);
  border:1px solid #5d4126;border-radius:8px;
  box-shadow:
    inset 0 0 0 1px rgba(255,246,220,.45),
    inset 0 -12px 20px rgba(122,84,40,.16),
    0 3px 8px rgba(0,0,0,.5);
}
.htx-name{color:#3a2a16;text-shadow:0 1px 0 rgba(255,246,220,.35)}
.htx-name.done{color:#9a8563;text-decoration-color:rgba(122,96,58,.65)}
/* links inside names: the app-wide lb-link blue is re-pigmented to a deep
   sapphire that belongs on parchment — scoped to the block, nowhere else */
.htx .htx-name a.lb-link{color:#3d5a96;border-bottom-color:rgba(61,90,150,.45)}
.htx .htx-name a.lb-link:hover{border-bottom-color:#3d5a96;background:rgba(61,90,150,.1)}
.htx-time{color:#857050}

/* check orbs — a jewel set in a gold ring; empty socket when undone */
.htx-check svg{filter:drop-shadow(0 1px 1px rgba(0,0,0,.4))}
.htx-check svg circle{fill:rgba(60,40,18,.14);stroke:rgba(122,90,48,.65);stroke-width:1.6}
.htx-check.done svg circle{fill:#3d9e5f;stroke:#e6c27a;stroke-width:1.4}
.htx-check.done svg path{stroke:#fdf6e0}

/* grips, glyphs, row icons */
.htx-cardgrip svg,.htx-rowgrip svg{fill:#a07c4e}
.htx-projicon{opacity:.9}
.htx-projicon svg path{stroke:#7a5a30}
.htx-icon:hover{background:rgba(122,90,48,.14)}
.htx-icon svg path[stroke]{stroke:#7a5a30}

/* edit / delete chips */
.htx-act{
  background:linear-gradient(180deg,#efe0ba,#dcc493);border:1px solid #6b4a24;
  box-shadow:inset 0 1px 0 rgba(255,246,220,.5),0 1px 2px rgba(0,0,0,.35);
}
.htx-act:hover{background:linear-gradient(180deg,#f7ecc9,#e6d0a2)}
.htx-act.edit svg path{stroke:#6b4a1c}
.htx-act.del svg path{stroke:#a33327}

/* drag indicators — the drop line turns to gold */
.htx-card.drop-before{box-shadow:inset 0 2px 0 0 #d4af37,inset 0 0 0 1px rgba(255,246,220,.45),0 3px 8px rgba(0,0,0,.5)}
.htx-card.drop-after{box-shadow:inset 0 -2px 0 0 #d4af37,inset 0 0 0 1px rgba(255,246,220,.45),0 3px 8px rgba(0,0,0,.5)}
.htx-kidrow.drop-before{box-shadow:inset 0 2px 0 0 #d4af37}
.htx-kidrow.drop-after{box-shadow:inset 0 -2px 0 0 #d4af37}
.htx-card.drop-into{outline:2px solid rgba(212,175,55,.75);background:rgba(212,175,55,.14)}
.htx-card.drop-out{outline:1.5px dashed rgba(212,175,55,.6)}

/* ---------- empty states ---------- */
.htx-empty-ic path[stroke]{stroke:#9c7a4e}
.htx-empty-ic path[fill]:not([fill="none"]){fill:#9c7a4e}
.htx-empty-t1{color:#e8d5ac;font-variant:small-caps;letter-spacing:.08em}
.htx-empty-t2{color:#a98c5e}

/* ---------- performance: HP/TP gauges ---------- */
.htx-perfcard{
  background:
    radial-gradient(130% 150% at 28% 0%, rgba(255,248,224,.5) 0%, rgba(255,248,224,0) 55%),
    linear-gradient(180deg,#f1e2bd 0%,#e6d2a6 58%,#dbc292 100%);
  border:1px solid #5d4126;border-radius:8px;
  box-shadow:
    inset 0 0 0 1px rgba(255,246,220,.45),
    inset 0 -12px 20px rgba(122,84,40,.16),
    0 3px 8px rgba(0,0,0,.5);
}
.htx-perfrow.sep{border-top:1px solid rgba(122,90,48,.35)}
.htx-perfrow.dim{opacity:.55}
.htx-perfname{color:#3a2a16}
.htx-perfbar{
  height:9px;background:linear-gradient(180deg,#43301a,#2a1c0c);border-radius:4px;
  box-shadow:inset 0 1px 3px rgba(0,0,0,.65),0 1px 0 rgba(255,246,220,.4);
}
.htx-perffill{border-radius:3px;box-shadow:inset 0 1px 0 rgba(255,255,255,.35),inset 0 -1px 1px rgba(0,0,0,.25)}
.htx-perffill[style*="52,199,89"]{background:linear-gradient(180deg,#8fd6a8 0%,#3d9e5f 55%,#2c7a48 100%)!important}
.htx-perffill[style*="255,159,10"]{background:linear-gradient(180deg,#f2d488 0%,#d99a2b 55%,#a86f14 100%)!important}
.htx-perffill[style*="255,69,58"]{background:linear-gradient(180deg,#e99080 0%,#c75044 55%,#8e2f26 100%)!important}
.htx-perfcount[style*="52,199,89"],.htx-perfrate[style*="52,199,89"]{color:#2c7a48!important}
.htx-perfcount[style*="255,159,10"],.htx-perfrate[style*="255,159,10"]{color:#a86f14!important}
.htx-perfcount[style*="255,69,58"],.htx-perfrate[style*="255,69,58"]{color:#a33327!important}
.htx-perfsec{color:#c9a252;letter-spacing:.18em;font-variant:small-caps;font-size:11.5px}
.htx-perfdash{color:#9a8563}

/* ---------- next queue ---------- */
.htx-daycap{
  background:rgba(185,141,79,.2);color:#6b4a1c;border:1px solid rgba(122,90,48,.45);
  font-variant:small-caps;letter-spacing:.04em;
}
.htx-activate{
  background:rgba(61,158,95,.16);color:#2c7a48;border:1px solid rgba(44,122,72,.5);
  font-variant:small-caps;letter-spacing:.05em;
}
.htx-activate svg path{fill:#2c7a48}
.htx-activate:hover{background:rgba(61,158,95,.28)}
.htx-nextrow .htx-act{background:linear-gradient(180deg,#efe0ba,#dcc493)}

/* ---------- FAB: a gold medallion ---------- */
.htx-fab{
  background:radial-gradient(circle at 34% 28%,#f2dcA4 0%,#d9b677 38%,#a87c34 78%,#7a5a20 100%);
  border:1px solid #5d4126;
  box-shadow:
    inset 0 1px 0 rgba(255,248,224,.65),
    inset 0 -3px 6px rgba(90,60,20,.45),
    0 4px 14px rgba(0,0,0,.55),0 0 18px rgba(212,175,55,.3);
}
.htx-fab:hover{filter:brightness(1.08)}
.htx-fab svg path{stroke:#33200a}
.htx-fab.purple{
  background:radial-gradient(circle at 34% 28%,#d9b8f0 0%,#a06cc8 45%,#6c3d94 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,248,224,.5),
    inset 0 -3px 6px rgba(50,20,80,.5),
    0 4px 14px rgba(0,0,0,.55),0 0 18px rgba(160,108,200,.35);
}
.htx-fab.purple svg path{stroke:#f4e9fc}

/* fab menu — a dark walnut panel with gold trim */
.htx-fabmenu{
  background:linear-gradient(180deg,#33200f,#241407);border:1px solid #b98d4f;border-radius:8px;
  box-shadow:inset 0 0 0 1px rgba(230,194,122,.14),0 10px 28px rgba(0,0,0,.6);
}
.htx-fabitem{color:#f2e4c4}
.htx-fabitem:hover{background:rgba(185,141,79,.18)}
.htx-fabitem svg path[stroke]{stroke:#d9b677}
.htx-fabitem svg path[fill]:not([fill="none"]){fill:#d9b677}

/* ---------- sheets, dialogs, popovers: dark walnut + gold ---------- */
.htx-overlay{background:rgba(16,9,4,.62);backdrop-filter:blur(3px)}
.htx-sheet,.htx-confirm{
  background:linear-gradient(180deg,#33200f 0%,#271709 100%);
  border:1px solid #b98d4f;border-radius:10px;
  box-shadow:
    inset 0 0 0 1px rgba(230,194,122,.14),
    inset 0 0 40px rgba(0,0,0,.4),
    0 18px 50px rgba(0,0,0,.65);
}
.htx-sheet-title{
  color:#eed39a;font-variant:small-caps;letter-spacing:.08em;
  text-shadow:0 1px 0 rgba(0,0,0,.85);
}
.htx-seclabel{color:#c9a252;letter-spacing:.18em}
.htx-input{
  background:linear-gradient(180deg,#efe0ba,#e2cf9f);border:1px solid #6b4a24;
  color:#3a2a16;border-radius:6px;font-family:inherit;
  box-shadow:inset 0 1px 3px rgba(90,60,20,.3);
}
.htx-input::placeholder{color:#9a8563}
.htx-input:focus{border-color:#d4af37;box-shadow:inset 0 1px 3px rgba(90,60,20,.3),0 0 8px rgba(212,175,55,.35)}
.htx-linkbtn{
  background:linear-gradient(180deg,#efe0ba,#e2cf9f);border:1px solid #6b4a24;
  color:#3a2a16;border-radius:6px;font-family:inherit;
}
.htx-linkbtn.empty{color:#9a8563}
.htx-linkbtn:hover{border-color:#d4af37}
.htx-daypill{
  border:1px solid #7a5a30;color:#cbb27f;background:rgba(20,11,5,.5);
  font-variant:small-caps;
}
.htx-daypill:hover{border-color:#d9b677;color:#f2e4c4}
.htx-daypill.sel{
  background:linear-gradient(180deg,#eacd8f,#c49a52);border-color:#5d4126;color:#2e1c09;
  box-shadow:inset 0 1px 0 rgba(255,246,220,.5);
}
.htx-trackrow{color:#e8d5ac}
.htx-switch{background:#4a3620;border:1px solid #6b4a24;box-shadow:inset 0 1px 3px rgba(0,0,0,.55)}
.htx-switch.on{background:#3d9e5f;border-color:#2c7a48}
.htx-knob{background:#f0e3c0;box-shadow:0 1px 3px rgba(0,0,0,.5)}
.htx-btn{font-variant:small-caps;letter-spacing:.06em;border-radius:7px}
.htx-btn.ghost{color:#cbb27f}
.htx-btn.ghost:hover{color:#f2e4c4}
.htx-btn.solid{
  background:linear-gradient(180deg,#eacd8f,#c49a52);color:#2e1c09;border:1px solid #5d4126;
  box-shadow:inset 0 1px 0 rgba(255,246,220,.55),0 2px 5px rgba(0,0,0,.45);
}
.htx-btn.solid:hover{filter:brightness(1.07)}
.htx-btn.danger{
  background:linear-gradient(180deg,#c8563f,#8e2f26);color:#f6e8d0;border:1px solid #5d1f16;
  box-shadow:inset 0 1px 0 rgba(255,220,200,.3),0 2px 5px rgba(0,0,0,.45);
}
.htx-kiditem{background:rgba(20,11,5,.5);border:1px solid #5d4126;color:#e8d5ac}
.htx-kiddel{color:#a98c5e}
.htx-kiddel:hover{color:#e99080}
.htx-childedit{
  background:rgba(20,11,5,.42);border:1px solid #5d4126;border-radius:8px;
  box-shadow:inset 0 1px 3px rgba(0,0,0,.4);
}
.htx-addhabit{
  background:rgba(185,141,79,.1);border:1px dashed rgba(201,162,82,.6);color:#e6c27a;
  font-variant:small-caps;letter-spacing:.06em;border-radius:7px;
}
.htx-addhabit:hover{background:rgba(185,141,79,.2);border-color:#d4af37}
.htx-confirm-t{color:#eed39a;font-variant:small-caps;letter-spacing:.06em}
.htx-confirm-m{color:#cbb27f}
.htx-pop{
  background:linear-gradient(180deg,#33200f,#241407);border:1px solid #b98d4f;
  color:#f2e4c4;border-radius:8px;
  box-shadow:inset 0 0 0 1px rgba(230,194,122,.14),0 12px 32px rgba(0,0,0,.6);
}
.htx-projnote{color:#9a8563}

/* ---------- picker preview thumbnail: same world, miniature ---------- */
.htx-prev{
  background:radial-gradient(140% 120% at 50% 30%, #452b14 0%, #241407 70%, #180d05 100%);
  border:1px solid #5d4126;
}
.htx-prev-nav{color:#a98c5e}
.htx-prev-date{color:#f2e4c4}
.htx-prev-date i{background:rgba(212,175,55,.16);color:#e6c27a}
.htx-prev-card{background:linear-gradient(180deg,#efe0ba,#dcc493);border:1px solid #6b4a24}
.htx-prev-c{border-color:rgba(122,90,48,.6)}
.htx-prev-c.on{background:#3d9e5f;border-color:#2c7a48}
.htx-prev-n{color:#3a2a16}
.htx-prev-n.done{color:#9a8563}
.htx-prev-t{color:#857050}
