/* app/app.css — the prototype's own styles, carved from task-manager.html
   2026-08-28. tokens/globals.css stays the only source of colour. */
* { box-sizing: border-box; }
/* The scrollbar gutter is always reserved, scrollbar or not. Without it, any
   view tall enough to scroll shifts the centred page 5px sideways the moment it
   appears — switching views read as the layout jumping. */
html { scrollbar-gutter: stable; }
body {
  margin: 0;
  font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  background: var(--ink); color: var(--paper); -webkit-font-smoothing: antialiased;
}
.wrap { max-width: 1320px; margin: 0 auto; padding: 0 20px 36px; }

/* ---------------- header ---------------- */
.topbar {
  position: sticky; top: 0; z-index: 60;
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  padding: 12px 20px; border-bottom: 1px solid var(--line);
  background: rgb(var(--c-ink) / .95); backdrop-filter: blur(8px);
}
/* The heading is a PATH, so it is weighted like the trail it replaced: the parts
   above you recede, the separators recede further, and the page you are on is the
   only one at full strength. It keeps the heading's own size — this is still the
   top of the window — and wraps rather than overflowing on a narrow one. */
/* THE HEADING IS THE TRAIL, SO IT IS THE TRAIL'S STYLING — same 12px, same
   weight, same gap, same button padding and hover. CEO, 2026-08-25: "i believe i
   told you make the path the same style as it was before the location change ..
   why is the font bigger?". He had said it once already; I matched the colours
   and left a heading's 15px and weight 600, which is a title's weight on a path.
   The values are copied from `.crumbs` rather than chosen again, so the two
   cannot drift. `margin-bottom` is not copied: the trail sat above a title inside
   a card, this sits in a row. */
.topbar h1 { display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  font-size: 12px; font-weight: 400; color: var(--mute); margin: 0;
  letter-spacing: 0; min-width: 0; }
.topbar h1 .up { color: var(--mute); }
.topbar h1 .sep { color: var(--ghost); }
.topbar h1 .here { color: var(--paper); padding: 3px 6px; }
/* A part that IS a place looks pressable and answers hover, exactly as the
   trail's buttons did. */
.topbar h1 button.up { border: 0; background: none; font: inherit; font-size: 12px;
  color: var(--mute); cursor: pointer; padding: 3px 6px; border-radius: 6px; }
.topbar h1 button.up:hover { background: var(--panel2); color: var(--paper); }
.spacer { margin-inline-start: auto; }

/* The base carries a real look, not just the shared metrics. `.btn` on its own
   used to fall through to the browser's native button — grey fill, black text,
   system border — so a variant class that did not exist produced an OS control
   in the middle of the panel rather than something obviously wrong in the house
   style. A missing variant now degrades to the ghost button. */
.btn {
  border-radius: 8px; font-size: 13px; cursor: pointer; font-family: inherit;
  appearance: none; -webkit-appearance: none;
  background: transparent; color: var(--mute); border: 1px solid var(--line); padding: 6px 10px;
  transition: background-color .15s, border-color .15s, color .15s;
}
.btn:hover { background: var(--panel); color: var(--paper); }
.btn-primary { background: var(--wk); color: var(--onaccent); border: 0; padding: 7px 14px; font-weight: 600; }
.btn-primary:hover { background: var(--wkhover); }
.btn-ghost { background: transparent; color: var(--mute); border: 1px solid var(--line); padding: 6px 10px; }
.btn-ghost:hover { background: var(--panel); color: var(--paper); }
.btn-danger { background: transparent; color: var(--bad); border: 1px solid rgb(var(--c-bad) / .5); padding: 6px 10px; font-weight: 600; }
.btn-danger:hover { background: rgb(var(--c-bad) / .1); }
.ctl {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--line); border-radius: 8px; padding: 6px 10px;
  font-size: 11px; text-transform: uppercase; letter-spacing: .1em;
  color: var(--mute); background: transparent; cursor: pointer; font-family: inherit; text-decoration: none;
}
.ctl:hover { border-color: var(--mute); color: var(--paper); }
/* The selected look belongs to controls that are genuinely one of a set — the
   theme toggle, where Dark stays "Dark" and pressed reports which is in force.
   It does NOT belong to a button whose label changes to name its next action:
   the expand control says "Shrink to a side card" once expanded, so it is an
   action, not a state, and carrying aria-pressed there both painted it as
   permanently selected and made a reader announce "Shrink, pressed". */
.ctl[aria-pressed="true"] { color: var(--paper); border-color: var(--mute); background: var(--panel2); }

/* every interactive thing gets a visible focus ring — the module has real keyboard features */
:is(button, a, input, select, [tabindex]):focus-visible { outline: none; box-shadow: 0 0 0 2px var(--focus-ring); }





/* ---------------- table block ---------------- */
.blk.table { align-items: flex-start; }
.tblwrap { position: relative; flex: 1; min-width: 0; padding-inline-end: 26px; padding-bottom: 26px; }
/* Nothing scrolls sideways at page level (constraint 4): a wide table scrolls
   inside its own container, exactly as the board's table does. */
.tblscroll { overflow-x: auto; overscroll-behavior-x: contain; border: 1px solid var(--line); border-radius: 10px; }
.btable { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px; }
/* MIDDLE, NOT TOP. CEO, 2026-08-27: "the dots on the left of the block needs to
   always be vertically centered to the full hight of the block". A cell
   stretched by a taller neighbour used to pin its lone block to the top edge,
   dots and all — measured 30px above the cell's middle. Centring the CELL is
   what centres them: a lone block sits mid-cell, and a cell holding several
   blocks still reads top-down because its stack fills the height anyway. */
.btable th, .btable td { border-inline-end: 1px solid var(--line); border-bottom: 1px solid var(--line);
  padding: 0; vertical-align: middle; min-width: 90px; }
.btable tr > *:last-child { border-inline-end: 0; }
.btable tbody tr:last-child > * { border-bottom: 0; }
.btable thead th { background: var(--panel2); font-weight: 600; text-align: start; }
.tcell { padding: 7px 9px; min-height: 20px; outline: none; color: var(--paper); }
.tcell:focus { background: rgb(var(--c-wk) / .1); box-shadow: inset 0 0 0 2px var(--focus-ring); border-radius: 4px; }
.tcell:empty::before { content: attr(data-ph); color: var(--ghost); }
/* The two adders sit outside the frame, so they never cover a cell. They only
   appear on hover, with their space already reserved. */
.tbladd { position: absolute; display: flex; align-items: center; justify-content: center;
  border: 1px dashed var(--line); background: var(--panel); color: var(--ghost);
  border-radius: 6px; cursor: pointer; padding: 0; opacity: 0; transition: opacity .12s; }
.tbladd.col { inset-inline-end: 0; top: 0; bottom: 26px; width: 20px; }
.tbladd.row { inset-inline-start: 0; inset-inline-end: 26px; bottom: 0; height: 20px; }
.blk.table:hover .tbladd, .blk.table:focus-within .tbladd { opacity: 1; }
.tbladd:hover { border-style: solid; border-color: var(--wk); color: var(--wksoft); }


.vcard[draggable="true"] { cursor: grab; }
.vcard.dragging, .cal-item.dragging { opacity: .4; }
.kcol.kover .kbody { outline: 2px dashed rgb(var(--c-wk) / .7); outline-offset: 3px; border-radius: 10px; }
.cal-item[draggable="true"] { cursor: grab; }
.cal-cell.dayover { background: rgb(var(--c-wk) / .18); box-shadow: inset 0 0 0 2px var(--wk); }

/* ---------------- standalone pages ---------------- */
.navempty { font-size: 11px; color: var(--ghost); padding: 4px 8px 6px; line-height: 1.5; }
:root.navrail .navempty { display: none; }
/* ONE PAGE FRAME. A private page and an item opened as a page are the same
   object to a reader — a document in this system — so they are the same shape
   on screen: the same card, the same padding, the same measure, the title in
   the same place. They were drawn by two functions that had each picked their
   own geometry, and the difference was visible the moment anyone opened one
   after the other.
   The 940px measure is the one the full-width peek already uses. A card as wide
   as a 1320px page gives a text line nobody can track back to the start of;
   anything that genuinely needs the width — a table block, a wide embed —
   scrolls inside itself, which is the house rule anyway. */
/* The column menu and the "+" that adds one. Both hold their space and appear
   on hover like every other `.act`, so a header does not resize under the
   pointer. */
.colmenu { flex-shrink: 0; width: 20px; height: 20px; margin-inline-end: 6px;
  display: flex; align-items: center; justify-content: center; border: 0; background: none;
  padding: 0; border-radius: 5px; color: var(--ghost); cursor: pointer;
  opacity: 0; transition: opacity .12s; }
.tt thead th:hover .colmenu, .tt thead th:focus-within .colmenu { opacity: 1; }
.colmenu:hover { background: var(--panel2); color: var(--paper); }
.colplus { width: 22px; height: 22px; display: flex; align-items: center; justify-content: center;
  border: 1px dashed var(--line); background: none; padding: 0; border-radius: 6px;
  color: var(--ghost); cursor: pointer; margin-inline: auto; }
.colplus:hover { border-color: var(--wk); color: var(--wksoft); }
/* A property cell in the table is the same control as in the property list, in
   less room: the label is the column, so the control carries no label of its own. */
.pcell .cin, .pcell .cellin { width: 100%; }
/* A date in a cell is the same borderless field the rest of the table uses. */
.pcell .dfield { background: none; border: 0; }
.pcell .dfield:hover { background: var(--panel2); }
.pcell .tagcell { flex-wrap: nowrap; }
.pcell .person { max-width: 100%; }
.pcell input { width: 100%; }
@media (prefers-reduced-motion: reduce) { .colmenu { transition: none; } }
/* A property row the owner added. The options control keeps its space so the
   row does not resize under the pointer, and appears on hover like every other
   `.act` in this product. */
.pageprops .prow { padding-inline-end: 0; }
.propmore { flex-shrink: 0; opacity: 0; transition: opacity .12s; }
.prow:hover .propmore, .prow:focus-within .propmore { opacity: 1; }
.propcheck { width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: 5px; background: none; color: var(--onaccent);
  cursor: pointer; padding: 0; }
.propcheck.on { background: var(--wk); border-color: var(--wk); }
.plink { font-size: 13px; color: var(--wksoft); }
@media (prefers-reduced-motion: reduce) { .propmore { transition: none; } }
/* A link to something in the portal. Reads as one object, not as text with a
   URL under it — there is nothing to copy here, only somewhere to go. */
.gotolink { display: flex; align-items: center; gap: 9px; width: 100%; text-align: start;
  border: 1px solid var(--line); background: var(--panel2); border-radius: 9px;
  padding: 9px 12px; font: inherit; font-size: 13.5px; color: var(--paper); cursor: pointer; }
.gotolink:hover { border-color: var(--wk); }
.gotoicon { flex-shrink: 0; width: 18px; height: 18px; display: flex; align-items: center;
  justify-content: center; color: var(--wksoft); }
.gotoname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-decoration: underline;
  text-decoration-color: var(--line); text-underline-offset: 3px; }
.linkempty.gone { color: var(--ghost); }
/* The reference names itself in place. It IS the empty state it replaces, so it
   borrows `.linkempty`'s shape rather than inventing a second one — the field
   sits exactly where the name will sit, and nothing moves when it commits. */
.refnaming {
  display: flex; align-items: center; gap: 8px;
  border: 1px dashed var(--line); border-radius: 10px; padding: 12px;
  font-size: 13px; color: var(--ghost);
}
.refnaming:focus-within { border-color: rgb(var(--c-wk) / .6); }
/* Same input vocabulary as every other name typed in place (`.nin`, `.pgtitle`,
   `.pnewname`): transparent until it is used, and the focus ring is the ring. */
.refnamein { flex: 1; min-width: 0; border: 0; background: transparent; font: inherit;
  font-size: 13px; color: var(--paper); padding: 2px 4px; border-radius: 5px; outline: none; }
.refnamein::placeholder { color: var(--ghost); }
.refnamein:focus { background: var(--ink); box-shadow: 0 0 0 2px var(--focus-ring); }
/* The whole body is the target when a page is dragged over it — the link lands
   at the end, so there is nothing to aim at. */
.linkdrop { outline: 2px dashed rgb(var(--c-wk) / .55); outline-offset: -8px; border-radius: 12px; }
/* ONE CARD PER PAGE, AND A BOARD IS A PAGE.
   The frame used to be drawn by whoever happened to be rendering: `.pagewrap`
   drew a card round the head, then `.toolbar` drew the top of a SECOND card and
   `.ttwrap` or `.viewpane` closed it. On a board — and on any board page — the
   two cards met in the middle and the seam read as a heavy line across the
   screen a third of the way down, which no plain page has. It was the two
   systems showing through again: the head belonged to the page, the table
   belonged to the board, and each brought its own box.
   The frame is now one element, `.pagecard`, wrapped round both hosts in the
   markup. Everything inside it — head, properties, view chips, table — is one
   surface, and the parts below the head are the page's content in exactly the
   way blocks are on a document page. */
.pagecard { border: 1px solid var(--line); border-radius: 12px; background: var(--panel); overflow: hidden; }
/* Not a frame any more, just the head's group inside the card. `overflow` goes
   with the border: the card clips, and a second clipping ancestor here would
   silently cut anything the head ever needs to hang outside itself. */
.pagewrap { background: none; }
/* A PAGE IS NOT A PANEL, AND MUST NOT BORROW ITS SCROLLER.
   This body used to carry `.pbody` as well, to pick up the block editor's
   file-drop target. `.pbody` is the peek's scrolling middle: `overflow-y: auto`
   plus `overscroll-behavior: contain`. On a page that is a scroll container the
   height of its own content — so it never scrolls — and `contain` stops the
   wheel from passing through to the page behind it. The result was a page that
   simply did not scroll under the pointer. The drop target is now found by
   either class instead. */
.pagebody { padding: 22px 26px 44px; }
/* The head is followed, OUTSIDE the shell, by the board chrome in the same
   card. Its own bottom padding would then be a band of nothing between the
   properties and the view chips — the same 44px that used to be the last thing
   before a card edge, now stranded mid-card. The gap is the rule's 6px, and the
   toolbar's own padding does the rest.
   This replaced `.nocontent`, which trimmed the same padding for a board page
   whose card held only a trail, a title and properties. That is now this case
   by name, and the old rule had become unreachable: every remaining page has
   content, because `renderBlocks` always draws at least an "Add a block". */
.pagebody.contentbelow { padding-bottom: 0; }
/* No cap. A measure of 940 was a reading decision, and on a wide screen it paid
   for it with two empty bands inside the card — the page looked like it had
   been laid out for a narrower window and left there. The card is already
   bounded by `.wrap` at 1320px, which is where line length is decided; inside
   it, the content uses the width it is given. */
.pagewrap .inner { max-width: none; }
/* The head sits inside that column, not across the card, so the title starts on
   the same line as the text under it. Its rule is the top of the document. */
.pagehead { display: flex; align-items: center; gap: 8px; min-height: 30px; margin-bottom: 10px; }
/* ONE TITLE ROW FOR EVERY PAGE: icon, then title, then whatever that kind of
   page puts beside it. The icon used to sit on a line of its own above the
   title on a private page and be absent altogether on an item page — two
   answers to the same question, which is the thing being fixed here.
   The gap below is 22px because a title with the content pressed against it
   reads as a label on the first block rather than as the name of the page. */
/* The title row and the line under it are one group, and the air below the page
   title is the GROUP's margin. Put it on either child and a page with a
   subtitle would open differently from a page without one. */
.pagetop { margin-bottom: 22px; }
/* ---------------- the cover and the photo (5.106) ---------------- */
/* The cover bleeds to the card's own edges — it is a banner, not a figure in
   the text column — by walking back over .pagebody's padding. Fixed height:
   content never decides geometry, so a page with a cover starts at the same
   place whatever the image is. */
.pagecover {
  height: 190px;
  margin: -22px -26px 18px;
  background-size: cover; background-position: center;
  border-start-start-radius: inherit; border-start-end-radius: inherit;
}
.photorow { display: flex; align-items: flex-start; gap: 22px; min-width: 0;
  /* Air between the head and the body — it measured 2px (CEO, 2026-08-27:
     "there needs to be more space between the head and the body"). */
  margin-block-end: 26px; }
/* THE BODY SPANS TO THE IMAGE'S EDGE (CEO: "the image should be alined with
   the body elements .. extending the body elements to match the image .. the
   width"). Block text used to start a grip-gutter to the right of the photo;
   the gutter now lives in the card's padding, so content and photo share one
   left edge. Desktop only — the phone's padding is too thin to hold a gutter.
   Direct child of .inner only, so the bodies inside table cells keep their
   own geometry. */
@media (min-width: 768px) {
  .inner > [data-blocks] { margin-inline-start: -24px; }
}
.photocol { flex: 1; min-width: 0; }
/* A portrait, not an icon: large enough to be somebody, cropped rather than
   squashed. It spans the name and the properties beside it (his words). */
.pagephoto { flex-shrink: 0; }
.pagephoto img {
  display: block; width: 128px; height: 128px; object-fit: cover;
  border-radius: 14px; border: 1px solid var(--line);
}
/* A cover above a photo: the photo rides up over the banner's lower edge, the
   way every profile page people know does it — identity in front of scenery. */
.pagecover + .pagehead + .photorow .pagephoto { margin-block-start: -58px; }
.pagecover + .pagehead + .photorow .pagephoto img { box-shadow: var(--shadow-card); }
@media (max-width: 767.98px) {
  .pagecover { height: 120px; margin: -16px -12px 14px; }
  .pagephoto img { width: 96px; height: 96px; }
  .pagecover + .pagehead + .photorow .pagephoto { margin-block-start: -44px; }
}
.pagetop .itemhead { margin-bottom: 0; align-items: center; gap: 10px; }
.pagesub { margin: 4px 0 0; font-size: 12px; color: var(--ghost); }
/* The editable form. It sits in the layout whether or not it has anything in it
   — a line that appeared only once written would shift the whole page down the
   first time somebody used it. Borderless until hovered or focused, so an empty
   subtitle reads as space rather than as an unfilled form. */
.pgsub { display: block; width: 100%; margin: 4px 0 0; padding: 2px 6px;
  font: inherit; font-size: 12px; line-height: 1.5; color: var(--ghost);
  background: transparent; border: 1px solid transparent; border-radius: 6px; }
.pgsub::placeholder { color: var(--ghost); opacity: .55; }
.pgsub:hover { background: var(--panel2); }
.pgsub:focus { background: var(--ink); border-color: transparent; outline: 0;
  box-shadow: 0 0 0 2px var(--focus-ring); color: var(--paper); }
.pageicon { flex-shrink: 0; width: 32px; height: 32px; display: flex; align-items: center;
  justify-content: center; border: 0; background: none; padding: 0; border-radius: 8px;
  color: var(--mute); cursor: pointer; }
.pageicon:hover { background: var(--panel2); color: var(--paper); }
.pageicon svg { width: 22px; height: 22px; }
/* The peek's title row is top-aligned (its pen and chip hang off the first
   line), so the glyph is nudged onto that line rather than the box's top. */
.peek .pageicon { width: 26px; height: 26px; margin-top: 1px; }
.peek .pageicon svg { width: 18px; height: 18px; }
.pagewrap .itemhead h2 { font-size: 26px; }
.pgtitle { flex: 1; min-width: 0; border: 0; background: none; font: inherit; font-size: 26px;
  font-weight: 600; letter-spacing: -.02em; color: var(--paper); padding: 2px 6px; border-radius: 8px;
  outline: none; margin: 0; margin-inline: -6px 0; }
.pgtitle:hover { background: var(--panel2); }
.pgtitle:focus { background: var(--ink); box-shadow: 0 0 0 2px var(--focus-ring); }
.pgtitle::placeholder { color: var(--ghost); font-style: italic; }
.pgtitle.ro:hover { background: none; }
.pgfrom { font-size: 11.5px; color: var(--ghost); }
/* Read-only where it is read-only: the state is visible rather than discovered
   by typing into something that will not take it. */
.pagebody.locked { opacity: .8; pointer-events: none; }

/* ---------------- the portal nav ----------------
 * Anchored to the edge of the screen rather than to the page's centred column,
 * because it is not part of this board — it is the way between the parts of the
 * portal. Everything else is inset past it, including the top bar, so the nav
 * reads as the frame and the board reads as what is inside it.
 *
 * PortalNav.tsx in the real app is the same three presentations: a labelled
 * column, an icon rail, a bottom bar on a phone. One difference worth carrying
 * across deliberately — it renders NO chrome at 0 or 1 destination, on the
 * grounds that navigation between one place is decoration. Here it is drawn at
 * one on purpose, so the shape is visible while the second module is still to
 * come. When it lands, this behaves as PortalNav already does.
 */
/* The person's own width, when they have set one (5.43). The rail and the phone
   rules below are more specific, so folding still overrides whatever was
   dragged — a width is a preference for the expanded panel, not a way to defeat
   the two presentations that exist because the screen is narrow. */
/* The floor here is `NAV_W.min` in nav.js and moves with it — 176px since
   5.130, which is what the full logo needs. The clamp exists so a stale or
   hand-edited preference can never produce a panel too narrow to read. */
:root { --navw: clamp(176px, var(--navw-user, 208px), 420px); }
body { padding-inline-start: var(--navw); }
.appnav {
  position: fixed; inset-block: 0; inset-inline-start: 0; z-index: 70;
  width: var(--navw); display: flex; flex-direction: column; gap: 2px;
  padding: 12px 10px; background: var(--panel); border-inline-end: 1px solid var(--line);
  overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain;
}
/* One height in both presentations. Expanded it is a row, folded it is a stack,
   and left to size themselves the two differ by about 25px — so every row below
   sat at a different height depending on the fold, which reads as the whole nav
   twitching when it is toggled. */
/* The mark does not move. Its box is the same width, at the same offset, in both
   presentations — 5px in from a nav that keeps its 10px padding either way — so
   folding the panel changes what is beside the mark and never the mark itself.
   Its centre also lines up with the centre of the icons below it. */
/* THE EDGE IS THE CONTROL, AND IT IS BIGGER THAN IT LOOKS.
   Seven pixels of target for a one-pixel line: the border reads as a hairline,
   but nobody can reliably hit a hairline. It sits outside the nav rather than
   inside it, because the nav scrolls and an absolutely-positioned child would
   scroll away with the tree — and be clipped by its overflow-x anyway. */
.navgrip {
  position: fixed; inset-block: 0; inset-inline-start: calc(var(--navw) - 3px);
  width: 7px; z-index: 71; cursor: col-resize; touch-action: none;
}
.navgrip::after {
  content: ''; position: absolute; inset-block: 0; inset-inline-start: 3px; width: 1px;
  background: var(--wk); opacity: 0; transition: opacity .12s;
}
.navgrip:hover::after, .navgrip:focus-visible::after, .navgrip.resizing::after { opacity: 1; }
.navgrip:focus-visible { outline: none; }
/* Nothing to resize when the width is not the reader's to choose. */
:root.navrail .navgrip { display: none; }
@media (prefers-reduced-motion: reduce) { .navgrip::after { transition: none; } }
@media (max-width: 767.98px) { .navgrip { display: none; } }
/* Pinned, because the tree can now make this column far taller than the screen
   (5.41). The mark is also the control that unfolds the rail — scrolling it out
   of reach would hide the only way back. */
.navbrand { display: flex; align-items: center; gap: 8px; padding: 0 5px;
  min-height: 64px; flex-shrink: 0;
  position: sticky; top: -12px; z-index: 1; background: var(--panel); }
/* THE ASSET ARRIVED (5.130), and this is where the placeholder note said it
   would go. The invented "A" and then the pale apostrophe on a neutral tile
   are both gone; the CEO's own two files are drawn here instead. They carry
   `currentColor`, so the mark is `--paper` on the dark ground and the same
   token's dark value on the light one — the white he supplied would have
   disappeared on light, and a hex fill is not something anything here may
   carry. globals.css's rule still holds: a pale mark on the panel, never an
   accent, and no module's colour standing in for the holding's. */
.navmark { flex-shrink: 0; width: 30px; height: 30px; border-radius: 8px; padding: 0;
  background: none; border: 0;
  color: var(--paper); display: flex; align-items: center; justify-content: center; }
button.navmark { cursor: pointer; }
button.navmark:hover { background: var(--panel2); color: var(--wksoft); }
.brandmark { width: 100%; height: 100%; display: block; }
/* THE FULL LOGO SETS THE PANEL'S NARROWEST WIDTH (his instruction). Its box is
   a fixed HEIGHT with the width following the artwork's own proportions, so it
   is never stretched; `max-width: 100%` and `min-width: 0` mean that if a
   window is somehow narrower than the minimum allows, it scales down rather
   than being cut in half. `NAV_W.min` in nav.js is set from this height —
   the two are commented at each other. */
.navbrandfull { display: flex; align-items: center; min-width: 0; flex: 1; color: var(--paper); }
.brandfull { height: 30px; width: auto; max-width: 100%; display: block; }
/* A fixed box, so the word and the rule that replaces it on the rail occupy the
   same height and nothing below them moves when the nav is folded. */
/* EACH FOLDER AND ITS CONTENTS, AS ONE THING (5.131 — CEO, 2026-08-29:
   "clearly indicates each folder and its content"). He offered a filled box
   like the current page's, an outline, or anything that belongs here; it is
   an OUTLINE, and the reason is a rule already written into this file: the
   filled pill is the one fill in this column and the only answer to "where
   am I" (see `.navitem.on`). A second fill wrapping five groups would make
   the pill compete with its own container. A hairline says the same thing
   and takes nothing from it — and it is the shape this product already uses
   for a container (`.prevwrap`, the cards): 1px of `--line`, radius 10.
   The heading keeps its own hover inside the box, so the group reads as a
   box and the heading still reads as a control. */
.navfoldergroup { border: 1px solid var(--line); border-radius: 10px;
  padding: 2px 4px 4px; margin-block-end: 8px; }
/* A shut folder is a bar, not an empty frame: the box shrinks to the
   heading and loses the padding that would leave a hollow strip below it. */
.navfoldergroup.shut { padding-block-end: 2px; }
/* The heading's own top margin belonged to a flat list; inside a box the
   box's padding is the spacing, and the margin would push it off its own
   top edge. */
.navfoldergroup .navgroup { margin-block-start: 0; }
/* THE FOLDER HEADING IS A ROW, NOT A CAPTION (5.127). It was 10px ghost caps
   starting in the icons' column, floating between rows a third larger than
   itself — "they look lost". Now it shares the rows' grid: the same twisty
   gutter, a glyph in the same 18px box, and the name at the same offset as
   every name below it. Brighter than the rows on purpose — a heading that is
   dimmer than its contents reads as disabled — and it takes a hover, because
   since 5.125 it IS a control: toggle, drag, right-click. The space above
   each one is a MARGIN rather than padding, so the hover fills the row and
   not the gap. */
/* The offsets are the ROW's, written out rather than left to a flex gap:
   a row puts its icon 5px past the twisty (2px row gap + 3px cell padding)
   and its name 7px past the icon, so the heading does the same and the two
   columns line up to the pixel. `gap: 0` because those margins ARE the gaps. */
.navgroup { display: flex; align-items: center; gap: 0; font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em; color: var(--mute);
  min-height: 30px; padding-inline: 0 8px; margin-block-start: 12px;
  border-radius: 8px; box-sizing: border-box; flex-shrink: 0; }
.navgroup:first-of-type { margin-block-start: 0; }
.navfolder:hover { background: var(--panel2); color: var(--paper); }
.navfolder .folicon { color: var(--ghost); margin-inline-start: 5px; }
.navfolder:hover .folicon { color: var(--wksoft); }
/* For an admin the glyph is a button (5.129) — it must not gain a button's
   box, only a button's cursor and a mark that it can be pressed. */
button.folicon { border: 0; background: none; padding: 0; cursor: pointer; border-radius: 6px; }
button.folicon:hover { background: rgb(var(--c-wk) / .18); color: var(--paper); }
.navfolder .folname { padding-inline-start: 7px; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.navfolder .navinput { margin-inline-start: 7px; }
/* The folder headings (5.125): a twisty like the rows', draggable to arrange
   for yourself, and on the trash a COUNT — a badge only for a real positive
   figure; an empty trash says it is empty by saying nothing. */
.navfolder[draggable="true"] { cursor: grab; }
.navfolder.dragging { opacity: .4; }
/* The NAME clips, never the glyph beside it (which is a span too). */
.navfolder .navinput { font-size: 12.5px; font-weight: 600; letter-spacing: 0; text-transform: none; }
.trashcount { margin-inline-start: auto; flex-shrink: 0; font-size: 9px; font-weight: 700;
  color: var(--mute); border: 1px solid var(--line); border-radius: 999px; padding: 1px 6px;
  letter-spacing: .02em; }
/* The head's word for a trashed page (5.126) — a state, not a control. */
.deadnote { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  color: var(--mute); border: 1px solid var(--line); border-radius: 999px; padding: 4px 10px;
  flex-shrink: 0; }
/* The rows carry no standing button (5.125b — CEO, on a column of RESTORE
   pills: "remove it all togeather") — right-click, long-press and the drag
   out are the affordances, like every other row here. */
.trashrow .navname { color: var(--mute); flex: 1; min-width: 0; font-size: 13px; }
.trashrow[draggable="true"] { cursor: grab; }
.trashrow.dragging { opacity: .4; }
button.navtrashall { display: block; width: 100%; text-align: start; background: none; border: 0;
  cursor: pointer; font: inherit; font-size: 11px; color: var(--ghost); }
button.navtrashall:hover { color: var(--paper); }
:root.navrail .trashcount, :root.navrail .trashrow { display: none; }
/* `auto` on the first of them pushes the group to the end; a second control
   beside it must not push again or the two separate across the heading. */
.navadd + .navadd { margin-inline-start: 0; }
.navadd { margin-inline-start: auto; width: 22px; height: 22px; display: flex; align-items: center;
  justify-content: center; border: 0; background: none; padding: 0; border-radius: 6px;
  color: var(--ghost); cursor: pointer; }
.navadd:hover { background: var(--panel2); color: var(--paper); }
/* The share control on a folder's heading (5.133). It is a `.navadd` like the
   plus beside it — same box, same hover — with the COUNT of people riding on
   it when there are any, so the heading answers "who can see this" without
   opening anything. A badge only for a real positive figure: nobody yet
   draws no number, which is how it says nobody. */
.navshare { position: relative; }
.navshare .fshrn { position: absolute; inset-block-start: -1px; inset-inline-end: -2px;
  min-width: 12px; height: 12px; padding-inline: 2px; border-radius: 999px;
  background: var(--panel2); border: 1px solid var(--line); color: var(--mute);
  font-size: 8px; font-weight: 700; line-height: 10px; text-align: center; }
.navshare:hover .fshrn { color: var(--paper); border-color: var(--wk); }
/* The row holds the destination and its menu. The menu only appears on hover or
   focus, with its space reserved, so nothing moves under the pointer. */
.navrow { display: flex; align-items: center; gap: 2px; border-radius: 8px; }
.navrow[draggable="true"] { cursor: grab; }
.navrow.dragging { opacity: .4; }
/* Beside is a line, drawn on the edge it will land against. */

/* A drop that cannot happen, shown while the pointer is over it and again when
   it is released. Colour is not doing the work alone: the cursor becomes
   no-drop, the row shakes, and the reason arrives in words. */
.navrow.refuse { box-shadow: inset 0 0 0 1px rgb(var(--c-bad) / .6); background: rgb(var(--c-bad) / .08); }
@keyframes work-refuse {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-4px); }
  45% { transform: translateX(4px); }
  70% { transform: translateX(-2px); }
}
.navrow.refused { animation: work-refuse 380ms ease-in-out; }
/* THE SAME INSERTION EDGE THE REST OF THE PRODUCT USES. The table, the timeline
   and the block editor all mark "it lands here" with a 2px inset edge; the
   sidebar briefly grew a line-and-dot of its own, which made one list behave
   like a different product. One vocabulary, everywhere. */
/* THE SHELF HEADING TAKES THE SAME EDGE, because it sits in the same column and a
   drag passes straight through it. Between the last board row and the first
   private page there are 38 pixels of heading, and lighting the whole heading
   there broke the line the drag had been drawing the whole way down — one list,
   two different marks, at the one point where the reader is furthest from home.
   Extended onto `.navgroup` rather than given a style of its own: it is the same
   mark, so it is the same declaration. */
.navrow.dropbefore, .navgroup.dropbefore { box-shadow: inset 0 2px 0 var(--wk); }
.navrow.dropafter, .navgroup.dropafter { box-shadow: inset 0 -2px 0 var(--wk); }
.navrow .navitem { flex: 1; min-width: 0; }
.navmore { flex-shrink: 0; width: 24px; height: 24px; display: flex; align-items: center;
  justify-content: center; border: 0; background: none; padding: 0; border-radius: 6px;
  color: var(--ghost); cursor: pointer; opacity: 0; transition: opacity .12s; }
.navrow:hover .navmore, .navrow:focus-within .navmore { opacity: 1; }
.navmore:hover { background: var(--panel2); color: var(--paper); }
:root.navrail .navrow { justify-content: center; }
:root.navrail .navmore, :root.navrail .navadd { display: none; }
/* The twisty keeps its own column so names line up whether or not a row has
   children — nothing shifts sideways as branches open. */
.navtw { flex-shrink: 0; width: 15px; height: 22px; display: flex; align-items: center;
  justify-content: center; border: 0; background: none; padding: 0; border-radius: 4px;
  color: var(--ghost); cursor: pointer; }
/* The twisty takes its column out of the name's width, so the padding it sits
   beside is given back — otherwise "Projects and Work" starts ellipsising the
   moment the board becomes expandable. */
.navrow .navtw + .navitem { padding-inline-start: 3px; gap: 7px; }
.navtw:hover { background: var(--panel2); color: var(--paper); }
/* Closed points along the reading direction, so it mirrors; open points down,
   which is the same in both. */
[dir='rtl'] .navtw[aria-expanded="false"] svg { transform: scaleX(-1); }
.navtw.blank { pointer-events: none; }
.navrow.navsub { padding-inline-start: var(--nd, 0); }
.navrow.navsub .navitem { font-size: 12.5px; min-height: 30px; padding-block: 5px; }
.navrow.dropinto { box-shadow: inset 0 0 0 1px var(--wk); background: rgb(var(--c-wk) / .12); }
/* `.navgroup.shelfover` used to live here — a wash and a ring across the whole
   heading. Gone, and not replaced: a heading offered as a target now draws the
   same insertion edge every row draws. The refusal below keeps its wash because
   it is a different signal, and one nobody has asked to change. */
.navgroup.refuse { background: rgb(var(--c-bad) / .1); border-radius: 6px;
  box-shadow: inset 0 0 0 1px rgb(var(--c-bad) / .5); }
.navgroup.refused { animation: work-refuse 380ms ease-in-out; }
.tt tbody tr.refuse td { background: rgb(var(--c-bad) / .09); }
.tt tbody tr.refused { animation: work-refuse 380ms ease-in-out; }
/* Not the current page — the branch the current page is in. No fill, so the one
   filled pill in the list is still the answer to "where am I". */
.navitem.inbranch { color: var(--paper); font-weight: 600; }
.navitem.inbranch .navicon { color: var(--wksoft); }
.navname.done { text-decoration: line-through; color: var(--ghost); }
/* The rail has 56px and no names; a tree in it would be a column of identical
   glyphs. The branches fold away with the labels. */
:root.navrail .navsub, :root.navrail .navtw { display: none; }
.navitem {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: start;
  border: 0; background: none; font: inherit; font-size: 13px; color: var(--mute);
  padding: 8px; border-radius: 8px; cursor: pointer; min-height: 36px; text-decoration: none;
}
.navitem:hover { background: var(--panel2); color: var(--paper); }
/* Current is a filled pill and nothing else. An inset rule on a rounded box
   leaves a curved sliver at the corners, which reads as a rendering fault rather
   than as emphasis. Colour is not carrying the state alone — aria-current says
   it, and the weight changes with it. */
.navitem.on { background: var(--panel2); color: var(--paper); font-weight: 600; }
.navicon { flex-shrink: 0; width: 18px; height: 18px; display: flex; align-items: center; justify-content: center; color: var(--ghost); }
.navitem.on .navicon { color: var(--wksoft); }
.navname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The row being named. It is the row, not a dialog over it: same height, same
   icon, same indent, so the name is edited where the name lives and the list
   does not move under the pointer when the field comes and goes. */
.navitem.renaming { cursor: default; background: var(--panel2); }
.navname.navinput {
  flex: 1; min-width: 0; border: 0; background: var(--ink); color: var(--paper);
  font: inherit; font-size: 13px; padding: 3px 6px; margin-inline: -6px 0;
  border-radius: 6px; outline: none; box-shadow: 0 0 0 2px var(--focus-ring);
}
.navsoon { margin-inline-start: auto; font-size: 9.5px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--ghost); border: 1px solid var(--line); border-radius: 999px; padding: 1px 6px; }
.navfoot { margin-top: auto; padding-top: 10px; font-size: 10.5px; color: var(--ghost); }

/* THE RAIL IS A CLASS, NOT A BREAKPOINT.
   It is reached two ways — someone folds it, or the screen is too narrow to
   carry names — and both produce the same thing, so both set the same class.
   Written as a media query as well it would be two definitions of one state,
   and they would drift. The name becomes the tooltip AND the accessible label;
   a title attribute is not a label. */
:root.navrail { --navw: 56px; }
:root.navrail .appnav { align-items: stretch; }
/* `.navwordmark` was the typed word beside the placeholder; it went with the
   real logo (5.130), and the rail draws the square mark instead. */
:root.navrail .navname, :root.navrail .navbrandfull,
:root.navrail .navsoon, :root.navrail .navfoot { display: none; }
/* The heading keeps its box on the rail and loses its word: a group boundary
   still has to be visible when there is more than one group, and hiding the row
   outright moved every destination 38px up the moment the nav was folded. */
/* ON THE RAIL THE GLYPH IS WHAT IS LEFT (5.127): the name goes, the folder's
   own icon stays and marks the group — which is what a rail is for. The
   hairline that used to stand in for the whole heading is gone with it, since
   there is now something real to see. */
/* No boxes on the rail (5.131): at 56px a border around each group is four
   nested frames and no room for any of them. A HAIRLINE between groups says
   the same thing in the space there is — and it has to say something, because
   with the names gone a folder and a page were the same shape (the CEO, on
   the two panels side by side: "why is their styling different"). */
/* `border-radius: 0` matters: the box's 10px radius curved the ends of this
   hairline into little hooks, which read as fragments of a frame rather than
   as a rule between two groups. */
:root.navrail .navfoldergroup { border: 0; border-radius: 0; padding: 0; margin-block-end: 4px; }
:root.navrail .navfoldergroup + .navfoldergroup {
  border-block-start: 1px solid var(--line); padding-block-start: 6px; margin-block-start: 6px; }
/* The folder's glyph IS the toggle on the rail, so it takes a control's box
   and a control's hover — the destinations below it are buttons too, and a
   heading that looked like plain art would be the one thing there that does
   not answer a press. */
:root.navrail .navfolder .folicon { width: 36px; height: 32px; margin-inline: auto;
  border-radius: 8px; color: var(--ghost); }
:root.navrail .navfolder .folicon:hover { background: var(--panel2); color: var(--wksoft); }
:root.navrail .navgroup { justify-content: center; padding-inline: 0; margin-block-start: 8px; }
:root.navrail .navgroup .folname, :root.navrail .navgroup .navinput,
:root.navrail .navgroup .trashcount { display: none; }
:root.navrail .navfolder .folicon { margin-inline-start: 0; }
:root.navrail .navitem { justify-content: center; padding: 9px 0; width: 36px; margin-inline: auto; }
:root.navrail .navbrand { justify-content: flex-start; }
.navfold {
  margin-inline-start: auto; flex-shrink: 0; width: 26px; height: 26px;
  display: flex; align-items: center; justify-content: center;
  border: 0; background: none; padding: 0; border-radius: 6px; color: var(--ghost); cursor: pointer;
}
.navfold:hover { background: var(--panel2); color: var(--paper); }
/* No chevron on the rail: the mark is the way back out, so a second control
   pointing the same way would be one too many in 56px. */
:root.navrail .navfold { display: none; }
[dir='rtl'] .navfold svg { transform: scaleX(-1); }
/* PHONE: THE SIDEBAR HIDES ITSELF AND IS REVEALED FROM THE SIDE.
   CEO, 2026-08-24: "it should not be a bottom bar .. it should be an auto hide
   side bar .. it can be dropdown or side reveal".

   WHAT THE BOTTOM BAR GOT WRONG. It put one cell per sidebar row into a grid
   with `grid-auto-columns: 1fr` — unlimited implicit columns — so with ten
   boards the cells ran off the right edge and everything past it was
   unreachable. The comment above it claimed a grid cannot exceed its column
   count, which is true only if the column count is fixed, and it was not.

   A HIDDEN SIDEBAR HAS NO SUCH LIMIT: it is a vertical list, and a vertical list
   gains height instead of overflowing — the argument `PortalNav.tsx` already
   makes for the same problem. It is also the SAME sidebar, with the same tree,
   the same twisties and the same drag, rather than a second navigation that has
   to be kept in step by hand.

   Side reveal rather than a dropdown: the nav is a tree that can be tall, and a
   drawer with its own scroll holds depth where a dropdown hanging off the top
   has to fight the page for height. `inset-block: 0` with the drawer scrolling
   internally is hard constraint 5 — a fixed overlay must fit the viewport.

   Mirrored explicitly for RTL: the drawer leaves from whichever edge it sits
   against, so the transform is stated for both directions rather than assumed. */
@media (max-width: 767.98px) {
  :root { --navw: 0px; }
  body { padding-inline-start: 0; }
  .appnav {
    inset-block: 0; inset-inline-start: 0; inset-inline-end: auto;
    width: min(84vw, 320px); max-width: 320px;
    flex-direction: column; padding: 12px 10px;
    border-inline-end: 1px solid var(--line);
    box-shadow: var(--shadow-card);
    transform: translateX(-101%);
    transition: transform .18s ease;
    visibility: hidden;
  }
  [dir='rtl'] .appnav { transform: translateX(101%); }
  :root.navopen .appnav,
  [dir='rtl'] :root.navopen .appnav { transform: translateX(0); visibility: visible; }
  /* The scrim is what makes it a drawer rather than a panel that has landed on
     the page: a press anywhere outside closes it, which is where people press. */
  .navscrim {
    position: fixed; inset: 0; z-index: 69; background: rgb(var(--c-scrim) / 0.5);
    opacity: 0; pointer-events: none; transition: opacity .18s ease;
  }
  :root.navopen .navscrim { opacity: 1; pointer-events: auto; }
  /* The drawer is the full sidebar, so nothing in it is hidden the way the
     bottom bar hid the brand, the groups and the shelves. */
  .navfold { display: none; }
  :root.navrail .navname, .navname { display: block; }
  :root.navrail .navitem { width: auto; margin-inline: 0; }
  /* Folding is a desktop decision about a column that does not exist here. Left
     to apply, someone who folded the rail on their laptop opened the drawer to
     an unlabelled column of icons. */
  .navitem { min-height: 44px; }
  .navgrip { display: none; }
}
/* The control that reveals it. Desktop has the sidebar in front of it already,
   so this exists only where the sidebar does not. */
.navshow { display: none; }
@media (max-width: 767.98px) {
  .navshow {
    display: inline-flex; align-items: center; justify-content: center;
    width: 40px; height: 40px; flex: none;
    border: 1px solid var(--line); border-radius: 10px;
    background: var(--panel); color: var(--paper);
  }
  .navshow:hover { background: var(--panel2); }
}

/* ---------------- toolbar ---------------- */
/* The toolbar is a fixed set of controls and must not reflow. The sort status
   used to live here and pushed Export onto a second line the moment anyone
   sorted — a row of buttons that moves under the pointer is worse than one that
   is slightly cramped. The status now has its own strip below. */
.toolbar {
  display: flex; flex-wrap: nowrap; align-items: center; gap: 8px;
  padding: 12px;
  overflow-x: auto; overscroll-behavior-x: contain;
}
.toolbar > * { flex-shrink: 0; }
.toolbar .search { flex-shrink: 1; min-width: 130px; }
/* Constraint 5: a fixed panel must fit the viewport, so it is capped and scrolls
   inside itself rather than running off the bottom edge. */
.adminpanel {
  position: fixed; z-index: 80; inset-inline: 0; margin: 0 auto; top: 50%;
  transform: translateY(-50%); width: min(560px, calc(100vw - 32px));
  max-height: calc(100dvh - 48px); display: flex; flex-direction: column;
  border: 1px solid var(--line); border-radius: 16px; background: var(--panel);
  box-shadow: var(--shadow-card); overflow: hidden;
}
.adminbody { overflow-y: auto; padding: 6px 0; }
.admrow { display: flex; align-items: flex-start; gap: 16px; padding: 16px 18px; border-bottom: 1px solid var(--line); }
.admrow:last-child { border-bottom: 0; }
.admrow > div { flex: 1; min-width: 0; }
.admrow .btn { flex-shrink: 0; margin-top: 2px; }
.admname { font-size: 13.5px; font-weight: 600; color: var(--paper); }
.userlist { display: flex; flex-direction: column; gap: 2px; margin: 10px 0; }
.urow { display: flex; align-items: center; gap: 9px; padding: 6px 8px; border-radius: 8px; }
.urow:hover { background: var(--panel2); }
.uname { flex: 1; min-width: 0; font-size: 13px; display: flex; flex-direction: column; overflow: hidden; }
.uemail { font-size: 11px; color: var(--ghost); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.invitebar { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.invitebar input, .invitebar select {
  flex: 1; min-width: 130px; border: 1px solid var(--line); background: var(--ink);
  border-radius: 8px; padding: 7px 9px; color: var(--paper); font: inherit; font-size: 12.5px; outline: none;
}
.invitelist { display: flex; flex-direction: column; gap: 4px; margin-top: 10px; }
.invrow { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; padding: 7px 8px; border: 1px solid var(--line); border-radius: 8px; }
.invwho { font-size: 12px; color: var(--paper); display: flex; align-items: center; gap: 6px; }
.invurl { flex: 1; min-width: 160px; border: 0; background: var(--ink); border-radius: 6px; padding: 6px 8px;
  color: var(--mute); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; outline: none; }
.advadd { margin-top: 12px; }
.advadd summary { font-size: 12px; color: var(--ghost); cursor: pointer; }
.advadd summary:hover { color: var(--paper); }
.newuser { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.newuser input, .newuser select {
  flex: 1; min-width: 110px; border: 1px solid var(--line); background: var(--ink);
  border-radius: 8px; padding: 7px 9px; color: var(--paper); font: inherit; font-size: 12.5px; outline: none;
}
.newuser input:focus, .newuser select:focus { box-shadow: 0 0 0 2px var(--focus-ring); }

/* ---------------- sign in ---------------- */
.authback { position: fixed; inset: 0; z-index: 100; background: var(--ink); }
.authbox {
  position: fixed; z-index: 101; inset-inline: 0; margin: 0 auto; top: 50%;
  transform: translateY(-50%); width: min(360px, calc(100vw - 32px));
  max-height: calc(100dvh - 40px); overflow-y: auto;
  border: 1px solid var(--line); border-radius: 16px; background: var(--panel);
  box-shadow: var(--shadow-card); padding: 24px;
}
.authbox h2 { margin: 0 0 6px; font-size: 18px; font-weight: 600; letter-spacing: -.02em; }
.authbox p { margin: 0 0 16px; font-size: 13px; line-height: 1.6; color: var(--mute); }
.authbox label { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .07em; color: var(--ghost); margin-bottom: 12px; }
.authbox input, .authinput {
  display: block; width: 100%; margin-top: 6px; border: 1px solid var(--line); background: var(--ink);
  border-radius: 8px; padding: 10px 12px; color: var(--paper); font: inherit; font-size: 14px;
  text-transform: none; letter-spacing: normal; outline: none;
}
.authbox input:focus, .authinput:focus { box-shadow: 0 0 0 2px var(--focus-ring); }
.authbox .btn { width: 100%; min-height: 42px; margin-top: 4px; }
.autherr { color: var(--bad); font-size: 12.5px; margin: -6px 0 12px; }
.authnote { font-size: 11.5px; color: var(--ghost); margin: 12px 0 0; line-height: 1.5; }
.admdesc { margin: 5px 0 0; font-size: 12.5px; line-height: 1.6; color: var(--mute); }
.admdesc strong { color: var(--paper); }
.search {
  display: flex; align-items: center; gap: 7px; border: 1px solid var(--line);
  border-radius: 8px; padding: 5px 10px; background: var(--ink); min-width: 190px;
}
.search input { border: 0; background: none; color: var(--paper); font: inherit; font-size: 13px; outline: none; width: 100%; }
.search input::placeholder { color: var(--ghost); }
.stat { font-size: 11px; color: var(--ghost); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ---------------- table ---------------- */
/* No frame of its own — see `.pagecard`. The header band already draws the line
   under the chips: `.tt th` is panel2 with a bottom border, so the table starts
   with an edge whether or not anything is drawn above it. */
.ttwrap { overflow: hidden; }
.ttscroll { overflow-x: auto; overscroll-behavior-x: contain; }
.tt { width: 100%; table-layout: fixed; border-collapse: separate; border-spacing: 0; font-size: 13px; }
.tt th {
  position: sticky; top: 0; z-index: 3; background: var(--panel2); text-align: start; white-space: nowrap;
  padding: 9px 12px; font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .08em; color: var(--ghost); border-bottom: 1px solid var(--line);
}
.tt td { padding: 0 12px; height: 44px; border-bottom: 1px solid var(--line); vertical-align: middle; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tt tbody tr:hover td { background: var(--panel2); }
.tt thead th { padding: 0; }
/* The header height is fixed and the arrow slot is always in the layout, empty
   or not. Drawn only when sorted, a 13px glyph is taller than a 10px uppercase
   line box, so the whole header — and the table under it — stepped down a pixel
   the moment anyone sorted. Reserve the space instead of reacting to it. */
/* The header's single row. Its height is the header's height, fixed, whatever is
   in it — see renderHeaders. */
.thcell { display: flex; align-items: center; height: 34px; min-width: 0; }
.sortbtn {
  display: flex; align-items: center; gap: 5px; flex: 1; min-width: 0; height: 34px;
  border: 0; background: none; cursor: pointer; font: inherit;
  padding: 0 12px; text-align: start; white-space: nowrap; line-height: 1;
  font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .08em;
  color: var(--ghost);
}
/* The label is the only thing allowed to give way. The icon, the arrow (a fixed
   slot, see above) and the options control all keep their size, so a narrow
   column ellipsises the word rather than wrapping or growing. */
.thicon { flex-shrink: 0; display: flex; align-items: center; color: var(--ghost);
  width: 13px; height: 13px; }
.thicon svg { width: 13px; height: 13px; }
.sortbtn.on .thicon { color: var(--wksoft); }
.thlabel { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sortbtn .arrow { flex-shrink: 0; }
.sortbtn:hover { color: var(--paper); background: rgb(var(--c-line) / .5); }
.sortbtn.on { color: var(--wksoft); }
.sortbtn[draggable="true"] { cursor: grab; }
.sortbtn[draggable="true"]:active { cursor: grabbing; }
/* Where the column will land, drawn on the edge it will land against. */
.tt thead th.dropbefore { box-shadow: inset 2px 0 0 var(--wk); }
.tt thead th.dropafter { box-shadow: inset -2px 0 0 var(--wk); }
.tt thead th.coldragging .sortbtn { opacity: .45; }
.tt thead th { position: sticky; top: 0; }
.tt thead th[data-th] { position: sticky; }
.colgrip {
  position: absolute; top: 0; bottom: 0; inset-inline-end: -4px; width: 9px;
  cursor: col-resize; z-index: 6; touch-action: none;
}
.colgrip::after {
  content: ''; position: absolute; top: 7px; bottom: 7px; inset-inline-start: 4px;
  width: 2px; border-radius: 2px; background: transparent; transition: background-color .12s;
}
.colgrip:hover::after, .colgrip.dragging::after { background: var(--wk); }
/* While dragging, the whole document keeps the resize cursor and stops selecting
   text — otherwise the pointer flickers and the drag highlights the table. */
body.resizing { cursor: col-resize; user-select: none; }
body.resizing * { cursor: col-resize !important; }
.sortbtn .arrow { display: flex; align-items: center; justify-content: center; flex-shrink: 0; width: 13px; height: 13px; }
.rolechip {
  font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  border-radius: 999px; padding: 2px 6px; background: var(--panel2); color: var(--ghost);
}
.rolechip.adm { background: rgb(var(--c-wk) / .18); color: var(--wksoft); }
/* ONE SELECTED LOOK, FOR EVERY KIND OF THING.
   A selected task, a selected sidebar page and a selected block are the same
   state, so they carry the same wash and the same edge. Written once: changing
   how "selected" looks changes it in all three places, and there is no way for
   one surface to drift into its own idea of it. The wash is the work accent at
   10%, which clears its contrast floor on both grounds; the inline edge is what
   makes a run of them read as one block rather than three stripes. */
.tt tbody tr.sel td,
.navrow.sel,
.blk.sel { background: rgb(var(--c-wk) / .1); }
.navrow.sel, .blk.sel { border-radius: 6px; box-shadow: inset 2px 0 0 var(--wk); }
.tt tbody tr.sel td:first-child { box-shadow: inset 2px 0 0 var(--wk); }
/* A block is inline-padded by its indent, so the wash needs to reach past the
   grip rather than stopping at the text. */
.blk.sel { outline: 1px solid rgb(var(--c-wk) / .35); outline-offset: 1px; }

/* WHAT IS SELECTED AND WHAT CAN BE DONE WITH IT.
   Fixed inside the viewport, never taller than it (constraint 5), and it cannot
   scroll the page sideways because it is a flex row that shrinks. Above the
   phone's bottom navigation, so it never buries the way out. */
.selbar {
  position: fixed; inset-inline: 12px; bottom: 12px; z-index: 60;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  max-width: 640px; margin-inline: auto;
  padding: 8px 10px; border-radius: 12px;
  background: var(--panel2); border: 1px solid var(--line);
  box-shadow: 0 8px 30px rgb(var(--c-scrim) / .45);
  animation: work-fade-in .12s ease-out;
}
.selbar .selcount { font-size: 12.5px; font-weight: 600; color: var(--paper); }
@media (prefers-reduced-motion: reduce) { .selbar { animation: none; } }
@media (max-width: 767.98px) {
  /* Clear of the bottom bar, which is 62px plus the safe area. */
  .selbar { bottom: calc(70px + env(safe-area-inset-bottom)); inset-inline: 8px; }
}

/* pinned name column, so depth stays legible however far the table scrolls */
.tt .name { position: sticky; inset-inline-start: 0; z-index: 2; background: var(--panel); }
/* The footer sticks to the bottom of the scroll box the way the header sticks to
   the top. Its name cell is sticky on both axes at once — bottom and inline
   start — so it stays put whichever way the table is scrolled. */
.tt tfoot td { position: sticky; bottom: 0; z-index: 4; background: var(--panel);
  border-top: 1px solid var(--line); border-bottom: 0; }
.tt tfoot td.name { z-index: 5; }
/* The row sets --pad from its depth. Unchanged geometry — this used to be an
   inline padding on the cell; it moved to the row so the phone layout can
   indent both of the row's lines by the same amount. */
.ncell { padding-inline-start: var(--pad, 12px); }
.tt tbody tr:hover .name { background: var(--panel2); }
.tt tbody tr.sel .name { background: rgb(var(--c-panel2)); }
.tt th.name { z-index: 4; background: var(--panel2); }
.tt .name::after { content: ''; position: absolute; inset-block: 0; inset-inline-end: 0; width: 1px; background: var(--line); }

.ncell { display: flex; align-items: center; gap: 6px; min-width: 0; }
/* The row itself is grabbed now, so the whole row dims while it is in flight. */
tr.dragging td { opacity: .4; }
tr[draggable="true"], .tlrow[draggable="true"] { cursor: grab; }
tr.dragging, .tlrow.dragging { cursor: grabbing; }
.tlrow.dragging { opacity: .4; }

/* Beside is a line; inside is a filled row. Two different shapes so the answer
   to "what will this do" never depends on reading a 2px edge. */
.tt tbody tr.dropbefore td { box-shadow: inset 0 2px 0 var(--wk); }
.tt tbody tr.dropafter td { box-shadow: inset 0 -2px 0 var(--wk); }
.tt tbody tr.dropinto td { background: rgb(var(--c-wk) / .16); }
.tt tbody tr.dropinto .name { box-shadow: inset 3px 0 0 var(--wk); }
/* the same three answers in the timeline, drawn the same three ways */
.tlrow.dropbefore { box-shadow: inset 0 2px 0 var(--wk); }
.tlrow.dropafter { box-shadow: inset 0 -2px 0 var(--wk); }
.tlrow.dropinto { background: rgb(var(--c-wk) / .16); box-shadow: inset 3px 0 0 var(--wk); }
.tw { flex-shrink: 0; width: 18px; height: 18px; display: flex; align-items: center; justify-content: center; border: 0; background: none; padding: 0; color: var(--ghost); cursor: pointer; border-radius: 4px; }
.tw:hover { background: var(--line); color: var(--paper); }
.tw.blank { cursor: default; }
.tw.blank:hover { background: none; }
/* The twisty is a real touch target on a phone, not a 18px desktop one. */
@media (max-width: 767.98px) {
  .tt .tw { width: 34px; height: 40px; margin-inline-start: -6px; }
  .tt .tw svg { width: 15px; height: 15px; }
}
/* The glyph sits in a fixed box and the SVG is forced to fill it.
   `ICO` entries were authored at 13, 14 and 15px, so an icon carrying its own
   width made the name shift sideways the moment someone changed it. Same rule as
   the sort arrow: reserve the space, never let the content set the geometry. */
.gl {
  flex-shrink: 0; width: 22px; height: 22px; margin-inline-start: -3px;
  color: var(--ghost); display: flex; align-items: center; justify-content: center;
  border: 0; background: none; padding: 0; border-radius: 6px; cursor: pointer;
}
.gl svg { width: 15px; height: 15px; }
.gl:hover { color: var(--paper); background: rgb(var(--c-line) / .6); }
.migrid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 2px; padding: 4px 3px; }
.miicon {
  display: flex; align-items: center; justify-content: center; height: 32px;
  border: 0; background: none; color: var(--mute); border-radius: 7px; cursor: pointer;
}
.miicon svg { width: 16px; height: 16px; }
.mhint { padding: 0 9px 6px; font-size: 11px; line-height: 1.5; color: var(--ghost); max-width: 280px; }
.menu textarea {
  width: 100%; min-width: 250px; border: 0; background: var(--ink); border-radius: 6px;
  padding: 8px; color: var(--paper); font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11.5px; line-height: 1.5; outline: none; resize: vertical;
}
.miicon:hover { background: var(--panel2); color: var(--paper); }
.miicon.on { background: rgb(var(--c-wk) / .18); color: var(--wksoft); box-shadow: inset 0 0 0 1px rgb(var(--c-wk) / .5); }

/* the name is always an input, so typing never rebuilds the row under the caret */
.nin {
  flex: 1; min-width: 0; border: 0; background: none; font: inherit; font-size: 13px;
  color: var(--paper); padding: 5px 6px; border-radius: 6px; outline: none;
}
.nin:hover { background: rgb(var(--c-line) / .5); }
.nin:focus { background: var(--ink); box-shadow: 0 0 0 2px var(--focus-ring); }
.nin.done { color: var(--ghost); text-decoration: line-through; }
.nin::placeholder { color: var(--ghost); font-style: italic; }

/* The name OPENS the item. Renaming is a separate, explicit act behind the
   pencil — clicking a title and landing in a text field is the wrong default
   when the title is the way into the thing. */
.nlink {
  flex: 1; min-width: 0; text-align: start; border: 0; background: none;
  font: inherit; font-size: 13px; color: var(--paper); padding: 5px 6px;
  border-radius: 6px; cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nlink:hover { background: rgb(var(--c-line) / .5); text-decoration: underline; text-underline-offset: 2px; }
.nlink.done { color: var(--ghost); text-decoration: line-through; }
.nlink.untitled { color: var(--ghost); font-style: italic; }
.pen { opacity: 0; transition: opacity .12s; flex-shrink: 0; }
tr:hover .pen, tr:focus-within .pen,
.tlrow:hover .pen, .tlrow:focus-within .pen { opacity: 1; }



/* ---------------- references ---------------- */
.minote { display: block; font-size: 10.5px; color: var(--ghost); margin-top: 1px; }
/* A select's choice drawn as the chip it becomes (menu `tone` rows). */
.mi .mtone { display: inline-flex; align-items: center; gap: 5px;
  padding: 1.5px 9px; border-radius: 999px; font-size: 12px; font-weight: 600; }
.refcard { border: 1px solid var(--line); border-radius: 12px; background: var(--panel2); overflow: hidden; }
/* Inherited is drawn quieter than owned, the same 55% the inherited property
   fields use, so "this came from somewhere else" reads the same way everywhere. */
.refcard.inh { background: transparent; }
.refcard.missing { border-style: dashed; }
.refhead { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-bottom: 1px solid var(--line); }
.refcard.missing .refhead { border-bottom: 0; }
.refhead .linkicon { color: var(--ghost); display: flex; }
.refname { font-size: 12.5px; font-weight: 600; color: var(--paper); }
.refsrc { display: inline-flex; align-items: center; gap: 3px; border: 1px solid var(--line);
  background: none; border-radius: 999px; padding: 1px 8px; font: inherit; font-size: 10.5px;
  color: var(--ghost); cursor: pointer; }
.refsrc:hover { color: var(--paper); border-color: var(--wk); }
.refsrc.as-text { border: 0; padding: 0; cursor: default; }
.refedit { border: 0; background: none; font: inherit; font-size: 11px; color: var(--ghost);
  cursor: pointer; padding: 2px 6px; border-radius: 6px; }
.refedit:hover { color: var(--wksoft); background: rgb(var(--c-wk) / .1); }
/* THE PEN IS THE WORD. CEO, 2026-08-26: "i dont want the edit button to be here
   .. just a pen or pencel icon will do". Sized to the same box the word occupied
   so the row's geometry does not change, and it carries an `aria-label` because a
   `title` is not a label. */
.refedit.pen { display: inline-flex; align-items: center; justify-content: center;
  padding: 2px 5px; }
.refedit.pen svg { display: block; }
.refbody { padding: 2px 4px 6px; }
.refbody.plain { padding: 10px 12px; font-size: 13px; color: var(--paper); display: flex; align-items: center; gap: 8px; }
/* Read-only until someone says which way they mean to edit it. Not hidden — a
   value you can see but not touch is the honest state for something owned by a
   page above this one. */
/* Locked means "not yours to type in yet", not "invisible to the pointer".
   `pointer-events: none` meant a press landed on the card behind it and nothing
   happened at all — no caret, no reason, no refusal. The body takes the press and
   answers it; the blocks inside stay untypeable. */
.refbody.locked { opacity: .72; cursor: pointer; }
.refbody.locked .btext, .refbody.locked .bgrip,
.refbody.locked button, .refbody.locked input { pointer-events: none; }
.refempty { padding: 12px; display: flex; align-items: center; gap: 12px; }
.refempty p { margin: 0; font-size: 12px; color: var(--ghost); }
.reflog { padding: 6px 10px; border-top: 1px solid var(--line); font-size: 10.5px; color: var(--ghost); }

/* ---------------- Dropbox preview and quick look ---------------- */
.dbxframe { position: relative; min-height: 260px; border-top: 1px solid var(--line); background: var(--panel2); }
.dbxframe iframe { width: 100%; height: 100%; min-height: 260px; border: 0; display: block; }
.dbxwait { display: flex; align-items: center; justify-content: center; height: 260px;
  font-size: 12px; color: var(--ghost); }
/* Nothing to show is worth one line, not a screen of empty. The card above still
   opens the folder, so this is a note, not a failure. */
.dbxframe.dbxoff { min-height: 0; }
.dbxmsg { display: block; padding: 12px 14px; font-size: 11.5px; color: var(--ghost); }
.pdfhead .linkmeta { min-width: 0; flex: 1; }
.pdfhead .linkname, .pdfhead .linkhost { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qlbox {
  position: fixed; z-index: 82; inset: 24px; margin: auto;
  width: min(1100px, calc(100vw - 48px)); height: min(760px, calc(100dvh - 48px));
  display: flex; flex-direction: column; border: 1px solid var(--line); border-radius: 16px;
  background: var(--panel); box-shadow: var(--shadow-card); overflow: hidden;
}
.qlbody { flex: 1; min-height: 0; display: flex; }
.qlbody .dbxframe { flex: 1; border-top: 0; }
.qlbody .dbxframe iframe { height: 100%; }
.qlframe { flex: 1; width: 100%; border: 0; background: var(--panel2); }
@media (max-width: 767.98px) { .qlbox { inset: 8px; width: calc(100vw - 16px); height: calc(100dvh - 16px); } }

/* ---------------- templates ---------------- */
.tkey { margin-inline-start: auto; font-size: 10px; color: var(--ghost); border: 1px solid var(--line);
  border-radius: 4px; padding: 0 4px; line-height: 15px; flex-shrink: 0; }
.tpl .tname { display: flex; align-items: center; gap: 6px; }
.tplmore { border-style: dashed; }
.tplpanel { width: min(680px, calc(100vw - 32px)); }
.tpledit .adminbody { padding-bottom: 20px; }
.tplsec { padding: 10px 14px 14px; border-top: 1px solid var(--line); }
/* The public log. Three columns that always line up — who, what, when — because
   the question is usually "who touched this" and a ragged left edge makes that
   a search rather than a glance. */
.actlist { list-style: none; margin: 0; padding: 0; }
.actrow { display: grid; grid-template-columns: minmax(84px, 22%) 1fr auto; gap: 10px;
  align-items: baseline; padding: 9px 14px; border-block-end: 1px solid var(--line);
  font-size: 13px; line-height: 1.5; }
.actrow:last-child { border-block-end: 0; }
.actwho { font-weight: 600; color: var(--paper); overflow-wrap: anywhere; }
.actwhat { color: var(--mute); overflow-wrap: anywhere; }
.actwhen { color: var(--ghost); font-size: 11px; white-space: nowrap; }
.actempty { margin: 0; padding: 14px; font-size: 13px; color: var(--mute); }
@media (max-width: 767.98px) {
  /* The columns give way before the content does: who and what stack, and the
     time keeps its own line rather than squeezing the sentence. */
  .actrow { grid-template-columns: 1fr; gap: 2px; }
}
.tplsectitle { display: flex; align-items: center; gap: 10px; font-size: 12px; font-weight: 600;
  color: var(--paper); margin-bottom: 2px; min-height: 28px; }
.tplsectitle .btn { margin-inline-start: auto; }
/* Cards, the same shape the empty page offers, so the shelf and the page someone
   lands on read as one thing rather than a list and a set of buttons. */
.tplgrid { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); margin-top: 8px; }
.tplcard { position: relative; display: flex; border: 1px solid var(--line); border-radius: 12px; background: var(--panel2); }
.tplcard:hover { border-color: rgb(var(--c-wk) / .6); }
.tplgo {
  display: flex; flex-direction: column; gap: 5px; width: 100%; text-align: start;
  justify-content: flex-start;
  border: 0; background: none; font: inherit; padding: 11px 12px; cursor: pointer; border-radius: 12px;
}
/* Stretched link: the whole card is the target, the two controls sit above it. */
.tplgo::after { content: ''; position: absolute; inset: 0; border-radius: 12px; }
.tplcard .tname { padding-inline-end: 56px; display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 500; color: var(--paper); }
.tplcard .tname svg { flex-shrink: 0; color: var(--ghost); }
.tntxt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tpldesc { font-size: 11.5px; color: var(--ghost); line-height: 1.4;
  /* Two lines, then an ellipsis. A card that grows with its description makes
     the row it sits in grow with it, and one long line leaves gaps beside every
     short one. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.tplmeta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: auto; padding-top: 2px; }
.tplowner { font-size: 11px; color: var(--ghost); }
/* Both corner controls keep their space reserved and only fade in, so nothing
   moves under the pointer as a card is approached. */
.tplstar, .tplmenu {
  position: absolute; top: 6px; z-index: 1; width: 26px; height: 26px;
  display: flex; align-items: center; justify-content: center;
  border: 0; background: none; padding: 0; border-radius: 6px; color: var(--ghost); cursor: pointer;
}
.tplstar { inset-inline-end: 34px; opacity: 0; transition: opacity .12s; }
.tplmenu { inset-inline-end: 4px; opacity: 0; transition: opacity .12s; }
.tplcard:hover .tplstar, .tplcard:hover .tplmenu,
.tplcard:focus-within .tplstar, .tplcard:focus-within .tplmenu { opacity: 1; }
.tplstar.on { opacity: 1; color: var(--warn); }
.tplstar.on svg { fill: currentColor; }
.tplstar:hover, .tplmenu:hover { background: rgb(var(--c-line) / .6); color: var(--paper); }
/* The share panel lists people, and a person is a row. */
.tplrow { display: flex; align-items: center; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--line); }
.tplrow:last-child { border-bottom: 0; }
.tplmain { min-width: 0; flex: 1; }
.tplname { font-size: 13px; color: var(--paper); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tplform { display: flex; flex-direction: column; gap: 10px; padding: 12px 14px; }
.tplform label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--mute); }
.tplform input { border: 1px solid var(--line); border-radius: 8px; background: var(--panel2);
  color: var(--paper); font: inherit; font-size: 13px; padding: 7px 9px; }
.tplwhat { border: 1px solid var(--line); border-radius: 10px; padding: 8px 10px; margin: 0; }
.tplwhat legend { font-size: 11px; color: var(--ghost); padding: 0 4px; }
.tplwhat label { flex-direction: row; align-items: center; gap: 8px; font-size: 12.5px; color: var(--paper);
  padding: 3px 0; }
.tplwhat input:disabled + span, .tplwhat label:has(input:disabled) { color: var(--ghost); }
.tpleblocks { padding: 0 14px; }

/* ---------------- the side peek ---------------- */
/* W-14a: the task side-peek slides in and the backdrop fades. Both keyframes
   are the real ones out of globals.css, so this matches the module. A card at
   the inline end is the default because the board behind it is the context you
   came from; expanding is for when the item IS the work. */
.peekback {
  position: fixed; inset: 0; z-index: 70;
  background: rgb(var(--c-scrim) / .6);
}
.peek {
  position: fixed; inset-block: 0; inset-inline-end: 0; z-index: 80;
  min-width: 340px; display: flex; flex-direction: column;
  background: var(--panel); border-inline-start: 1px solid var(--line);
  box-shadow: var(--shadow-card);
}
/* Constraint 5: a fixed overlay must fit the viewport. inset-block:0 with the
   body scrolling internally is what guarantees the foot is always reachable. */
/* .peek.full no longer spans the screen: since 5.124a the stops divide the
   CONTENT AREA, so 100% ENDS AT THE SIDEBAR — the width says so and needs no
   inset override. The phone block below still takes the whole screen. */
/* The draggable edge. Wide enough to hit, invisible until pointed at. */
.peekgrip {
  position: absolute; inset-block: 0; inset-inline-start: -3px; width: 9px;
  cursor: col-resize; z-index: 9; touch-action: none;
}
.peekgrip::after {
  content: ''; position: absolute; inset-block: 0; inset-inline-start: 3px; width: 2px;
  background: transparent; transition: background-color .12s;
}
.peekgrip:hover::after, body.resizing .peekgrip::after { background: var(--wk); }
/* The jump between stops is animated so a snap reads as a snap rather than a
   glitch. WHILE DRAGGING it is off (5.124): the edge follows the pointer now,
   and a transition there is a panel trailing behind the hand — the magnet
   snap mid-drag stays visible as the jump it is. */
@media (prefers-reduced-motion: no-preference) {
  .peek { transition: width .12s ease-out; }
  .peek.enter { transition: none; }
}
body.resizing .peek { transition: none; }
/* The slide belongs to OPENING the peek, not to redrawing it. Left on .peek it
   replayed on every re-render — renaming, checking a box, posting a message —
   and the panel read as reloading itself. It is now only on .enter, which is
   set the first time an item is shown and never again. */
@media (prefers-reduced-motion: no-preference) {
  .peekback.enter { animation: work-fade-in 150ms ease-out; }
  .peek.enter { animation: work-peek-in 200ms ease-out; }
  .peek.full.enter { animation: work-fade-in 150ms ease-out; }
}
/* Icon-only, so the label has to be carried by aria-label — a title attribute
   is not announced by most screen readers, the finding HomeLink already records. */
.iconbtn { padding: 0; gap: 0; width: 30px; height: 30px; justify-content: center; align-items: center; }
.phead .ctl[data-close] { padding: 0; gap: 0; width: 30px; height: 30px; justify-content: center; align-items: center; }
.cornerarrow { display: flex; align-items: center; justify-content: center; }
[dir='rtl'] .cornerarrow { transform: scaleX(-1); }
.phead {
  display: flex; align-items: center; gap: 8px; flex-shrink: 0;
  padding: 10px 14px; border-bottom: 1px solid var(--line); background: var(--panel);
}
.pbody { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: 20px; }
.peek.full .pbody { padding: 28px 20px 60px; }
/* Was capped at 940 for the same reason and dropped for the same one — a
   full-width peek is a page, and pages do not have bands down their sides. */

.crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; font-size: 12px; color: var(--mute); margin-bottom: 16px; }
.crumbs button { border: 0; background: none; font: inherit; font-size: 12px; color: var(--mute); cursor: pointer; padding: 3px 6px; border-radius: 6px; }
.crumbs button:hover { background: var(--panel2); color: var(--paper); }
.crumbs .sep { color: var(--ghost); }
.crumbs .here { color: var(--paper); padding: 3px 6px; }
/* Inline in the page's control row rather than a band of its own, and it gives
   way before the controls do — a long branch must not push Share off the edge. */
.pgcrumbs { margin-bottom: 0; min-width: 0; overflow: hidden; flex-wrap: nowrap; }
.pgcrumbs .root { padding: 3px 6px; color: var(--ghost); }
.pgcrumbs button, .pgcrumbs .here { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.itemhead { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 4px; }
.itemhead h2 { margin: 0; font-size: 21px; font-weight: 600; letter-spacing: -.02em; flex: 1; min-width: 0; }
.peek.full .itemhead h2 { font-size: 26px; }
.itemhead h2.untitled { color: var(--ghost); font-style: italic; }
.titlein {
  flex: 1; min-width: 0; font: inherit; font-size: 21px; font-weight: 600; letter-spacing: -.02em;
  background: var(--ink); border: 0; border-radius: 8px; padding: 2px 8px; color: var(--paper); outline: none;
  box-shadow: 0 0 0 2px var(--focus-ring);
}

.fields { display: grid; gap: 2px; margin: 18px 0; }
.frow { display: flex; align-items: center; gap: 12px; min-height: 36px; }
.fkey { width: 120px; flex-shrink: 0; font-size: 11px; text-transform: uppercase; letter-spacing: .07em; color: var(--ghost); }
.fval { flex: 1; min-width: 0; }
.fval input {
  width: 100%; border: 0; background: none; font: inherit; font-size: 13px; color: var(--paper);
  padding: 6px 8px; border-radius: 6px; outline: none;
}
.fval input:hover { background: var(--panel2); }
.fval input:focus { background: var(--ink); box-shadow: 0 0 0 2px var(--focus-ring); }
.notes {
  width: 100%; min-height: 92px; resize: vertical; border: 1px solid var(--line); background: var(--ink);
  border-radius: 10px; padding: 10px 12px; color: var(--paper); font: inherit; font-size: 13px;
  line-height: 1.65; outline: none;
}
.notes:focus { box-shadow: 0 0 0 2px var(--focus-ring); }
.notes::placeholder { color: var(--ghost); }

.kidlist { border: 1px solid var(--line); border-radius: 12px; overflow: hidden; }
.kid { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border-bottom: 1px solid var(--line); }
.kid:last-child { border-bottom: 0; }
.kid:hover { background: var(--panel2); }
.kid button.klink { flex: 1; min-width: 0; text-align: start; border: 0; background: none; font: inherit; font-size: 13px; color: var(--paper); cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kid button.klink:hover { text-decoration: underline; text-underline-offset: 2px; }
.kid button.klink.done { color: var(--ghost); text-decoration: line-through; }
.kid .kcount { font-size: 10px; color: var(--ghost); flex-shrink: 0; }
.sectitle { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 12px; }
.sectitle h3 { margin: 0; font-size: 15px; font-weight: 600; letter-spacing: -.01em; }
.sectitle .tile { display: flex; height: 28px; width: 28px; flex-shrink: 0; align-items: center; justify-content: center; border-radius: 8px; background: rgb(var(--c-wk) / .14); color: var(--wksoft); }
.sectitle .cnt { border: 1px solid var(--line); border-radius: 999px; background: var(--panel2); padding: 2px 8px; font-size: 11px; font-weight: 600; color: var(--mute); }
.emptykids { padding: 22px 14px; text-align: center; font-size: 13px; color: var(--mute); border: 1px dashed rgb(var(--c-line) / .8); border-radius: 12px; }

/* ---------------- page properties, Notion-style ---------------- */
/* Compact rows, muted labels, the value doing the talking. A property row is
   quiet until you point at it — the filled information is what you came to
   read, not the chrome around it. */
.props { display: grid; gap: 1px; margin: 14px 0 6px; }
.prow { display: flex; align-items: center; gap: 8px; min-height: 32px; border-radius: 6px; padding: 0 4px; }
.prow:hover { background: rgb(var(--c-panel2) / .6); }
.pkey {
  display: flex; align-items: center; gap: 7px; width: 150px; flex-shrink: 0;
  font-size: 12.5px; color: var(--ghost);
}
.pkey svg { flex-shrink: 0; }

/* THE EMPTY PROPERTY ROW. It has to read as an invitation rather than as a
   property with no name, so the label is a field and the row is quiet until it is
   hovered or focused — the same way the waiting block is a line and not a button.
   It holds the same height as a real row, so nothing moves when it becomes one. */
.prow.pnew { opacity: .6; }
.prow.pnew:hover, .prow.pnew:focus-within { opacity: 1; }
.pnewname {
  background: transparent; border: 0; color: var(--paper); font: inherit;
  font-size: 12.5px; padding: 2px 4px; border-radius: 5px; min-width: 0; width: 100%;
}
.pnewname::placeholder { color: var(--ghost); }
/* A column heading being renamed. Same metrics as the label it replaces so the
   header does not change height (reserve space), and the same transparent-then-
   focus-ring treatment as every other in-place name. */
.threname { width: 100%; min-width: 0; border: 0; background: transparent;
  font: inherit; font-size: 11px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--paper); padding: 3px 6px;
  border-radius: 6px; outline: 0; }
.threname:focus { background: var(--ink); box-shadow: 0 0 0 2px var(--focus-ring); }
/* Where a row's properties come from, in the place the naming field would have
   been — so the answer is at the point the question is asked. */
/* Same dashed empty-state shape as `.linkempty`, because it is one: the place a
   property would have been named, saying why it cannot be. */
.pfromboard { margin: 6px 0 0; padding: 12px; font-size: 13px; line-height: 1.5;
  color: var(--ghost); border: 1px dashed var(--line); border-radius: 10px; }
.pnewname:hover { background: var(--panel2); }
.pnewname:focus { background: var(--ink); outline: 0; box-shadow: 0 0 0 2px var(--focus-ring); }
.pnewtype {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; flex-shrink: 0; padding: 0;
  background: transparent; border: 0; border-radius: 5px; color: var(--mute); cursor: pointer;
}
.pnewtype:hover { background: var(--panel2); color: var(--paper); }
.pnewhint { color: var(--ghost); font-size: 11.5px; }


.pval { flex: 1; min-width: 0; }
.pval input {
  width: 100%; border: 0; background: none; font: inherit; font-size: 13px; color: var(--paper);
  padding: 5px 7px; border-radius: 6px; outline: none;
}
.pval input:hover { background: var(--panel2); }
.pval input:focus { background: var(--ink); box-shadow: 0 0 0 2px var(--focus-ring); }
.pval input::placeholder { color: var(--ghost); }

.notesline {
  width: 100%; border: 0; background: none; resize: none; overflow: hidden;
  font: inherit; font-size: 13.5px; line-height: 1.7; color: var(--mute);
  padding: 7px 8px; border-radius: 8px; outline: none; min-height: 34px;
  margin: 2px 0 10px;
}
.notesline:hover { background: rgb(var(--c-panel2) / .6); }
.notesline:focus { background: var(--ink); box-shadow: 0 0 0 2px var(--focus-ring); color: var(--paper); }
.notesline::placeholder { color: var(--ghost); }

.rule { height: 1px; background: var(--line); margin: 14px 0 6px; }

/* ---------------- the page body: blocks ---------------- */
.blocks { margin-top: 2px; }
.blk { position: relative; display: flex; align-items: flex-start; gap: 4px; border-radius: 6px; }
/* A FIELD BLOCK IS ITS CONTROL. It borrows `.prow` for its shape, and `.prow`
   reserves a 150px label column because a property row lines its names up — a
   block has no name to line up, so the column would be 150px of nothing before
   every date. */
.blk.field .pval { flex: 1; min-width: 0; }
.blk:hover { background: rgb(var(--c-panel2) / .45); }
/* THE GRIP IS CENTRED ON WHATEVER IT IS GRIPPING.
   CEO, 2026-08-25: "the dots needs to be always centered vertically". `.blk` is
   `align-items: flex-start` so that a paragraph running to three lines keeps its
   first line beside the grip — right for text, wrong for the grip itself, which
   was pinned to the top of a reference card several times its own height and read
   as belonging to the card's first line rather than to the card.
   `align-self` overrides the row's alignment for this one child, so the text
   still starts at the top and the dots sit in the middle of any height. The old
   `margin-top: 2px` was nudging it against a single line of text and fights
   centring, so it goes. */
.bgrip {
  opacity: 0; flex-shrink: 0; width: 20px; height: 26px; display: flex; align-items: center;
  justify-content: center; border: 0; background: none; color: var(--ghost); cursor: pointer; border-radius: 4px;
  align-self: center;
}
.blk:hover .bgrip, .blk:focus-within .bgrip { opacity: 1; }
.bgrip:hover { background: var(--line); color: var(--paper); }
.bmark { flex-shrink: 0; display: flex; align-items: center; justify-content: center; width: 18px; margin-top: 6px; color: var(--mute); font-size: 13px; }
.btext {
  flex: 1; min-width: 0; outline: none; padding: 4px 6px; border-radius: 4px;
  font-size: 14px; line-height: 1.7; color: var(--paper); word-break: break-word;
}
/* A PLACEHOLDER IS A PROMPT, AND A PROMPT IS FOR SOMEBODY WHO IS ALREADY THERE.
   CEO, 2026-08-25: "this needs to be remove from the default block .. it should
   only shows up when the user clicks to type but has not typed anything".
   Every empty block used to carry "Type '/' for commands", so the line waiting at
   the bottom of every page in the product was permanently announcing a shortcut
   nobody had asked for — the busiest screen carrying an instruction on it at all
   times. Shown on FOCUS and empty, which is exactly the moment the sentence is
   useful and the only moment it is. */
.btext:empty:focus::before { content: attr(data-ph); color: var(--ghost); pointer-events: none; }
.btext:focus { box-shadow: 0 0 0 2px var(--focus-ring); }

.blk.h1 .btext { font-size: 21px; font-weight: 600; letter-spacing: -.02em; }
.blk.h1 { margin-top: 18px; }
.blk.h2 .btext { font-size: 17px; font-weight: 600; letter-spacing: -.015em; }
.blk.h2 { margin-top: 14px; }
.blk.h3 .btext { font-size: 14.5px; font-weight: 600; letter-spacing: -.01em; }
.blk.h3 { margin-top: 10px; }
/* A toggle heading IS the heading, with a triangle. The rules are shared rather
   than duplicated so the two can never drift into different sizes. */
.blk.tg1 .btext { font-size: 21px; font-weight: 600; letter-spacing: -.02em; }
.blk.tg1 { margin-top: 18px; }
.blk.tg2 .btext { font-size: 17px; font-weight: 600; letter-spacing: -.015em; }
.blk.tg2 { margin-top: 14px; }
.blk.tg3 .btext { font-size: 14.5px; font-weight: 600; letter-spacing: -.01em; }
.blk.tg3 { margin-top: 10px; }
/* The triangle sits on the heading's baseline rather than the body text's. */
.blk.tg1 .bmark.tgl, .blk.tg2 .bmark.tgl, .blk.tg3 .bmark.tgl { align-self: center; }

.bmark.num { font-variant-numeric: tabular-nums; font-size: 12.5px; color: var(--mute); }
.bmark.tgl { border: 0; background: none; cursor: pointer; color: var(--ghost); padding: 0; transition: transform .12s; }
.bmark.tgl.open { transform: rotate(90deg); }
/* Empty reads as empty: dimmed, and it does not turn. Same space either way, so
   filling a toggle moves nothing on the line. */
.bmark.tgl.empty { color: var(--line); }
.bmark.tgl.empty:hover { color: var(--ghost); }
.bmark.tgl:hover { color: var(--paper); }

.blk.todo .box {
  flex-shrink: 0; margin-top: 8px; height: 15px; width: 15px; border-radius: 4px;
  border: 1.5px solid var(--line); display: flex; align-items: center; justify-content: center;
  cursor: pointer; background: none; padding: 0; color: transparent;
}
.blk.todo .box.on { border-color: var(--good); background: rgb(var(--c-good) / .18); color: var(--good); }
.blk.todo .btext.on { color: var(--ghost); text-decoration: line-through; }

.blk.quote { border-inline-start: 3px solid var(--line); margin: 6px 0; }
.blk.quote .btext { color: var(--mute); font-style: italic; }

.blk.callout {
  background: rgb(var(--c-wk) / .09); border: 1px solid rgb(var(--c-wk) / .28);
  border-radius: 10px; padding: 8px 12px 8px 8px; margin: 8px 0;
}
.blk.callout .bmark { color: var(--wksoft); font-size: 10px; }
.blk.callout .btext { color: var(--paper); font-size: 13.5px; }

.blk.code { background: var(--panel2); border: 1px solid var(--line); border-radius: 10px; margin: 8px 0; padding: 6px 10px 6px 4px; }
.blk.code .btext {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px;
  line-height: 1.7; white-space: pre-wrap; color: var(--paper);
}

/* media --------------------------------------------------------------- */
.blk.link { align-items: flex-start; }
.linkwrap { flex: 1; min-width: 0; padding: 4px 0; }
.linkfig { margin: 0; }
.blkimg, .blkvid { max-width: 100%; display: block; border-radius: 10px; border: 1px solid var(--line); }
.blkaud { width: 100%; }
.embed { position: relative; padding-top: 56.25%; border-radius: 10px; overflow: hidden; border: 1px solid var(--line); background: var(--ink); }
.embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

.linkcard {
  display: flex; align-items: center; gap: 10px; text-decoration: none;
  border: 1px solid var(--line); border-radius: 10px; padding: 11px 12px;
  background: var(--panel2); color: var(--paper);
}
.linkcard:hover { border-color: rgb(var(--c-wk) / .6); }
.linkcard.bad { border-color: rgb(var(--c-bad) / .5); color: var(--bad); cursor: pointer; }
.linkicon { flex-shrink: 0; display: flex; color: var(--wksoft); }
.linkmeta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.linkname { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.linkhost { font-size: 11px; color: var(--ghost); }
.linkcard > svg:last-child { flex-shrink: 0; color: var(--ghost); }

.pdfhead {
  display: flex; align-items: center; gap: 10px;
  border: 1px solid var(--line); border-radius: 10px 10px 0 0;
  background: var(--panel2); padding: 9px 12px;
}
.pdfhead + .pdfframe { border-radius: 0 0 10px 10px; border-top: 0; }
.pdftgl, .pdfopen {
  flex-shrink: 0; border: 0; background: none; padding: 0; font: inherit; font-size: 11px;
  color: var(--ghost); cursor: pointer; text-decoration: none;
}
.pdftgl:hover, .pdfopen:hover { color: var(--wksoft); text-decoration: underline; }
/* Resizable because a one-page memo and a forty-page portfolio do not want the
   same height, and the reader knows which they have. */
.pdfframe {
  border: 1px solid var(--line); border-radius: 10px; overflow: hidden;
  background: var(--ink); height: 520px; min-height: 160px; resize: vertical;
}
.pdfframe iframe { width: 100%; height: 100%; border: 0; display: block; }


.filedrop { outline: 2px dashed rgb(var(--c-wk) / .8); outline-offset: -8px; background: rgb(var(--c-wk) / .06); }
.filedrop::after {
  content: 'Drop to add these files to the page';
  position: sticky; bottom: 12px; display: block; margin: 12px auto 0; width: fit-content;
  background: var(--wk); color: var(--onaccent); font-size: 12px; font-weight: 600;
  padding: 7px 14px; border-radius: 999px;
}
[data-missing] { outline: 1px dashed rgb(var(--c-line)); min-height: 0; height: 0; display: none; }
.filemissing { padding: 10px 12px; font-size: 11.5px; color: var(--ghost); border: 1px dashed var(--line);
  border-radius: 10px; background: var(--panel2); }
.linkopen.off, .pdfopen.off { color: var(--ghost); cursor: default; text-decoration: none; pointer-events: none; }

.linkempty {
  display: flex; align-items: center; gap: 8px; cursor: pointer;
  border: 1px dashed var(--line); border-radius: 10px; padding: 12px;
  font-size: 13px; color: var(--ghost);
}
.linkempty:hover { border-color: rgb(var(--c-wk) / .6); color: var(--wksoft); }
.linkcap { font-size: 12px; color: var(--mute); margin-top: 6px; }
.linkbar { display: flex; align-items: center; gap: 10px; margin-top: 6px; opacity: 0; transition: opacity .12s; }
.blk.link:hover .linkbar, .blk.link:focus-within .linkbar { opacity: 1; }

/* A RECOGNISED EMAIL, PHONE NUMBER OR PERSON, STYLED ONCE.
   The same in a block, in a discussion message and in a reference body, so a
   recognised thing never looks like two different things depending on where it
   was typed. Underlined rather than coloured alone — status is never colour
   alone here, and neither is a link. */
.rlink { color: var(--wksoft); text-decoration: underline; text-underline-offset: 2px; }
.rlink:hover { color: var(--accent2); }
.mention {
  color: var(--wksoft); background: rgb(var(--c-wk) / .14);
  border-radius: 4px; padding: 0 3px; font-weight: 600;
}
.linkopen { font-size: 11px; color: var(--ghost); text-decoration: none; }
.linkopen:hover { color: var(--wksoft); text-decoration: underline; }
.linkedit { border: 0; background: none; padding: 0; font: inherit; font-size: 11px; color: var(--ghost); cursor: pointer; }
.linkedit:hover { color: var(--wksoft); text-decoration: underline; }

.blk.div { padding: 8px 0; }
.blk.div .line { flex: 1; height: 1px; background: var(--line); }

/* inline marks */
.btext code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .88em;
  background: var(--panel2); border-radius: 4px; padding: 1px 5px; color: var(--wksoft);
}
.btext a { color: var(--wksoft); text-underline-offset: 2px; }
.btext s { color: var(--ghost); }

/* ================= A ROW OF BLOCKS =================
   Six across on the desktop, two across on a phone, and a lone leftover takes the
   whole width rather than sitting at half with a gap beside it — CEO, 2026-08-25,
   on what happens to the odd one: "the leftover will be alone", and full width is
   the recommendation he took.

   THE PHONE RULE IS CSS, NOT A SECOND GROUPING IN JS. `flex-wrap` turns six
   columns into three pairs on its own, in the same left-to-right order, so there
   is no phone-shaped copy of the layout logic to fall out of step with the
   desktop one. `:last-child:nth-child(odd)` is exactly "there was nothing to pair
   me with", which is the only case that wants the full width. */
.brow { display: flex; align-items: stretch; gap: 0; width: 100%; }
.bcol { flex: var(--w, 1) 1 0; min-width: 0; display: flex; flex-direction: column; }
/* A column's blocks keep their own left padding; the column supplies the gutter,
   so two paragraphs side by side are not touching. */
/* FLUSH BOTTOMS. CEO, 2026-08-25: "if one block in a row is higher than other
   blocks other blocks hight will be adjusted to be flushed with the higher one".
   `align-items: stretch` on the row gives every column the tallest one's height;
   these two carry that height down through the block to the editable area, so a
   short paragraph beside a tall one is a tall box you can click anywhere in —
   rather than a short box with dead space under it, which is the same defect as a
   press that lands on nothing.
   Blocks NOT in a row are untouched by any of this and stay full width, which is
   what dissolving a row of one in `normaliseRows` protects. */
.bcol > .blk { flex: 1; align-items: stretch; }
.bcol > .blk > .btext { flex: 1; }
.brow > .bcol + .bcol > .blk, .brow .bsplit + .bcol > .blk { padding-inline-start: 4px; }

/* THE HANDLE BETWEEN TWO COLUMNS. Six pixels wide with the rule drawn down the
   middle of it: a 1px target cannot be hit, and reserving the six keeps the
   columns from shifting when it appears. Invisible until the row is hovered, for
   the same reason the block grip is. */
.bsplit { flex: 0 0 6px; align-self: stretch; cursor: col-resize; position: relative;
  opacity: 0; transition: opacity .12s; }
.brow:hover .bsplit, .bsplit.dragging { opacity: 1; }
.bsplit::before { content: ''; position: absolute; inset-block: 2px; inset-inline-start: 2px;
  width: 2px; border-radius: 1px; background: var(--line); }
.bsplit:hover::before, .bsplit.dragging::before { background: var(--wk); }
/* On the snap point, so it is visible that it will land evenly. */
.bsplit.snapped::before { background: var(--good); }

/* A DIVIDER IN A ROW STANDS UP. CEO, 2026-08-25: "if the divider block is dragged
   in a row with more blocks becomes vertical not horozontal". It is still "a
   block like everything else" — its own column, counting toward the six — it is
   only drawn on its side, because a horizontal rule a third of the page wide
   reads as a mistake rather than a division. */
.bcol > .blk.div { align-items: stretch; }
.bcol > .blk.div .line { flex: 0 0 2px; width: 2px; height: auto; min-height: 24px;
  align-self: stretch; margin: 2px auto; background: var(--line); }

/* THE EDGES OF A BLOCK MEAN BESIDE. The insertion mark is vertical there, because
   the thing it promises is a column, not a line above or below. */
.blk.dropleft { box-shadow: inset 2px 0 0 var(--wk); }
.blk.dropright { box-shadow: inset -2px 0 0 var(--wk); }
.dropcopy.dropleft { box-shadow: inset 2px 0 0 var(--good); }
.dropcopy.dropright { box-shadow: inset -2px 0 0 var(--good); }

/* drag target */
.blk.dropbefore { box-shadow: inset 0 2px 0 var(--wk); }
.blk.dropafter { box-shadow: inset 0 -2px 0 var(--wk); }

/* ================= OPTION COPIES, AND IT LOOKS THE SAME EVERYWHERE =================
   CEO, 2026-08-24: "if the user option drags anywhere it make a copy", and "if a
   fucking user option drags a block .. it is copied .. this is consistent through
   out the fucking system".

   One gesture with one meaning gets one appearance, written once here rather than
   four times in four wirings — which is how the sidebar ended up with a green wash
   and a COPY tag while the board and the block editor showed nothing at all, so the
   same key looked like three different features.

   TWO SIGNALS, NOT ONE. They answer different questions and they were welded into a
   single class, which is what made a copy into a gap indistinguishable from a move:
     - the tag says THIS IS A COPY, and rides every zone, including the gaps;
     - the wash says IT LANDS INSIDE THIS ROW, and only appears with `dropinto`.
   The insertion edge keeps its shape and only changes colour, so the mark still
   reads as the same mark. Colour alone would not be enough on its own — the tag is
   the glyph that carries it, which is the house rule for any state.

   Placed after every surface's own drop rules so it wins on order, not on weight. */
.dropcopy.dropbefore, .tt tbody tr.dropcopy.dropbefore td { box-shadow: inset 0 2px 0 var(--good); }
.dropcopy.dropafter, .tt tbody tr.dropcopy.dropafter td { box-shadow: inset 0 -2px 0 var(--good); }
.dropcopy.dropinto, .tt tbody tr.dropcopy.dropinto td {
  background: rgb(var(--c-good) / .14); box-shadow: inset 0 0 0 1px rgb(var(--c-good) / .5);
}
.tt tbody tr.dropcopy.dropinto .name { box-shadow: inset 3px 0 0 var(--good); }
/* The tag needs a containing block, and every row here is a plain flow element.
   Reserved rather than inserted: it is positioned out of flow, so no row changes
   height or width when it appears and nothing under the pointer moves. */
.navrow.dropcopy, .blk.dropcopy, .tlrow.dropcopy, .tt tbody tr.dropcopy .name { position: relative; }
.navrow.dropcopy::after, .blk.dropcopy::after, .tlrow.dropcopy::after,
.tt tbody tr.dropcopy .name::after {
  content: 'COPY'; position: absolute; inset-inline-end: 8px; top: 50%;
  transform: translateY(-50%); font-size: 9px; font-weight: 700;
  letter-spacing: .08em; color: var(--good); pointer-events: none;
}
/* On a HEADING the tag follows the word instead of sitting at the inline end,
   because the end is where the trash and plus buttons already are. Out of flow
   either way, so neither the word nor the buttons move when it appears. */
.navgroup.dropcopy > span:first-child { position: relative; }
.navgroup.dropcopy > span:first-child::after {
  content: 'COPY'; position: absolute; inset-inline-start: 100%; top: 50%;
  transform: translateY(-50%); margin-inline-start: 8px;
  font-size: 9px; font-weight: 700; letter-spacing: .08em;
  color: var(--good); pointer-events: none;
}
.bgrip { cursor: grab; }
.bgrip:active { cursor: grabbing; }
.menu.slash .mi.cur { background: var(--panel2); box-shadow: inset 0 0 0 1px rgb(var(--c-wk) / .5); }

/* The waiting line's gutter. It has no grip — there is nothing to drag yet —
   but it holds the same space, so the line sits exactly where the block it is
   about to become will sit. Reserve space; never let content decide geometry. */
.ghostgrip { display: inline-block; width: 13px; height: 13px; flex-shrink: 0; }

.addblk {
  display: inline-flex; align-items: center; gap: 6px; border: 0; background: none;
  padding: 6px 8px; margin-top: 6px; font: inherit; font-size: 13px; color: var(--ghost);
  cursor: pointer; border-radius: 6px;
}
.addblk:hover { color: var(--wksoft); background: rgb(var(--c-wk) / .1); }

/* ---------------- templates ---------------- */
.tplbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 10px 0 4px; }
.tpls { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(158px, 1fr)); margin-top: 10px; }
.tpl {
  border: 1px solid var(--line); border-radius: 10px; background: var(--panel2);
  padding: 11px 12px; text-align: start; cursor: pointer; font: inherit;
  transition: border-color .15s, background-color .15s;
}
.tpl:hover { border-color: rgb(var(--c-wk) / .6); background: rgb(var(--c-wk) / .08); }
.tpl .tname { font-size: 12.5px; font-weight: 600; color: var(--paper); display: flex; align-items: center; gap: 7px; }
.tpl .tdesc { font-size: 11px; color: var(--ghost); margin-top: 4px; line-height: 1.5; }
.blankpage {
  border: 1px dashed rgb(var(--c-line) / .9); border-radius: 12px; padding: 18px;
}
.blankpage p { margin: 0 0 4px; font-size: 13px; color: var(--mute); }
.blankpage .hint { font-size: 11.5px; color: var(--ghost); }


/* ---------------- view switcher ---------------- */
.viewbar { display: inline-flex; gap: 2px; background: var(--panel2); border-radius: 10px; padding: 3px; }
.viewbtn {
  display: inline-flex; align-items: center; gap: 6px; border: 0; background: none;
  padding: 6px 11px; border-radius: 8px; font: inherit; font-size: 12.5px;
  color: var(--mute); cursor: pointer; white-space: nowrap;
}
.viewbtn:hover { color: var(--paper); }
.viewbtn.on { background: var(--panel); color: var(--paper); box-shadow: var(--shadow-card); }
.viewbtn.on svg { color: var(--wksoft); }
/* The strip's editor affordance and its reorder marks (5.111). The marks are
   the column headers' own, physical for the same reason theirs are. */
.viewbtn.viewedit { color: var(--ghost); }
.viewbtn.viewedit:hover { color: var(--paper); }
.viewbtn.dropbefore { box-shadow: inset 2px 0 0 var(--wk); }
.viewbtn.dropafter { box-shadow: inset -2px 0 0 var(--wk); }
/* A chip being renamed IS the field (5.113): same footprint as the chip it
   replaces, so the strip does not shift under the caret. */
.viewbtn.viewrenaming { background: var(--panel); box-shadow: var(--shadow-card); }
.viewrename {
  background: none; border: 0; outline: none; font: inherit; font-size: 12.5px;
  color: var(--paper); width: 10ch; padding: 0;
  border-block-end: 1px solid var(--wk); border-radius: 0;
}

/* Board, Calendar and Timeline sit in the same card as the table they replace.
   This one used to draw a full 12px box under a toolbar whose bottom border was
   removed on the assumption the TABLE would close it — so switching to an alt
   view left an unclosed box above a rounded one. Gone with the rest of the
   second card. */
.viewpane { overflow: hidden; }
.vempty { font-size: 12px; color: var(--ghost); padding: 10px 4px; margin: 0; }
.vnote { font-size: 11.5px; color: var(--ghost); }
.vblocked { padding: 34px 24px; text-align: center; }
.vblocked p { font-size: 13px; color: var(--mute); margin: 0; }

/* cards shared by Board and Timeline */
.vcard {
  display: flex; flex-direction: column; gap: 7px; width: 100%; text-align: start;
  border: 1px solid var(--line); border-radius: 10px; background: var(--panel2);
  padding: 10px 11px; cursor: pointer; font: inherit;
}
.vcard:hover { border-color: rgb(var(--c-wk) / .6); }
.vcrumb { font-size: 10px; color: var(--ghost); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vname { font-size: 13px; font-weight: 500; color: var(--paper); line-height: 1.4; }
.vname.done { color: var(--ghost); text-decoration: line-through; }
.vmeta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }

/* ---------------- Board ---------------- */
.kanban { display: flex; gap: 12px; padding: 14px; overflow-x: auto; overscroll-behavior-x: contain; align-items: flex-start; }
.kcol { flex: 0 0 268px; display: flex; flex-direction: column; gap: 10px; }
.khead { display: flex; align-items: center; gap: 8px; }
.kcount { font-size: 11px; color: var(--ghost); font-variant-numeric: tabular-nums; }
.kbody { display: flex; flex-direction: column; gap: 8px; }

/* ---------------- Calendar ---------------- */
.calhead { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 12px 14px; border-bottom: 1px solid var(--line); }
/* The week rows SHARE the height sizeScroller gives the grid, so the whole
   month is on screen at once (5.116) — no row may refuse to shrink, which is
   why the cells' floor is 0 on desktop and a crowded day scrolls inside its
   own cell instead of growing the row. The phone keeps flowing rows: two
   scroll areas fighting for one thumb is worse than a tall page there. */
.cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); grid-template-rows: auto; grid-auto-rows: minmax(0, 1fr); }
.cal-dow { padding: 8px 10px; font-size: 10px; text-transform: uppercase; letter-spacing: .08em; color: var(--ghost); border-bottom: 1px solid var(--line); }
.cal-cell { min-height: 0; overflow-y: auto; border-inline-end: 1px solid var(--line); border-bottom: 1px solid var(--line); padding: 6px; display: flex; flex-direction: column; gap: 4px; }
.cal-cell.out { background: rgb(var(--c-panel2) / .35); }
/* ---------------- the three page blocks (5.118) ---------------- */
/* The card: the page's image AS A THUMBNAIL BESIDE THE CONTENT — a card, not
   a banner (CEO, 2026-08-28: "i dont want a fucking banner for the page card
   i want a fucking page card"). Panel ground, hairline, radius from the kit. */
.pagecardbtn {
  display: flex; flex-direction: row; align-items: stretch; width: 100%; text-align: start;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  padding: 0; overflow: hidden; cursor: pointer; font: inherit; color: var(--ink);
}
.pagecardbtn:hover { border-color: rgb(var(--c-wk) / .5); }
.pcimg { flex: 0 0 96px; min-height: 96px; background-size: cover; background-position: center;
  border-inline-end: 1px solid var(--line); }
.pcbody { display: flex; flex-direction: column; gap: 6px; padding: 12px 14px; min-width: 0; }
.pcname { display: inline-flex; align-items: center; gap: 8px; font-weight: 600; color: var(--paper); }
.pcname svg { color: var(--wksoft); }
.pcrow { display: flex; gap: 10px; font-size: 12.5px; }
.pclab { color: var(--mute); min-width: 96px; flex-shrink: 0; }
.pcval { color: var(--ink); overflow-wrap: anywhere; }
/* The preview: the whole body, read-only, scrolling inside its own frame —
   hard constraint 4's shape. The inner content is inert; the scroll lives on
   the wrapper, which is not. */
.prevwrap { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; background: var(--panel); }
.prevhead { width: 100%; border-block-end: 1px solid var(--line); border-radius: 0; }
/* The word that says the table below is withholding rows (5.123) — a filtered
   preview must never pass for the whole board. A word, not a colour: status
   never travels on colour alone. */
.prevflt { margin-inline-start: auto; flex-shrink: 0; font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em; color: var(--mute);
  border: 1px solid var(--line); border-radius: 999px; padding: 2px 8px; }
/* Most of a screen, not a slot (CEO, 2026-08-28) — the body has to be
   READABLE in place, and the frame scrolls when it runs longer. */
/* Both axes: a wide table inside the preview scrolls HERE (constraint 4 —
   the page level never scrolls sideways). 420px is the DEFAULT height (CEO,
   2026-08-28); the grip below stretches or shrinks it, stored on the block. */
.prevscroll { max-height: 420px; overflow: auto; }
.prevgrip { height: 9px; cursor: row-resize; touch-action: none;
  border-block-start: 1px solid var(--line); background: var(--panel2); }
.prevgrip:hover { background: rgb(var(--c-wk) / .25); }
/* The as-is table scrolls SIDEWAYS inside its own wrap (hard constraint 4;
   CEO, 2026-08-28: "horozontal scrowlling should work on page side view") —
   in the side view there is no outer scroller to lean on, and since 5.119b
   the preview is interactive, so an inner scroller works there too. */
.prevtable { overflow-x: auto; }
.prevtable .tt { table-layout: fixed; min-width: 100%; }
.prevtable th { text-align: start; font-size: 11px; color: var(--ghost);
  text-transform: uppercase; letter-spacing: .04em; padding: 8px 10px;
  border-block-end: 1px solid var(--line); }
/* pointer-events, not `inert`: inert removed the content from hit-testing so
   the wheel had nothing to grab and the preview would not scroll under the
   pointer. pointer-events lets the wheel land while every click passes dead
   through — and the data- hooks are stripped anyway, so there is nothing to
   click into. */
.prevbody { padding: 8px 14px; }
.prevrow { display: flex; align-items: center; gap: 8px; padding-block: 5px;
  border-block-end: 1px solid rgb(var(--c-line) / .5); font-size: 13px; }
.prevrow .done { text-decoration: line-through; color: var(--mute); }
.prevrow svg { color: var(--mute); flex-shrink: 0; }
.prevempty { color: var(--mute); font-size: 12.5px; padding-block: 8px; }

/* Today is ONLY the circle on the number (CEO, 2026-08-28: "the cell should
   be the same as the rest") — the washed cell that sat here is gone. */
.cal-num { font-size: 11px; color: var(--ghost); font-variant-numeric: tabular-nums; }
/* TODAY WEARS THE FILLED CIRCLE every calendar people know (CEO, 2026-08-28:
   "todays date should be highlighted" — the tinted wash alone did not read).
   onaccent on a wk fill measures 6.64:1 dark, 6.32:1 light — computed from
   the triplets, both clear of the 4.5 floor. */
.cal-cell.today .cal-num {
  color: var(--onaccent); background: var(--wk); font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
  align-self: start;      /* the cell stretches its children; the circle must not become a bar */
  min-width: 20px; height: 20px; padding-inline: 5px; border-radius: 999px;
}
.cal-item {
  display: flex; align-items: center; gap: 5px; width: 100%; text-align: start;
  border: 0; border-radius: 6px; background: var(--panel2); color: var(--paper);
  padding: 4px 6px; font: inherit; font-size: 11px; cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-item:hover { background: rgb(var(--c-wk) / .18); }
.cal-item.late { color: var(--bad); background: rgb(var(--c-bad) / .12); }
.cal-item svg { flex-shrink: 0; }

.hovercard {
  position: fixed; z-index: 88; width: 292px; pointer-events: none;
  border: 1px solid var(--line); border-radius: 12px; background: var(--panel);
  box-shadow: var(--shadow-card); padding: 12px 14px;
}
@media (prefers-reduced-motion: no-preference) { .hovercard { animation: work-fade-in 120ms ease-out; } }
.hccrumb { font-size: 10.5px; color: var(--ghost); margin-bottom: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hcname { font-size: 14px; font-weight: 600; color: var(--paper); line-height: 1.35; }
.hcname.done { color: var(--ghost); text-decoration: line-through; }
.hcchips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.hcrows { margin-top: 10px; display: flex; flex-direction: column; gap: 5px; }
.hcrow { display: flex; gap: 10px; align-items: flex-start; font-size: 12px; }
.hckey { width: 74px; flex-shrink: 0; color: var(--ghost); }
.hcval { flex: 1; min-width: 0; color: var(--paper); display: flex; flex-wrap: wrap; align-items: center; gap: 5px; }
.hcval .person { padding: 0; font-size: 12px; }
.hcval .av { height: 18px; width: 18px; font-size: 8px; }
.hcnames { font-size: 11.5px; color: var(--mute); }

/* ---------------- Timeline ---------------- */
.tlwrap { display: flex; flex-direction: column; min-height: 0; }
.tlrows { padding: 6px 0 8px; overflow-y: auto; }
.tlfoot { display: flex; align-items: center; gap: 12px; padding: 2px 14px 4px;
  border-top: 1px solid var(--line); background: var(--panel); flex-shrink: 0; }
.tlrow.newrow { padding-block: 0; }
.tlrow.newrow:hover { background: none; }
/* Collapsed: the tasks inside still draw, as thin lines on the project's own
   track. Folding a project should hide the detail, not the fact that there is
   work in there and roughly when it falls. */
.tlmini {
  position: absolute; min-width: 3px; border: 0; padding: 0; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  color: var(--onaccent); background: var(--wk);
  /* the hairline is what carves a child out of the parent it sits on; without it
     two bands of the same colour read as one thick bar with a ragged edge */
  box-shadow: 0 0 0 1px rgb(var(--c-panel) / .7);
}
.tlmini.done { background: var(--good); }
.tlmini.late { background: var(--bad); }
.tlmini:hover { filter: brightness(1.12); box-shadow: 0 0 0 1px rgb(var(--c-panel) / .7), 0 0 0 3px rgb(var(--c-wk) / .3); }
.tlmini svg { opacity: .9; }
.tlrow { display: flex; align-items: center; gap: 12px; padding: 4px 14px; }
.tlrow:hover { background: var(--panel2); }
/* The timeline's left side IS the table's name cell — same markup, same rules.
   The only thing this adds is a width, and it takes that from the table's own
   Name column, so dragging that column's edge resizes both. */
.tlrow > .ncell, .tlfoot > .ncell { flex: 0 0 var(--namew, 260px); overflow: hidden; }
.tltrack { position: relative; flex: 1; height: 26px; background: var(--panel2); border-radius: 6px; }
.tlbar {
  position: absolute; top: 3px; bottom: 3px; min-width: 22px; border: 0; border-radius: 5px;
  background: var(--wk); color: var(--onaccent); cursor: pointer; display: flex;
  align-items: center; justify-content: center; padding: 0;
}
.tlbar.late { background: var(--bad); }
.tlbar.done { background: var(--good); }

/* ---------------- discussion ---------------- */
.thread { display: flex; flex-direction: column; gap: 2px; margin-bottom: 10px; }
.msg { display: flex; gap: 10px; padding: 8px; border-radius: 10px; }
.msg:hover { background: rgb(var(--c-panel2) / .55); }
.msg .body { flex: 1; min-width: 0; }
.mhead { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.mhead .who { font-size: 12.5px; font-weight: 600; color: var(--paper); }
.mhead .when { font-size: 11px; color: var(--ghost); font-variant-numeric: tabular-nums; }
.mtext { font-size: 13.5px; line-height: 1.65; color: var(--mute); margin-top: 2px; white-space: pre-wrap; word-break: break-word; }
.msg .del { opacity: 0; flex-shrink: 0; }
.msg:hover .del { opacity: 1; }

.composer { display: flex; gap: 10px; align-items: flex-start; border: 1px solid var(--line); border-radius: 12px; padding: 10px; background: var(--ink); }
.composer:focus-within { box-shadow: 0 0 0 2px var(--focus-ring); }
.composer textarea {
  flex: 1; min-width: 0; border: 0; background: none; resize: none; overflow: hidden;
  font: inherit; font-size: 13.5px; line-height: 1.65; color: var(--paper);
  outline: none; min-height: 22px; padding: 2px 0;
}
.composer textarea::placeholder { color: var(--ghost); }
.composer .send { flex-shrink: 0; align-self: flex-end; }
.nothread { font-size: 13px; color: var(--ghost); padding: 4px 8px 12px; }

/* overlapping avatars so a row of five people costs the width of two */
.stack { display: inline-flex; align-items: center; }
.stack .av { height: 22px; width: 22px; font-size: 9px; margin-inline-end: -6px; box-shadow: inset 0 0 0 1px rgba(255,255,255,.1), 0 0 0 2px var(--panel); }
.stack .av:last-child { margin-inline-end: 0; }
.pval .stack { margin-inline-end: 8px; }

/* ---------------- who else is here ----------------
   Presence, drawn in two places: the faces of everyone reading this page, and a
   quiet edge on the block whoever is writing has their caret in.

   THE STRIP SITS BEFORE THE SPACER ON PURPOSE. Everything after the spacer is
   pinned to the inline end, so a strip placed there would push the identity and
   theme buttons sideways every time somebody arrived or left — a control moving
   under the pointer as a colleague opens a page. Placed before it, the strip
   grows into the gap and nothing else moves. Its height is held open whether or
   not anybody is there, so the header never changes height either. */
.herewho { display: inline-flex; align-items: center; min-height: 22px; }
.herewho .av { box-shadow: inset 0 0 0 1px rgba(255,255,255,.1), 0 0 0 2px var(--ink); }
/* The faces are `aria-hidden`, because a screen reader reading a stack of
   initials says "MS NA" and means nothing by it. The sentence beside them is
   what gets announced, and only when it changes. */
.sronly { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

/* The block a colleague has their caret in. `::before`, because `::after` on a
   block is already the Option-copy tag and the two can be true at once. Both the
   edge and the name are out of flow, so a block does not move or change size when
   somebody starts writing in it.

   `--guest` is set on the element from the same `avColor()` that draws that
   person's avatar, so the edge, the name and their face are one colour. That
   palette is the theme-fixed avatar palette — identity, not surface, and the one
   documented exception to colour coming from `globals.css`. */
.blk.guest { box-shadow: inset 0 0 0 1px var(--guest); }
.blk.guest::before {
  content: attr(data-guest); position: absolute; inset-block-start: -7px;
  inset-inline-start: 8px; z-index: 2;
  font-size: 8px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  background: var(--guest); color: rgba(255,255,255,.95);
  padding: 1px 5px; border-radius: 999px; pointer-events: none; white-space: nowrap;
}

.dfield {
  border: 0; background: none; font: inherit; font-size: 13px; color: var(--paper);
  padding: 5px 7px; border-radius: 6px; outline: none; color-scheme: dark;
}
:root[data-theme='light'] .dfield { color-scheme: light; }
.dfield:hover { background: var(--panel2); }
.dfield:focus { background: var(--ink); box-shadow: 0 0 0 2px var(--focus-ring); }

/* Overdue never travels on colour alone — it carries the alert glyph and the
   word, so it survives greyscale and a colour-blind reader. */
.overdue { display: inline-flex; align-items: center; gap: 5px; color: var(--bad); font-weight: 600; font-size: 12.5px; }
.duecell { font-size: 12.5px; color: var(--mute); font-variant-numeric: tabular-nums; }
.ro { font-size: 12.5px; color: var(--mute); font-variant-numeric: tabular-nums; padding-inline-start: 7px; }

.cellbtn { border: 0; background: none; padding: 4px 6px; margin: 0 -6px; border-radius: 8px; cursor: pointer; font: inherit; color: inherit; display: inline-flex; align-items: center; }
.cellbtn:hover { background: rgb(var(--c-line) / .55); }
.dash { color: var(--ghost); }
.menu input[type=date] { color-scheme: dark; }
:root[data-theme='light'] .menu input[type=date] { color-scheme: light; }
.datewrap { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.datewrap .dfield { border: 1px solid transparent; }
.datewrap .overdue { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; background: rgb(var(--c-bad) / .15); border-radius: 999px; padding: 2px 8px; }
.tagcell { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; }
.tagx { border: 0; background: none; color: inherit; cursor: pointer; padding: 0 0 0 3px; font-size: 12px; opacity: .6; }
.tagx:hover { opacity: 1; }
.tagadd { display: inline-flex; align-items: center; gap: 4px; border: 1px dashed var(--line); background: none; color: var(--ghost); border-radius: 999px; padding: 2px 8px; font: inherit; font-size: 10px; text-transform: uppercase; letter-spacing: .04em; cursor: pointer; }
.tagadd:hover { color: var(--wksoft); border-color: rgb(var(--c-wk) / .6); }


.whoami { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--line); border-radius: 999px; padding: 3px 8px 3px 3px; background: none; cursor: pointer; font: inherit; font-size: 12px; color: var(--mute); }
.whoami:hover { border-color: var(--mute); color: var(--paper); }

.cin { width: 100%; border: 0; background: none; font: inherit; font-size: 13px; color: var(--paper); padding: 5px 6px; border-radius: 6px; outline: none; }
.cin:hover { background: rgb(var(--c-line) / .5); }
.cin:focus { background: var(--ink); box-shadow: 0 0 0 2px var(--focus-ring); }
.cin::placeholder { color: var(--ghost); }
.cin.inh { opacity: .55; }

/* A NUMBER PROPERTY LOOKS LIKE EVERY OTHER PROPERTY.
   CEO, 2026-08-25: "since all page properties are left lined .. the numbers need
   to be as well". A right-aligned figure is the convention for a column of
   numbers, and it was the wrong convention to import here: a property list is
   read ACROSS — name, then value — not down, so one value sitting against the far
   edge of its row reads as a different kind of thing rather than as a number.
   The whole visual treatment is gone. What makes a Number a Number is what it
   ACCEPTS — see `data-numonly` — and how a board column sorts it. Nothing about
   how it is drawn. */

.chip {
  display: inline-flex; align-items: center; gap: 4px; border-radius: 999px; padding: 3px 9px;
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
  white-space: nowrap; border: 0; cursor: pointer; font-family: inherit;
}
.chip.inh { opacity: .55; }
.c-neutral { background: var(--panel2); color: var(--mute); }
.c-accent  { background: rgb(var(--c-wk) / .15); color: var(--wksoft); }
.c-good    { background: rgb(var(--c-good) / .15); color: var(--good); }
.c-warn    { background: rgb(var(--c-warn) / .15); color: var(--warn); }
.c-bad     { background: rgb(var(--c-bad) / .15); color: var(--bad); }
.c-violet  { background: rgb(var(--c-accent2) / .15); color: var(--accent2); }
.c-empty   { background: none; color: var(--ghost); box-shadow: inset 0 0 0 1px var(--line); }
/* Was never defined, so Low's chip rendered bare — found in the CEO's
   2026-08-27 screenshot while everything else wore a wash. */
.c-ghost   { background: rgb(var(--c-ghost) / .15); color: var(--ghost); }

.person { display: inline-flex; align-items: center; gap: 7px; min-width: 0; border: 0; background: none; padding: 3px 6px; border-radius: 999px; cursor: pointer; font: inherit; font-size: 13px; color: var(--paper); }
.person:hover { background: rgb(var(--c-line) / .5); }
.person.inh { opacity: .55; }
.av { flex-shrink: 0; display: flex; align-items: center; justify-content: center; height: 22px; width: 22px; border-radius: 999px; font-size: 9px; font-weight: 700; color: rgba(255,255,255,.95); box-shadow: inset 0 0 0 1px rgba(255,255,255,.1); }
.av.none { background: var(--panel2); color: var(--ghost); box-shadow: inset 0 0 0 1px var(--line); }

/* row actions, revealed on hover or focus so they do not add permanent noise */
/* Sits at the inline end of its cell. flex-end follows the writing direction, so
   this stays correct when the layout mirrors — do not swap it for a left/right
   value. The column also lost a button when the overflow "..." moved to
   right-click, so its reserved width came down with it. */
.acts { display: flex; align-items: center; justify-content: flex-end; gap: 2px; opacity: 0; transition: opacity .12s; }
tr:hover .acts, tr:focus-within .acts { opacity: 1; }
.act { width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; border: 0; background: none; color: var(--ghost); border-radius: 6px; cursor: pointer; }
.act:hover { background: var(--line); color: var(--paper); }
.act.danger:hover { background: rgb(var(--c-bad) / .15); color: var(--bad); }
/* Desktop reaches the row menu by right-clicking the row. The phone has no
   right-click, so its own media query puts the button back. */
@media (min-width: 768px) { .act[data-more] { display: none; } }

/* ---------------- confirm ---------------- */
/* Constraint 5: capped and centred so it can never run past the bottom edge. */
.confirmbox {
  position: fixed; z-index: 95; inset-inline: 0; margin: 0 auto; top: 50%;
  transform: translateY(-50%); width: min(420px, calc(100vw - 32px));
  max-height: calc(100dvh - 48px); overflow-y: auto;
  border: 1px solid var(--line); border-radius: 16px; background: var(--panel);
  box-shadow: var(--shadow-card); padding: 20px;
}
.confirmbox h3 { margin: 0 0 8px; font-size: 15px; font-weight: 600; letter-spacing: -.01em; line-height: 1.45; }
.confirmbox p { margin: 0 0 18px; font-size: 13px; line-height: 1.6; color: var(--mute); }
/* The opt-in to a larger loss. It sits above the buttons, in the reading order
   of the sentence it qualifies, and its hit area is the whole label rather than
   the 13px box — a control that is hard to hit is a control that gets checked by
   accident. */
.confirmcheck {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 0 0 18px; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--panel2);
  font-size: 13px; line-height: 1.5; color: var(--paper); cursor: pointer;
}
.confirmcheck input { margin-block-start: 2px; accent-color: var(--wk); flex: none; }
.confirmrow { display: flex; justify-content: flex-end; gap: 8px; }
.confirmrow .btn { min-height: 40px; padding-inline: 16px; }
/* The property-edit dialog (editPropertyDialog), in the product's OWN form
   voice: the title IS the name and is edited in place (the row is the field,
   2026-08-24), the type sits under it as the subtitle, and every field label
   sits ABOVE its field — the same block-label pattern the sign-in form set
   (`.authbox label`). Option rows keep their controls' SPACE at all times and
   reveal the quiet ones (move, remove) on hover by opacity — nothing shifts
   under a pointer reaching for it. */
.confirmbox.epbox { width: min(560px, calc(100vw - 48px)); }
.eptitle { display: block; width: 100%; border: 0; background: none; color: var(--paper);
  font: inherit; font-size: 18px; font-weight: 600; letter-spacing: -.02em;
  padding: 2px 6px; margin: -2px 0 2px -6px; border-radius: 6px; outline: none; }
.eptitle:hover { background: rgb(var(--c-panel2) / .45); }
.eptitle:focus { background: var(--panel2); box-shadow: 0 0 0 2px var(--focus-ring); }
.eptype { margin: 0 0 14px; font-size: 13px; color: var(--mute); }
.epfield { margin-block: 14px; }
.eplab { display: block; font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .07em; color: var(--ghost); margin-block-end: 6px; }
.epopts { border: 1px solid var(--line); border-radius: 10px;
  padding: 6px; max-height: 260px; overflow-y: auto; }
.epopt { display: flex; align-items: center; gap: 6px; padding: 3px 6px; border-radius: 8px; }
.epopt:hover { background: rgb(var(--c-panel2) / .45); }
/* The swatch shows the colour people READ — the chip class's text tone, via
   currentColor — not the dark wash it sits on (CEO, 2026-08-27). */
.epswatch { width: 24px; height: 16px; border-radius: 999px; border: 0; cursor: pointer;
  flex-shrink: 0; padding: 0; background: currentColor; }
.epswatchmini { display: inline-block; width: 22px; height: 12px; border-radius: 999px;
  background: currentColor; padding: 0; }
.opticon { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
.opticon svg { width: 100%; height: 100%; }
.epicon, .epdone, .epx, .epgrip { width: 26px; height: 26px; display: flex; align-items: center;
  justify-content: center; border: 0; background: none; color: var(--mute);
  border-radius: 6px; flex-shrink: 0; }
.epicon, .epdone, .epx { cursor: pointer; }
.epgrip { cursor: grab; color: var(--ghost); opacity: 0; }
.epgrip:active { cursor: grabbing; }
.epicon:hover, .epdone:hover, .epx:hover { background: var(--line); color: var(--paper); }
.epdone.on { color: var(--good); }
.epx { opacity: 0; }
.epopt:hover .epx, .epopt:focus-within .epx,
.epopt:hover .epgrip, .epopt:focus-within .epgrip { opacity: 1; }
.epx:hover { color: var(--bad); }
/* The drop side: a hairline on the edge the drop will land against. */
.epopt.epover-top { box-shadow: inset 0 2px 0 0 var(--accent); }
.epopt.epover-btm { box-shadow: inset 0 -2px 0 0 var(--accent); }
/* The name is WRITTEN IN ITS OWN TONE — the row shows the style the option
   will actually wear (CEO, 2026-08-27: "the name next to it should reflect the
   style it will apear to be in"). The colour comes from the tone class on the
   input; the wash does not, because this is still a field being typed into. */
.eplabel { flex: 1; min-width: 0; border: 1px solid transparent; background: none;
  padding: 4px 6px; border-radius: 6px; font-size: 14px; }
.eplabel:hover { border-color: var(--line); }
.eplabel:focus { border-color: var(--accent); outline: none; background: var(--panel2); }
.epadd { margin-block-start: 6px; border-block-start: 1px solid var(--line); padding-block-start: 6px; }
.epadd input { width: 100%; background: var(--panel2); border: 1px solid var(--line);
  border-radius: 8px; padding: 7px 9px; color: var(--paper); }
.epempty { color: var(--mute); font-size: 13px; padding: 8px; margin: 0; }
/* The crop stage (5.106a): the image at fit, the selection over it. The scrim
   outside the selection is painted by the selection's own giant shadow, so
   there is exactly one element to move. */
.cropstage { position: relative; margin: 0 auto; overflow: hidden; border-radius: 10px;
  border: 1px solid var(--line); touch-action: none; }
.cropstage img { display: block; width: 100%; height: 100%; user-select: none; }
.cropsel { position: absolute; border: 1.5px solid var(--accent); border-radius: 4px;
  box-shadow: 0 0 0 9999px rgb(var(--c-scrim) / .55); cursor: grab; }
.cropsel:active { cursor: grabbing; }
/* INSIDE the corner, never straddling it: half-out it was clipped by the
   stage's overflow whenever the selection was full-bleed — exactly the state
   every crop starts in — so the control could not be grabbed at all. */
.crophandle { position: absolute; inset-inline-end: 2px; inset-block-end: 2px;
  width: 14px; height: 14px; border-radius: 50%; background: var(--accent);
  border: 2px solid var(--paper); cursor: nwse-resize; }

.newrow td { height: 36px; }
.newbtn { display: inline-flex; align-items: center; gap: 6px; border: 0; background: none; padding: 4px 6px; font: inherit; font-size: 12px; color: var(--ghost); cursor: pointer; border-radius: 6px; }
.newbtn:hover { color: var(--wksoft); background: rgb(var(--c-wk) / .1); }

.empty { padding: 44px 20px; text-align: center; }
.empty p { font-size: 13px; color: var(--mute); margin: 0 0 14px; }

/* ---------------- menu ---------------- */
.menu {
  /* Above the confirm/property dialogs (95): a menu opened FROM a dialog must
     land on top of it — the icon picker drew behind the property popup once,
     which is a control placed where it cannot be used. */
  position: fixed; z-index: 105; min-width: 190px; max-height: 60vh; overflow-y: auto;
  border: 1px solid var(--line); border-radius: 12px; background: var(--panel);
  box-shadow: var(--shadow-card); padding: 5px;
}
.mi {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: start;
  border: 0; background: none; padding: 7px 9px; border-radius: 8px;
  font: inherit; font-size: 13px; color: var(--paper); cursor: pointer;
  /* ONE LINE, ALWAYS. A menu row that wraps reads as two entries, and the eye
     has to work out which lines belong together before it can choose. The menu
     takes the width the longest label needs instead — a menu is as wide as what
     is in it, which is the one dimension nobody is short of. */
  white-space: nowrap;
}
.mi > span { min-width: 0; }
/* The label may not shrink into an ellipsis either: a truncated action is a
   question rather than a choice. */
.mi > span:not(.tick):not(.michev):not(.minote) { overflow: visible; }
.mi:hover { background: var(--panel2); }
.mi .tick { margin-inline-start: auto; color: var(--wksoft); display: flex; }
/* THE CHEVRON SAYS THERE IS MORE, before anyone has to discover it by hovering.
   It mirrors under RTL, because it points at where the flyout will open and that
   side changes with the direction. */
.mi .michev { margin-inline-start: auto; color: var(--ghost); display: flex; }
[dir='rtl'] .mi .michev svg { transform: scaleX(-1); }
.mi[aria-expanded='true'] { background: var(--panel2); }
.mi[aria-expanded='true'] .michev { color: var(--wksoft); }
/* The flyout is a menu like any other, so it inherits the surface, the border
   and the shadow; it only needs to sit above its parent. */
.menu.sub { z-index: 107; }
.mi.danger { color: var(--bad); }
.mi.danger:hover { background: rgb(var(--c-bad) / .1); }
/* An impossible move is shown with its reason, not hidden. A control that
   vanishes teaches nobody why it was unavailable. */
.mi.off { color: var(--ghost); cursor: default; }
.mi.off:hover { background: none; }
.menu { min-width: 190px; }
.msep { height: 1px; background: var(--line); margin: 5px 3px; }
.mhead { padding: 6px 9px 3px; font-size: 10px; text-transform: uppercase; letter-spacing: .08em; color: var(--ghost); }
.mi input { border: 0; background: var(--ink); border-radius: 6px; padding: 6px 8px; color: var(--paper); font: inherit; font-size: 13px; width: 100%; outline: none; }

/* ---------------- toast ---------------- */
.toast {
  position: fixed; inset-inline: 0; bottom: 22px; z-index: 95;
  display: flex; justify-content: center; pointer-events: none;
}
.toast > div {
  pointer-events: auto; display: flex; align-items: center; gap: 12px;
  border: 1px solid var(--line); border-radius: 12px; background: var(--panel2);
  box-shadow: var(--shadow-card); padding: 10px 14px; font-size: 13px; color: var(--paper);
  max-width: calc(100vw - 32px);
}
.toast button { border: 0; background: none; color: var(--wksoft); font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; }

.warnbar { margin-top: 14px; border-radius: 10px; padding: 9px 12px; font-size: 12px; border: 1px solid rgb(var(--c-warn) / .4); background: rgb(var(--c-warn) / .08); color: var(--warn); }

/* ==================================================================
   PHONE. Everything below 768px — the same breakpoint PortalNav.tsx
   uses to swap its sidebar for the bottom bar, so the product has one
   phone threshold rather than two.

   EVERY RULE IN THIS BLOCK IS GATED. Desktop does not move.
   ================================================================== */
@media (max-width: 767.98px) {

/* ---------------- the table stops being a table ----------------
 *
 * WHAT COLLAPSES IS THE COLUMNS, NOT THE TREE.
 *
 * Two other phone shapes were considered and both were rejected for the same
 * reason. Cards-with-a-breadcrumb (sketched in preview-subtasks.html) and
 * drill-in navigation each move depth off the list — the first makes it
 * implicit, the second makes it a tap away. HANDOVER section 2 records exactly
 * what that costs: cascading sub-tasks shipped inside the side panel and
 * production measured 14 checklist rows and ZERO nested. The diagnosis was
 * "in a drawer, nesting is somewhere you go; in a table it is something you do
 * without leaving the list". A phone shape that hides depth behind a tap
 * rebuilds the drawer on the smaller screen and would reproduce that result.
 *
 * The second fact rules out the usual excuse for cards. preview-subtasks.html
 * measures that a 360px screen carries NINE levels of 18px indent before a
 * label drops under 120px. Width was never the constraint on depth. It is the
 * constraint on COLUMNS. So the nine columns collapse and the tree survives
 * intact: same indentation, same disclosure toggle in the list, same
 * "New sub-page" without leaving it.
 *
 * The row wraps to two lines instead of scrolling sideways. It gains height
 * rather than width, which is the argument PortalNav.tsx already makes for the
 * nav: a vertical list cannot outgrow its container the way a horizontal one
 * does, and height is cheap in the direction people already scroll.
 */
.tt { display: block; min-width: 0 !important; }
.tt colgroup, .tt thead { display: none; }
.tt tbody { display: block; }
/* Block layout on the phone, so the sticky lives on the tfoot itself rather than
   on cells that are no longer laid out as a row. */
.tt tfoot { display: block; position: sticky; bottom: 0; z-index: 4; background: var(--panel); }
.tt tfoot td { position: static; border-top: 0; }

/* --glyphs is the width of the disclosure toggle and the item glyph that sit
   before the name. The row is indented by the depth PLUS that run, and the name
   cell is pulled back over it, so line two starts under the name text rather
   than under the toggle. A meta line that begins to the left of the thing it
   describes reads as belonging to the row above it. */
.tt tbody tr {
  --glyphs: 54px;
  display: flex; flex-wrap: wrap; align-items: center; column-gap: 8px; row-gap: 4px;
  padding-block: 8px; padding-inline: calc(var(--pad, 12px) + var(--glyphs)) 8px;
  border-bottom: 1px solid var(--line); min-height: 56px;
}
.tt tbody tr > td {
  display: flex; align-items: center; height: auto; padding: 0;
  border-bottom: 0; overflow: visible;
}
/* Line one: the name takes the whole line so nothing else can join it, and the
   row menu holds the end. Its outer width is (line - 52px), the 44px menu plus
   the 8px gap, which is exactly what forces the fields onto line two. */
.tt tbody tr > td.name {
  flex: 1 1 auto; width: calc(100% + var(--glyphs) - 52px);
  margin-inline-start: calc(var(--glyphs) * -1);
  padding-inline-start: 0; position: static;
}
/* The row does the indenting on the phone, so the cell must not do it again. */
.tt .ncell { padding-inline-start: 0; }
.tt .name::after { display: none; }
.tt tbody tr > td.actscell { flex: 0 0 44px; order: 2; justify-content: flex-end; }

/* Line two: the fields that drive triage, in the row's own indent.
 * Owner, client and department are dropped here — they are the three fields
 * that INHERIT (HANDOVER 6.1), so they print the same value on every row of a
 * project and buy no information at this width. Assignees is kept because it
 * is the one person-field that deliberately does NOT inherit, so it is the one
 * that varies row to row. All six dropped fields remain on the item page. */
/* Addressed by key, never by position: the columns can be reordered (5.30), so
   nth-child(2) is whatever the reader dragged there this morning. */
.tt tbody tr > td[data-col="owner"],
.tt tbody tr > td[data-col="client"],
.tt tbody tr > td[data-col="dept"] { display: none; }
.tt tbody tr > td[data-col="assignees"] { order: 3; }
.tt tbody tr > td[data-col="priority"] { order: 4; }
.tt tbody tr > td[data-col="due"] { order: 5; }
.tt tbody tr > td[data-col="status"] { order: 6; }

/* An unset field on line two draws as an em dash, and on a phone that dash has
   no column header over it to say what is unset — three of them in a row read
   as damage rather than as absence. In a table the dash is legible because the
   header names the column; off the table it names nothing, so an unset field
   simply does not draw here. Setting one is a tap away on the item page, which
   is where the other twelve properties live anyway. Status always draws: it has
   no empty state. */
.tt tbody tr > td:has(> .cellbtn > .dash),
.tt tbody tr > td:has(> .person > .dash) { display: none; }

/* The "New sub-page" affordance keeps its own line and its own indent. */
.tt tr.newrow { min-height: 0; }
.tt tr.newrow > td:not(.name) { display: none; }
.tt tr.newrow > td.name { width: 100%; }
.tt tr.newrow .newbtn { min-height: 40px; }

/* Sorting and column resizing are both header controls, and the header does
   not survive the collapse. The grips are disabled rather than merely hidden:
   a touch-drag on one writes into the per-user stored column widths
   (HANDOVER 5.18) and would corrupt the reader's desktop layout. */
.colgrip { display: none; }

/* Touch. The row is 56px; the controls inside it are brought up to match.
   The disclosure toggle is the control that makes the tree legible, so it is
   the one that gets the most: full row height, and wide enough to hit without
   catching the name next to it. */
.tw { width: 32px; height: 40px; border-radius: 6px; }
.pen { display: none; }          /* rename moved into the row sheet */
.acts { opacity: 1; }
.acts .act[data-add], .acts .act[data-del] { display: none; }  /* both are in the sheet */
.act { width: 44px; height: 44px; }
.tt td .chip, .tt td .cellbtn, .tt td .person { min-height: 34px; }
/* An unset priority or due date is a single em dash, which is a 23px target.
   The cell keeps the dash — HANDOVER 5.4 reserves "Not measured" for a figure
   that was sought, and an em dash is how genuinely-unset is drawn — but the
   button around it is widened to something a thumb can land on. The board is
   where triage happens, so a field drawn here has to be settable here
   (HANDOVER 5.19). */
.tt td .cellbtn, .tt td .person { min-width: 44px; justify-content: center; }
.nlink, .nin { padding-block: 8px; font-size: 14px; }
/* The name is long-pressable, so the browser's own selection callout has to
   be kept out of the way or it lands on top of the sheet. */
.tt tbody tr > td.name { -webkit-touch-callout: none; user-select: none; }
.nin { user-select: text; }

/* ---------------- menus become bottom sheets ----------------
 * Anchored popovers are the desktop answer: on a 390px screen they clip, and
 * they land wherever the control is rather than where the thumb is. This is
 * the same shape PortalNav.tsx gives its "More" sheet below 768px — rounded
 * top, panel ground, capped at 80dvh, scrolling inside itself — and it uses
 * the same work-sheet-in keyframe out of globals.css, so the module has one
 * mobile idiom rather than a second one invented here.
 * The slash menu is excluded: it is caret-anchored on purpose, and a sheet
 * would cover the block being typed into. */
.menuscrim { position: fixed; inset: 0; z-index: 103; background: rgb(var(--c-scrim) / .6); }
.menu.sheet {
  position: fixed; inset-inline: 0; inset-block-start: auto; bottom: 0; z-index: 90;
  max-width: none !important; width: auto;
  max-height: 80dvh; overflow-y: auto; overscroll-behavior: contain;
  border: 0; border-top: 1px solid var(--line);
  border-radius: 16px 16px 0 0; padding: 8px 8px calc(env(safe-area-inset-bottom) + 12px);
}
.menu.sheet .mi { min-height: 46px; font-size: 15px; padding: 10px 12px; }
.menu.sheet .mi.off { min-height: 46px; padding: 10px 12px; }
.menu.sheet .mhead { padding: 10px 12px 4px; }
/* The "there is more" line sits under the list, so it reads as a footer rather
   than as a second heading. */
.mhead.slashmore { border-top: 1px solid var(--line); margin-top: 4px; padding-top: 6px; }
/* A cell of an advanced table is a body, so it needs the room a body needs and
   none of the page's own indent. The grip is hidden until the cell is hovered:
   a drag handle on every cell of a six-column table is more chrome than table. */
.tcellb { min-width: 120px; padding: 2px; }
.tcellb .blk { margin-inline-start: 0 !important; }
.tcellb .bgrip { opacity: 0; }
.tcellb:hover .bgrip, .tcellb .blk:focus-within .bgrip { opacity: 1; }
.tcellb .blk.h1 .btext, .tcellb .blk.tg1 .btext { font-size: 17px; }
.tcellb .blk.h2 .btext, .tcellb .blk.tg2 .btext { font-size: 15px; }
.tcellb .blk.h1, .tcellb .blk.h2, .tcellb .blk.h3,
.tcellb .blk.tg1, .tcellb .blk.tg2, .tcellb .blk.tg3 { margin-top: 0; }
.menu.sheet .msep { margin: 6px 3px; }
.menu.sheet .mi input { padding: 10px 12px; font-size: 15px; }

/* ---------------- the peek is the whole screen ----------------
 * Width stops are meaningless here: at a third the 340px floor already
 * takes the screen, so every stop looks the same. The drag edge does not
 * render at all rather than sitting there doing nothing; the Open button
 * stays — the full page is still a different place on a phone.
 * The stored width is NOT rewritten — it is the reader's desktop preference
 * (HANDOVER 5.18) and a phone visit must not overwrite it, so this is done
 * with !important over the inline width rather than in JavaScript. */
.peek { width: 100% !important; inset-inline: 0; min-width: 0; border-inline-start: 0; }
.peekgrip { display: none; }
.peek .pbody, .peek.full .pbody { padding: 16px 14px calc(env(safe-area-inset-bottom) + 40px); }
/* The way back up a level. It already names its destination rather than saying
   "Back" — the finding BackLink.tsx records — so it only needs room to breathe
   and permission to shrink instead of pushing Close off the edge. */
.phead { padding: 8px 10px; gap: 6px; }
.phead .ctl { min-height: 44px; }
.phead .ctl[data-goto] { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.phead .ctl[data-close] { width: 44px; height: 44px; flex-shrink: 0; }
.itemhead h2, .peek.full .itemhead h2, .pagewrap .itemhead h2 { font-size: 20px; }
.pgtitle { font-size: 20px; }
.crumbs { font-size: 11px; }
/* TWO ACROSS ON A PHONE, and the odd one out takes the width rather than half of
   it. CEO, 2026-08-25: "on mobile it would be two in a row". The stored split is
   ignored here on purpose — a 70/30 written for a six-column row means nothing
   once it is two-up, and honouring it would give one unreadable column. The
   handle goes too: there is nothing to drag when the pairing is not the author's.
   Wrapping preserves the left-to-right order, so a row of five reads 1 2 / 3 4 / 5. */
.brow { flex-wrap: wrap; }
.bcol { flex: 1 1 calc(50% - 3px); }
.brow > .bcol:last-child:nth-child(odd) { flex-basis: 100%; }
.bsplit { display: none; }
/* Sixteen property rows at 120px of label leave nothing for the value — and a
   118px label at 390px wraps to two lines with the value floating beside the
   middle of it, which reads as broken alignment rather than as a field. The
   label goes on its own line and the value under it, both with the full width:
   gain height, never width, the same answer the table rows give at this size
   (5.25).
   `flex: 0 0 118px` HAD TO GO WITH THE ROW DIRECTION. A flex basis is measured
   along the main axis, so in a column it is a HEIGHT — 118px of empty label. */
/* And the rows take their content's height and no more. `.props` is a grid, so
   a box taller than its content stretches the implicit rows to fill it. */
.props { align-content: start; }
/* A GRID, NOT A COLUMN, SO THE ROW MENU STAYS ON THE LABEL'S LINE. Stacked as a
   plain column the three-dots button became a third line of its own — 44px,
   because a touch target has to be 44px — and every property row was a hundred
   pixels tall. Label and menu share the first line; the value has the width to
   itself underneath. */
.prow { display: grid; grid-template-columns: 1fr auto; gap: 2px 8px;
  align-items: center; min-height: 44px; padding-block: 6px; }
.pkey { width: auto; flex: none; padding-top: 0; grid-column: 1; }
.propmore { grid-column: 2; grid-row: 1; opacity: 1; }
.pval { flex: none; width: 100%; grid-column: 1 / -1; }
.frow { flex-wrap: wrap; }
.fkey { width: auto; flex: 0 0 100px; }
.kid { padding: 12px 12px; flex-wrap: wrap; }
.composer { flex-wrap: wrap; }
.composer .send { align-self: stretch; width: 100%; min-height: 44px; }
.msg .del, .linkbar { opacity: 1; }   /* no hover on touch; both are words, not clutter */
/* The block grip carries data-bmenu as well as data-bdrag — it is the only way
   into "turn into", duplicate, move and delete. Hiding it would leave the page
   body type-only on a phone, so it stays and is sized for a thumb. Its drag
   half is HTML5 drag and is simply inert on touch; the menu half is the part
   that matters, and the aria-label already says "Block actions". */
.bgrip { opacity: 1; width: 32px; height: 40px; margin-top: 0; }

/* ---------------- toolbar ----------------
 * Two controls on one 390px line is two cramped controls. The view chips take a
 * line of their own and scroll inside it; the filter gets the second. Creating a
 * project lives at the end of the list of projects, not up here. */
.toolbar { flex-wrap: wrap; overflow: visible; padding: 10px; gap: 8px; }
.toolbar .spacer { display: none; }
.viewbar {
  display: flex; flex: 1 0 100%; overflow-x: auto; overscroll-behavior-x: contain;
  justify-content: flex-start; scrollbar-width: none;
}
.viewbar::-webkit-scrollbar { display: none; }
.viewbtn { padding: 9px 12px; flex-shrink: 0; }
/* The input, not the border around it, is what a tap has to land on, so the
   field takes the height and the wrapper keeps only enough padding to draw. */
.toolbar .search { flex: 1 1 140px; min-width: 0; padding-block: 4px; }
.toolbar .search input { min-height: 36px; }
.toolbar .btn-primary { flex: 0 0 auto; min-height: 44px; padding-inline: 16px; }

/* ---------------- header ----------------
 * The identity control loses its name and keeps its avatar and role. It is a
 * prototype stand-in that disappears in production (HANDOVER 5.10); the save
 * stamp does not, so that is what keeps the words. */
.wrap { padding: 0 12px 96px; }
.pagebody { padding: 16px 12px 40px; }
.topbar { padding: 10px 12px; gap: 8px; }
.topbar h1 { font-size: 11px; }   /* the trail's phone size */
.whoami .whoname { display: none; }
.whoami { padding: 3px 8px 3px 3px; min-height: 40px; }
.ctl.iconbtn { width: 44px; height: 44px; }

/* ---------------- the other three views ---------------- */
/* Board already scrolls inside its own container, which is the right answer on
   a phone; the column is narrowed so a second one is visible at the edge and
   the strip reads as scrollable rather than as the only column there is. */
.kanban { padding: 12px; }
.kcol { flex: 0 0 82%; }
/* Timeline gave a fixed 240px to the name, which at 336px left 96px of track.
   The name takes a share instead of a fixed width. */
/* On the phone the name takes what is left rather than a fixed share: the cell
   carries a disclosure, a glyph and an indent before the first letter, and at a
   fixed 47% a fourth-level name came out as four characters. The track keeps a
   fixed 40% so every bar is still measured against the same width. */
.tlrow > .ncell, .tlfoot > .ncell { flex: 1 1 auto; min-width: 0; }
.tlrow > .tltrack { flex: 0 0 40%; }
.tlrow { padding-inline: 10px; }
.calhead { padding: 10px; }
.cal-cell { min-height: 72px; padding: 4px 3px; }
.cal-dow { padding: 6px 3px; font-size: 9px; }
.cal-item { font-size: 10px; padding: 3px 4px; }

/* ---------------- the keyboard strip ----------------
 * There is no keyboard, so the shortcut legend is noise on a phone. Removed at
 * the CEO's instruction. Every shortcut it names still works if a keyboard is
 * attached; only the legend goes. */

.tpls { grid-template-columns: 1fr; }
.adminpanel { width: calc(100vw - 20px); }
.admrow { flex-wrap: wrap; gap: 10px; }
}
