/* ============================================================================
   zplex-ddb - the chrome.
   ----------------------------------------------------------------------------
   Every value here is a token from the design system (design-specs/tokens.css,
   reached by a symlink into x2203). Not one raw colour: the contract test
   fails the build if one appears.

   The frames, in order of definition:
     the top bar    the wordmark, the scope, the user, the gear
     the rail       the section tree, with twisties
     the content    the prose - a SERIF face, which is most of the character
     the tab bar    on a phone, the top scopes, and the way back to the rail

   The host layer (ddb-host.css) owns the surfaces; this file owns the shapes.
   ========================================================================== */

@import url("ddb-host.css");

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--zx-bg);
  color: var(--zx-fg);
  font: var(--zx-fs-body) / var(--zx-lh-body) var(--zx-font-sans);
  -webkit-font-smoothing: antialiased;
}

/* --- the app shell ------------------------------------------------------- */

#app {
  display: grid;
  /* minmax(0, 1fr), NOT 1fr. A `1fr` track is `minmax(auto, 1fr)`, and
     `auto` means the track may not be smaller than the CONTENT's
     min-content width - so one unbreakable token anywhere in the page
     would widen the whole column and give the PAGE a sideways
     scrollbar. The content column has to be allowed to be narrower
     than what it holds; the wide things inside it scroll in their own
     scroller instead. */
  grid-template-columns: var(--zx-rail) minmax(0, 1fr);
  grid-template-rows: var(--zx-appbar-h) 1fr;
  min-height: 100vh;
}

/* --- THE TOP BAR --------------------------------------------------------- */

.appbar {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: var(--zx-s3);
  padding: 0 var(--zx-bar-pad) 0 var(--zx-s4);
  height: var(--zx-appbar-h);
  background: var(--zx-panel);
  border-bottom: 1px solid var(--zx-line);
  position: sticky;
  top: 0;
  z-index: 20;
}

.wordmark {
  display: flex;
  align-items: baseline;
  gap: 1px;
  font-weight: 700;
  font-size: 15px;
  letter-spacing: .01em;
  text-decoration: none;
  white-space: nowrap;
}
.wordmark .wm-word { color: var(--zx-wm-word); }
.wordmark .wm-pipe { color: var(--zx-wm-pipe); font-weight: 900; margin: 0 .35em; }
.wordmark .wm-product { color: var(--zx-wm-product); }
.wordmark .wm-sm { font-size: .72em; }

.mark {
  width: 30px;
  height: 30px;
  flex: none;
  object-fit: contain;
}

.scope {
  color: var(--zx-ink-dim);
  font-size: var(--zx-fs-meta);
  border-left: 1px solid var(--zx-line);
  padding-left: var(--zx-s3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.appbar .spacer { flex: 1; }

.who {
  display: inline-flex;
  align-items: center;
  gap: var(--zx-s2);
  height: var(--zx-control-h);
  padding: 0 var(--zx-s3);
  border-radius: var(--zx-r-pill);
  border: 1px solid var(--zx-line);
  background: var(--zx-raised);
  color: var(--zx-ink-dim);
  font-size: var(--zx-fs-sm);
}
.who b { color: var(--zx-fg); font-weight: 600; }

/* the gear, and the three colour modes */
.gear {
  position: relative;
}
.gear-panel {
  position: absolute;
  right: 0;
  top: calc(var(--zx-control-h) + var(--zx-s2));
  min-width: 150px;
  padding: var(--zx-s1);
  background: var(--zx-raised);
  border: 1px solid var(--zx-line);
  border-radius: var(--zx-r-md);
  box-shadow: var(--zx-sh-2);
  z-index: 40;
}
.gear-panel[hidden] { display: none; }
.gear-panel button {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: var(--zx-s3);
  padding: var(--zx-s2) var(--zx-s3);
  border: 0;
  border-radius: var(--zx-r-sm);
  background: none;
  color: var(--zx-fg);
  font: inherit;
  font-size: var(--zx-fs-sm);
  cursor: pointer;
  text-align: left;
}
.gear-panel button:hover { background: var(--zx-bg-hover); }
.gear-panel button[aria-checked="true"] { color: var(--zx-accent); font-weight: 600; }
.gear-panel .tick { color: var(--zx-accent); }

/* --- THE RAIL ------------------------------------------------------------ */

.rail {
  grid-row: 2;
  position: sticky;
  top: var(--zx-appbar-h);
  /* dvh, not vh: on a phone the URL bar comes and goes, and 100vh is
     the height of the viewport with the bar HIDDEN - so the foot of the
     rail was unreachable. */
  height: calc(100dvh - var(--zx-appbar-h));
  overflow: auto;
  overflow-x: hidden;
  padding: var(--zx-s3) 0 var(--zx-s10);
  background: var(--zx-panel);
  border-right: 1px solid var(--zx-line);
}

.rail .filter {
  width: calc(100% - var(--zx-s5));
  margin: 0 var(--zx-s3) var(--zx-s3);
  height: var(--zx-control-h);
  padding: 0 var(--zx-s3);
  border-radius: var(--zx-r-sm);
  border: 1px solid var(--zx-line);
  background: var(--zx-raised);
  color: var(--zx-fg);
  font: inherit;
  font-size: var(--zx-fs-sm);
}
.rail .filter:focus-visible { outline: 2px solid var(--zx-accent); outline-offset: 1px; }

.rail nav { display: grid; }

.node .row {
  display: flex;
  align-items: center;
  gap: 2px;
  min-height: var(--zx-row-h);
  padding: 0 var(--zx-row-pad-x);
}
.node .row .link {
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
  padding: 6px var(--zx-s2);
  border-radius: var(--zx-r-sm);
  color: var(--zx-fg-dim);
  text-decoration: none;
  border-bottom: 0;
  font-size: var(--zx-fs-sm);
  display: flex;
  align-items: center;
  gap: var(--zx-s2);
  line-height: 1.25;
}
.node .row .link:hover { background: var(--zx-bg-hover); color: var(--zx-fg); }
.node .row .link.active { background: var(--zx-accent-soft); color: var(--zx-accent); font-weight: 600; }
.node.group > .row > .link { color: var(--zx-fg); font-weight: 600; }
.node.group > .row > .link.active { color: var(--zx-accent); }
.nav-index {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  min-width: 34px;
  height: 20px;
  padding: 0 7px;
  border: 1px solid var(--zx-accent-line);
  border-radius: var(--zx-r-pill);
  background: var(--zx-accent-soft);
  color: var(--zx-accent);
  font-family: var(--zx-font-mono);
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.02em;
}
.nav-label {
  min-width: 0;
  overflow-wrap: anywhere;
}

.tw {
  flex: 0 0 var(--zx-slot-arrow);
  /* 24px of target, on a glyph that is 18. WCAG 2.2 AA asks for 24 on
     the shortest side, and this is the twisty on every section of the
     rail. The glyph stays the design system's 18; the TARGET is wider,
     so a thumb does not have to hit the arrow itself. */
  width: 24px;
  height: var(--zx-row-h-sm);
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--zx-r-xs);
  background: none;
  color: var(--zx-ink-soft);
  cursor: pointer;
  font-size: 10px;
  line-height: 1;
}
.tw:hover { background: var(--zx-bg-hover); color: var(--zx-fg); }
.tw:focus-visible { outline: 2px solid var(--zx-accent); outline-offset: 1px; }

.kids {
  margin-left: var(--zx-row-indent);
  border-left: 1px solid var(--zx-line);
  padding-left: var(--zx-s1);
  display: grid;
}
.node.group.collapsed > .kids { display: none; }

.rail .foot {
  margin: var(--zx-s4) var(--zx-s4) 0;
  color: var(--zx-ink-soft);
  font-size: var(--zx-fs-meta);
}

/* --- THE CONTENT --------------------------------------------------------- */

/* ============================================================================
   THE CHAPTERS - the page as a tree of collapsible divisions.
   ----------------------------------------------------------------------------
   A chapter is a section, a heading with a twisty, and a body. Three
   levels, each indented by a fixed step, and the index shown beside the
   title so a heading is an address and not just a title.

   The collapse state is in the DOM (`data-zx-collapsed`), so the CSS can
   draw the arrow from the same fact the button announces. An icon that
   can disagree with the state is a bug waiting to happen.
   ========================================================================== */

.zx-chapter { margin: 0; }
.zx-chapter + .zx-chapter { margin-top: var(--zx-s5); }

.zx-chapter-heading {
  display: flex;
  align-items: baseline;
  gap: var(--zx-s2);
  scroll-margin-top: calc(var(--zx-appbar-h) + var(--zx-s4));
}

/* the twisty. It is a real button, so it is reachable by keyboard. */
.zx-chapter-toggle {
  flex: none;
  align-self: center;
  /* 24x24, not 22x22. WCAG 2.2 AA asks a target to be 24px on its
     shortest side, and this is the control on EVERY heading of every
     chapter - the one a reader touches most on a phone. */
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  border: 1px solid var(--zx-line);
  border-radius: var(--zx-r-xs);
  background: var(--zx-raised);
  color: var(--zx-fg-dim);
  cursor: pointer;
  padding: 0;
}
.zx-chapter-toggle:hover { color: var(--zx-fg); border-color: var(--zx-accent-line); }
.zx-chapter-toggle:focus-visible { outline: 2px solid var(--zx-accent); outline-offset: 1px; }

/* THE ARROW, drawn from the state. A triangle that points right when the
   chapter is closed and down when it is open. */
.zx-chapter-arrow {
  width: 0;
  height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform var(--zx-dur-1) var(--zx-ease);
}
.zx-chapter[data-zx-collapsed="0"] > .zx-chapter-heading .zx-chapter-arrow {
  transform: rotate(90deg);
}

/* the index, beside the title */
.zx-chapter-index {
  flex: none;
  font-family: var(--zx-font-mono);
  font-size: 0.8em;
  color: var(--zx-ink-dim);
  background: var(--zx-bg-hover);
  border: 1px solid var(--zx-line);
  border-radius: var(--zx-r-xs);
  padding: 0.05em 0.4em;
  white-space: nowrap;
}
.zx-chapter-title { min-width: 0; }

/* a COLLAPSED chapter hides its body. The heading stays - that is the
   outline the reader navigates by. */
.zx-chapter[data-zx-collapsed="1"] > .zx-chapter-body { display: none; }

/* the indentation of the three levels. Each level is one step in, so a
   sub-chapter reads as belonging to the chapter above it. */
.zx-chapter-2 > .zx-chapter-heading { padding-left: var(--zx-chapter-indent); }
.zx-chapter-3 > .zx-chapter-heading { padding-left: calc(var(--zx-chapter-indent) * 2); }
.zx-chapter-2 { margin-top: var(--zx-s4); }
.zx-chapter-3 { margin-top: var(--zx-s3); }

/* the sub-levels are quieter, because they are a step down. */
.zx-chapter-2 > .zx-chapter-heading { font-size: 18px; }
.zx-chapter-3 > .zx-chapter-heading { font-size: 16px; }

/* --- THE SOURCE, AND THE SOURCE NOTES -----------------------------------
   The three facts a reader needs to trust a page: which file it came
   from, how it is made, and which build they are reading. They sit at
   the foot of the page, quiet, because they are about the page rather
   than part of it. */
.zx-provenance {
  margin-top: var(--zx-s8);
  padding-top: var(--zx-s4);
  border-top: 1px solid var(--zx-line);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--zx-s5);
  max-width: var(--zx-frame);
}
.zx-provenance-part { min-width: 0; }
.zx-provenance-head {
  font-family: var(--zx-font-sans);
  font-size: var(--zx-fs-meta);
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--zx-ink-soft);
  margin: 0 0 var(--zx-s2);
  padding: 0;
  border: 0;
}
.zx-provenance-lead {
  margin: 0 0 var(--zx-s2);
  font-family: var(--zx-font-mono);
  font-size: var(--zx-fs-meta);
  color: var(--zx-fg-dim);
  line-height: 1.5;
  overflow-wrap: anywhere;
}

/* --- THE SAVE BUTTON, in the top bar ----------------------------------- */

.zx-save-ui {
  font: inherit;
  font-size: var(--zx-fs-meta);
  color: var(--zx-ink-dim);
  background: var(--zx-raised);
  border: 1px solid var(--zx-line);
  border-radius: var(--zx-r-pill);
  padding: 0.25em 0.8em;
  cursor: pointer;
  white-space: nowrap;
}
.zx-save-ui:hover { color: var(--zx-fg); border-color: var(--zx-accent-line); }
.zx-save-ui:disabled { opacity: .6; cursor: default; }
.zx-save-ui[data-zx-state="ok"] { color: var(--zx-state-ok); border-color: var(--zx-state-ok); }
.zx-save-ui[data-zx-state="warn"] { color: var(--zx-state-warn); border-color: var(--zx-state-warn); }

/* screen-reader-only text - the label a sighted reader gets from the
   arrow, and a blind reader gets from the button. */
.zx-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* --- THE TWO BUTTONS, beside the page tabs ------------------------------ */

.zx-chapter-tools {
  display: inline-flex;
  align-items: center;
  gap: var(--zx-s2);
  margin-left: var(--zx-s4);
}
.zx-chapter-tools button {
  font: inherit;
  font-size: var(--zx-fs-meta);
  color: var(--zx-ink-dim);
  background: none;
  border: 1px solid var(--zx-line);
  border-radius: var(--zx-r-pill);
  /* 24px of height, so "Expand all" is a target a thumb can hit. It
     was 23px, which is one pixel short of the WCAG 2.2 AA minimum. */
  min-height: 24px;
  padding: 0.1em 0.7em;
  cursor: pointer;
}
.zx-chapter-tools button:hover { color: var(--zx-fg); border-color: var(--zx-accent-line); }

#doc {
  grid-row: 2;
  min-width: 0;
  padding: var(--zx-s8) var(--zx-s8) var(--zx-s10);
}

/* THE CLOSE BUTTON - a corner cross, and the rule behind it.

   THE RULE: anything that OPENS must be CLOSABLE, and the way it is
   closed is a cross in its corner. Not a "Cancel" in a row of buttons,
   not the thing that opened it, and not a click somewhere else on the
   page - a cross, in the corner, in the same place every time.

   WHY IT IS A RULE AND NOT A STYLE. Because the ddb had five ways of
   closing things and none of them agreed: the gear panel closed on an
   outside click, the rail drawer closed on the button that opened it,
   the note editor closed on a Cancel button that sat in a row, and the
   gate card could not be closed at all. A reader who learns one way
   cannot use it on the next thing.

   So there is ONE close button. `.zx-close`, in the corner of whatever
   it closes. It is a real button, so it is reachable by keyboard, and
   it is 28px, so it is a target a thumb can hit.

   The markup is the same everywhere:
     <button type="button" class="zx-close" data-zx-close="rail"
             aria-label="Close the sections"></button>
   and the panel it closes says so:
     <aside class="rail" data-zx-dismissible="rail">

   `data-zx-close` NAMES the panel; the panel owns how it goes away.
   One convention, so the next thing that opens in this estate already
   knows how it is meant to be closed. */
.zx-close {
  position: absolute;
  top: var(--zx-s2);
  right: var(--zx-s2);
  z-index: 2;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--zx-line);
  border-radius: var(--zx-r-md);
  background: var(--zx-raised);
  color: var(--zx-fg-dim);
  cursor: pointer;
}
.zx-close:hover { color: var(--zx-fg); border-color: var(--zx-accent-line); }
.zx-close:focus-visible { outline: 2px solid var(--zx-accent); outline-offset: 2px; }
/* The cross, drawn - so the button is the same shape everywhere and does
   not depend on a font having the right glyph. */
.zx-close::before,
.zx-close::after {
  content: '';
  position: absolute;
  width: 13px;
  height: 1.5px;
  background: currentColor;
  border-radius: 1px;
}
.zx-close::before { transform: rotate(45deg); }
.zx-close::after { transform: rotate(-45deg); }

/* The panels that can be closed, and where their cross sits.
   `:where()` so this rule has NO specificity: a panel that is already
   positioned keeps its own positioning (the gear panel is absolutely
   placed, and must stay so), and a panel that is not becomes the
   containing block for its own cross. */
:where(.zx-dismissible) { position: relative; }

/* THE RAIL'S CROSS is for the DRAWER. On a wide screen the rail is not
   dismissible - it is the spine, and it is always there - so there is
   nothing to close and the cross would only be in the way. */
.rail > .zx-close { display: none; }
@media (max-width: 1004px) {
  .rail > .zx-close { display: grid; }
  /* and the filter moves out from under it */
  .rail > .filter { margin-right: var(--zx-s8); }
}

/* ============================================================================
   THE DOCUMENT GRID — two width classes, one grid, and no breakpoints.
   ============================================================================

   WHAT WAS WRONG, TWICE OVER.

   First: the reading measure was chosen by VIEWPORT. 76ch, then 84ch
   above 1500px, then 90ch above 1800px - three fixed widths picked at
   three fixed pixel widths. That is not responsive design; it is a
   staircase of hard-coded numbers, and it is why the prose still read
   as a phone layout on a laptop.

   Second, and worse: the widths were applied ELEMENT BY ELEMENT. A
   list of selectors here saying "a paragraph is the measure, a table
   is the frame, a pre is the frame", and every new kind of block meant
   another line in that list - and the list had already lost one battle
   to specificity (`#doc > *` beat `.page-notes`, so the note form was
   1440px wide while the code said it was the measure).

   THE GRID IS THE SYSTEM. The document is ONE grid with two columns:
   a reading column and the full width. Every block is placed in one of
   them by where it sits, not by a rule that has to remember what kind
   of block it is.

     [full-start] [ the reading column ] [content-end] [ the rest ]
     [full-end]

   PROSE lands in the reading column. TABLES, CODE and the provenance
   footer land in the full column. The rule is one line, and a block
   that nobody thought about lands in the reading column by default,
   which is the safe place for it to land.

   WHY A GRID AND NOT A max-width ON EACH ELEMENT. Because the two have
   to share a geometry. With a max-width per element, prose and a table
   are two unrelated numbers that happen to share a left edge, and the
   reader sees a ragged right. With one grid, the reading column and
   the full column are the SAME grid, so a table that breaks out of
   the prose is doing something legible rather than something arbitrary.

   WHY SUBGRID, AND NOT display: contents. The document is nested:
   document > chapter > chapter body > paragraph. A grid item can only
   be placed on ITS OWN grid's lines, so the intermediate boxes have to
   either disappear (display: contents, which throws away the box AND
   the element's role in the accessibility tree - the chapter is a
   <section>, and a section is a landmark) or PASS THEIR PARENT'S TRACKS
   DOWN. Subgrid does the second: a child that spans the full column
   and declares `grid-template-columns: subgrid` inherits the parent's
   lines, so a paragraph two levels down can still be placed on the
   document's reading column. Every box keeps its box. Every landmark
   keeps its landmark. */
/* THE PAGE GRID - the document, and the outline beside it. They are
   SIBLINGS: a grid item's sticky position is bounded by its own grid
   area, so an outline inside the document's grid could only ever stick
   within one row of it. Here the outline's area is the whole page. */
.zx-page {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 0;
  max-width: var(--zx-frame);
  margin-inline: auto;
}
.zx-doc {
  display: grid;
  grid-template-columns:
    [full-start content-start] min(var(--zx-measure), 100cqi) [content-end]
    minmax(0, 1fr) [full-end];
  min-width: 0;
}

/* THE OUTLINE COLUMN. On a wide enough column the page carries an
   "on this page" list on the right.

   The column is ALWAYS in the grid, and it is zero wide until there is
   room for it. That is deliberate: a grid whose tracks change with a
   media query would give the wide blocks a different width on
   different screens for no reason. The outline takes its width OUT of
   the leftover, so a table that wants the whole document still gets
   the whole document - minus the outline, which is the trade, made
   once, in one place.

   The threshold is a CONTAINER width, not a viewport width, because the
   question is "is there room in THIS column", and the column is what
   the rail has left. */
@container ddb (min-width: 1150px) {
  .zx-page {
    grid-template-columns: minmax(0, 1fr) min(var(--zx-outline-w), 22cqi);
  }
}

/* THE CONTAINER is the COLUMN, one level up - `#doc` on the portal and
   `.content` on the client. It has to be an ANCESTOR of the grid,
   because a container cannot be queried by the element that declares
   it: `100cqi` inside `.zx-doc` has to resolve against a container
   above it.

   `min(90ch, 100cqi)` is the whole measure, and it is a LENGTH
   EXPRESSION - the browser interpolates it continuously as the column
   grows, rather than jumping between states at a breakpoint. A narrow
   column gets a narrow measure because there is no more room; a wide
   one gets the full 90 characters because that is as wide as a line of
   text should be. */
#doc {
  container-type: inline-size;
  container-name: ddb;
}

/* THE DEFAULT IS THE READING COLUMN. A block nobody thought about
   lands in the measure, which is where prose belongs. */
.zx-doc > * { grid-column: content; min-width: 0; }

/* the same default, one level down: the prose inside a chapter body */
.zx-chapter-body > * { grid-column: content; min-width: 0; }

/* THE WIDE CLASS. These are the blocks that are DATA, and data may be
   wider than a comfortable line of prose:
     a table          many columns of facts
     a code block     the author's alignment is the content
     the provenance   three columns of source, notes and build
   They break out of the reading column and use the whole document.

   The selectors are DESCENDANTS, not children, and that matters: the
   portal's document is the client's document with one <article> around
   it, so a table is two levels down in one surface and three in the
   other. A child selector fits the portal and silently fails on the
   client - which is exactly how two surfaces end up disagreeing about
   the same page. */
.zx-outline {
  grid-column: 2;
  display: none;
  position: sticky;
  top: calc(var(--zx-appbar-h) + var(--zx-s5));
  align-self: start;
  max-height: calc(100dvh - var(--zx-appbar-h) - var(--zx-s8));
  overflow-y: auto;
  padding: var(--zx-s5) 0 var(--zx-s5) var(--zx-s5);
  border-left: 1px solid var(--zx-line);
  font-family: var(--zx-font-sans);
}
@container ddb (min-width: 1150px) {
  .zx-outline { display: block; }
}
.zx-outline-head {
  font-size: var(--zx-fs-meta);
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--zx-ink-soft);
  margin: 0 0 var(--zx-s3);
}
.zx-outline-list { list-style: none; margin: 0; padding: 0; }
.zx-outline-list .zx-outline-list { padding-left: var(--zx-s3); }
.zx-outline-item { margin: 0 0 var(--zx-s2); line-height: var(--zx-lh-tight); }
.zx-outline-item a {
  display: flex;
  gap: var(--zx-s2);
  align-items: baseline;
  color: var(--zx-fg-dim);
  text-decoration: none;
  border-bottom: 0;
  font-size: var(--zx-fs-sm);
  padding: 2px 0;
}
.zx-outline-item a:hover { color: var(--zx-fg); }
.zx-outline-index {
  flex: none;
  font-family: var(--zx-font-mono);
  font-size: var(--zx-fs-meta);
  color: var(--zx-ink-soft);
}
.zx-outline-item a[data-zx-outline-current] { color: var(--zx-fg); font-weight: 600; }
.zx-outline-item a[data-zx-outline-current] .zx-outline-index { color: var(--zx-accent); }

.zx-doc .zx-wide,
.zx-doc .zx-table-scroll,
.zx-doc pre,
.zx-doc .zx-provenance,
.zx-doc .board,
.zx-doc .orientation { grid-column: full; }

/* THE CHAPTERS PASS THE TRACKS DOWN. A section spans the document and
   inherits its columns; its heading sits in the reading column, and
   its body spans the document and inherits them too, so a paragraph
   two levels down is still placed on the document's reading column. */
.zx-doc .zx-chapter {
  grid-column: full;
  display: grid;
  grid-template-columns: subgrid;
}
.zx-doc .zx-chapter > .zx-chapter-heading { grid-column: full; }
.zx-doc .zx-chapter > .zx-chapter-body {
  grid-column: full;
  display: grid;
  grid-template-columns: subgrid;
}

/* THE STRUCTURE SPANS THE DOCUMENT, THE PROSE DOES NOT.
   A heading and the rule under it belong to the document, not to the
   text column - so a section rule reaches the same edge the table
   below it reaches, and the reader sees one page rather than a text
   column with a wider thing parked next to it. The heading's TEXT
   still starts on the left edge of the reading column, because the
   heading is left-aligned inside a wider box.
   The same is true of the page's own chrome: the breadcrumb, the tab
   bar, the footer. They are the frame around the text, and a frame
   that stops halfway across the page looks like a mistake. */
.zx-doc > h1,
.zx-doc > h2,
.zx-doc > h3,
.zx-doc > h4,
.zx-doc .page-path,
.zx-doc .page-tabs,
.zx-doc .pagefoot { grid-column: full; }

/* THE CLIENT'S PROSE ARTICLE is the same idea one level down: the
   client's document is the portal's document with an <article> around
   it, and that article inherits the grid's tracks the same way.
   Named with the parent so it outranks `.zx-doc > *` whatever order
   the two stylesheets are loaded in. */
.zx-doc > .prose {
  grid-column: full-start / full-end;
  display: grid;
  grid-template-columns: subgrid;
}

/* AN IMAGE IN THE DOCUMENT is content, and it must not push the column
   wider than the column is. */
.zx-doc img { max-width: 100%; height: auto; }

/* a CODE BLOCK is data, and it scrolls rather than wrapping away. */
h1, h2, h3, h4 {
  font-family: var(--zx-font-sans);
  line-height: var(--zx-lh-tight);
  color: var(--zx-body-heading);
  margin: 1.4em 0 .45em;
}
h1 { font-size: 26px; letter-spacing: -.01em; }
h2 { font-size: 20px; padding-bottom: var(--zx-s1); border-bottom: 1px solid var(--zx-line); }
h3 { font-size: 16px; }
#doc > h1:first-child { margin-top: 0; }

p, li, td, th { font-family: var(--zx-font-serif); font-size: var(--zx-fs-prose); line-height: var(--zx-lh-prose); }
p { margin: .7em 0; }
ul, ol { margin: .7em 0; padding-left: 1.3em; }
li { margin: .3em 0; }

a { color: var(--zx-body-link); text-decoration: none; border-bottom: 1px solid var(--zx-accent-line); }
a:hover { border-bottom-color: var(--zx-body-link); }

strong { color: var(--zx-body-bold); font-weight: 700; }

code {
  font-family: var(--zx-font-mono);
  font-size: .88em;
  color: var(--zx-body-inline-code);
  background: var(--zx-body-inline-code-soft);
  border-radius: var(--zx-r-xs);
  padding: .1em .35em;
}

/* A LONG TOKEN MUST BREAK, NOT PUSH.
   An identifier like `x2701_project_mgmt_01_tsv_sqlite` is one word with
   no break opportunity in it, so it sits in the paragraph and sticks
   out past the measure, and the PAGE - not the paragraph - grows a
   sideways scrollbar. Measured: at 1024 the page was 1259px wide, 235
   of it a code span that would not break.

   `anywhere` rather than `break-word`, because `break-word` keeps the
   word's MIN-CONTENT width, and the min-content width is exactly what a
   grid track is sized by - so `break-word` would still have widened the
   column. `anywhere` shrinks the min-content too.

   Only INLINE code. A code block keeps its own rules: the block scrolls
   (see `pre` below), and it must not re-wrap, because the indentation
   and the alignment are the content. */
code:not(pre code) {
  overflow-wrap: anywhere;
  word-break: break-word;
}

/* The same rule for the prose that holds them. A path written as prose
   - a source path, a URL, a record key - is one long word too. */
#doc > p,
#doc > ul,
#doc > ol,
#doc > blockquote,
.zx-chapter-body > p,
.zx-chapter-body > ul,
.zx-chapter-body > ol,
.zx-chapter-body > blockquote { overflow-wrap: break-word; }

pre {
  background: var(--zx-code-bg);
  border: 1px solid var(--zx-line);
  border-radius: var(--zx-r-lg);
  padding: var(--zx-s4);
  overflow: auto;
  max-width: 100%;
}
pre code { background: none; padding: 0; color: var(--zx-body-code-block); font-size: var(--zx-fs-sm); }
pre code[data-lang] { color: var(--zx-body-code-block-lang); }

/* THE TABLE'S OWN WIDTH.
   A table stretched to the full column is not a wide table - it is a narrow
   table with 600px of nothing between its cells, and the reader has to
   track a row across the whole screen to read it. So a table takes the
   width its CONTENT wants, and only the genuinely wide ones (the seven
   column claims table) reach the frame.

   `max-width: 100%` is the no-JS floor: the table never blows the page
   out. Inside the scroll wrapper the cap is lifted, so a table that really
   wants more than the column SCROLLS instead of being crushed. */
table {
  border-collapse: collapse;
  width: auto;
  max-width: 100%;
  margin: var(--zx-s4) 0;
  font-size: var(--zx-fs-sm);
}
.zx-table-scroll > table { max-width: none; }
th, td { border: 1px solid var(--zx-line); padding: var(--zx-s2) var(--zx-s3); text-align: left; vertical-align: top; }
th { background: var(--zx-bg-hover); font-family: var(--zx-font-sans); font-weight: 600; }

/* THE TABLE WIDGET — one treatment for EVERY table.

   The classic portal's tables come out of the markdown renderer and the
   client's come out of React, and a widget that behaves differently on the
   two surfaces is two widgets. So the rules are here, against the bare
   elements, and the client reaches them because it imports this
   stylesheet rather than copying it.

   Three parts, all of them viewing-only:

     .zx-table-scroll   the shell. A wide table scrolls inside it instead
                        of breaking the page, and the page keeps its
                        width. Added by public/table-resizer.js.
     .zx-table-fixed    a table the reader has SIZED. Fixed layout, so the
                        widths they dragged are the widths they get.
     .zx-col-resizer    the drag handle on each header. Double-click it to
                        put that column back.

   The widths live in the browser's localStorage and nowhere else. They
   are the reader's preference, not a fact of the estate, and nothing
   here writes a record. */
.zx-table-scroll {
  overflow-x: auto;
  max-width: 100%;
  /* A scrolling region that the reader reached by keyboard must SHOW
     that it has focus, or they are tabbing blind. */
  overscroll-behavior-inline: contain;
}
.zx-table-scroll:focus-visible,
.zx-doc pre:focus-visible {
  outline: 2px solid var(--zx-accent);
  outline-offset: 2px;
  border-radius: var(--zx-r-sm);
}
.zx-table-scroll > table { margin-top: 0; margin-bottom: 0; }
.zx-table-fixed { table-layout: fixed; }
.zx-table-fixed th, .zx-table-fixed td { overflow-wrap: anywhere; }

th { position: relative; }
th > .zx-col-resizer {
  position: absolute;
  top: 0;
  right: 0;
  width: 8px;
  height: 100%;
  cursor: col-resize;
  user-select: none;
  touch-action: none;
}
th > .zx-col-resizer:hover { background: var(--zx-accent-soft, var(--zx-bg-hover)); }
th > .zx-col-resizer::after {
  content: "";
  position: absolute;
  top: 25%;
  right: 3px;
  width: 2px;
  height: 50%;
  background: var(--zx-line);
  opacity: 0.55;
}
th > .zx-col-resizer:hover::after { background: var(--zx-accent); opacity: 1; }

blockquote {
  border-left: 3px solid var(--zx-accent);
  margin: var(--zx-s4) 0;
  padding: var(--zx-s1) var(--zx-s4);
  color: var(--zx-ink-dim);
  background: var(--zx-accent-soft);
  border-radius: 0 var(--zx-r-md) var(--zx-r-md) 0;
}
hr { border: 0; border-top: 1px solid var(--zx-line); margin: var(--zx-s6) 0; }

.pagefoot {
  margin-top: var(--zx-s10);
  padding-top: var(--zx-s3);
  border-top: 1px solid var(--zx-line);
  color: var(--zx-ink-soft);
  font-size: var(--zx-fs-meta);
}
.pagefoot a { color: var(--zx-ink-dim); border-bottom-color: var(--zx-line); }

.page-path {
  display: flex;
  align-items: center;
  gap: var(--zx-s2);
  margin: 0 0 var(--zx-s3);
  color: var(--zx-ink-soft);
  font-family: var(--zx-font-mono);
  font-size: var(--zx-fs-meta);
}
.page-path a { color: var(--zx-accent); text-decoration: none; }
.page-path b { color: var(--zx-ink); }

/* Page-level views. Notes are attached to the HTML page's stable index. */
.page-tabs {
  display: flex;
  gap: var(--zx-s1);
  align-items: center;
  margin: 0 0 var(--zx-s5);
  border-bottom: 1px solid var(--zx-line);
}
.page-tabs a {
  padding: var(--zx-s2) var(--zx-s3);
  color: var(--zx-ink-dim);
  border-bottom: 2px solid var(--zx-bg);
  text-decoration: none;
}
.page-tabs a:hover { color: var(--zx-ink); background: var(--zx-bg-hover); }
.page-tabs a.active {
  color: var(--zx-ink);
  border-bottom-color: var(--zx-accent);
}

/* `#doc > .page-notes`, NOT `.page-notes`.
   The frame rule above is `#doc > *` - an ID plus a universal, so it
   beats a bare class - and it won. The note form was 1440px wide on a
   1920 screen: a textarea you have to scroll sideways inside, and the
   stated intent on this line was dead. Naming the ID here outranks it. */
#doc > .page-notes { max-width: var(--zx-measure); }
.page-notes-head { margin-bottom: var(--zx-s6); }
.page-kicker {
  color: var(--zx-accent);
  font-family: var(--zx-font-mono);
  font-size: var(--zx-fs-meta);
  font-weight: 700;
  letter-spacing: .08em;
}
.page-notes-head h1 { margin-top: var(--zx-s1); }
.page-note-form,
.page-note {
  display: grid;
  gap: var(--zx-s3);
  margin: 0 0 var(--zx-s4);
  padding: var(--zx-s4);
  border: 1px solid var(--zx-line);
  border-radius: var(--zx-r-lg);
  background: var(--zx-panel);
}
.page-note-form label { display: grid; gap: var(--zx-s1); color: var(--zx-ink-dim); font-size: var(--zx-fs-sm); }
.page-note-form input,
.page-note-form textarea {
  width: 100%;
  border: 1px solid var(--zx-line);
  border-radius: var(--zx-r-md);
  background: var(--zx-raised);
  color: var(--zx-fg);
  font: inherit;
  font-family: var(--zx-font-serif);
  padding: var(--zx-s2) var(--zx-s3);
}
.page-note-form textarea { resize: vertical; min-height: 140px; }
.page-note-form input:focus-visible,
.page-note-form textarea:focus-visible { outline: 2px solid var(--zx-accent); outline-offset: 1px; }
.page-note-actions { display: flex; align-items: center; gap: var(--zx-s2); flex-wrap: wrap; }
.page-note-actions .zx-raised { width: auto; }
.page-note-actions [hidden] { display: none; }
.page-note-list { display: grid; gap: var(--zx-s3); }
.page-note h2 { margin: 0; border: 0; padding: 0; font-size: var(--zx-fs-title); }
.page-note-meta { display: flex; align-items: center; gap: var(--zx-s2); flex-wrap: wrap; color: var(--zx-ink-soft); font-size: var(--zx-fs-meta); }
.page-note-author { color: var(--zx-accent); font-weight: 650; }
.page-note-body { white-space: pre-wrap; overflow-wrap: anywhere; font-family: var(--zx-font-serif); font-size: var(--zx-fs-prose); line-height: var(--zx-lh-prose); }

/* ---- THE SCRIBBLE (A0) -------------------------------------------

   The working surface: two panels, notes and tasking, side by side on
   a wide screen and stacked on a narrow one. They are the SAME notes,
   so they wear the same card styles - only the panel around them is
   new.
 */
.scribble { max-width: var(--zx-frame); }
.scribble-head { margin-bottom: var(--zx-s5); }
.scribble-head h1 { margin-top: var(--zx-s1); }
.scribble-head p { max-width: var(--zx-measure); color: var(--zx-fg-dim); }
.scribble-grid {
  display: grid;
  gap: var(--zx-s5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 26rem), 1fr));
  align-items: start;
}
.scribble-panel {
  display: grid;
  gap: var(--zx-s3);
  padding: var(--zx-s4);
  border: 1px solid var(--zx-line);
  border-radius: var(--zx-r-md);
  background: var(--zx-raised);
}
.scribble-panel-head h2 { margin: 0; border: 0; padding: 0; font-size: var(--zx-fs-title); }
.scribble-panel-head p { margin: var(--zx-s1) 0 0; color: var(--zx-fg-dim); font-size: var(--zx-fs-meta); }
.scribble-task-fields { display: grid; gap: var(--zx-s2); grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr)); }
.scribble-panel .page-note-form label { display: grid; gap: var(--zx-s1); color: var(--zx-ink-dim); font-size: var(--zx-fs-sm); }
.scribble-panel .page-note-form input,
.scribble-panel .page-note-form textarea,
.scribble-panel .page-note-form select {
  width: 100%;
  padding: var(--zx-s2);
  border: 1px solid var(--zx-line);
  border-radius: var(--zx-r-sm);
  background: var(--zx-bg);
  color: var(--zx-fg);
  font: inherit;
}
.scribble-panel .page-note-form input:focus-visible,
.scribble-panel .page-note-form textarea:focus-visible,
.scribble-panel .page-note-form select:focus-visible { outline: 2px solid var(--zx-accent); outline-offset: 1px; }
.scribble-panel .page-note h3 { margin: 0; border: 0; padding: 0; font-size: var(--zx-fs-prose); font-weight: 650; }
.scribble-panel .page-note { padding: var(--zx-s3); border: 1px solid var(--zx-line); border-radius: var(--zx-r-sm); }

/* THE PENCIL. It edits in place, and it is only on your own items -
   the server enforces that rule, and the pencil does not pretend to
   offer what the server would refuse. */
.zx-pencil {
  width: var(--zx-control-h);
  height: var(--zx-control-h);
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--zx-line);
  border-radius: var(--zx-r-sm);
  background: transparent;
  color: var(--zx-accent);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.zx-pencil:hover { background: var(--zx-bg-hover); }
.zx-pencil:focus-visible { outline: 2px solid var(--zx-accent); outline-offset: 1px; }
.task-state {
  padding: 0 var(--zx-s1);
  border: 1px solid var(--zx-line);
  border-radius: 999px;
  font-family: var(--zx-font-mono);
  font-size: var(--zx-fs-meta);
}
.task-seat { color: var(--zx-fg-dim); font-family: var(--zx-font-mono); font-size: var(--zx-fs-meta); }

/* ---- THE SCRIBBLE DRAWER ------------------------------------------

   A pane on the right, from any page. The button lives in the page's
   tab bar, and the drawer is the same drawer everywhere - a quick task
   belongs to now, not to the document you happen to be reading.
 */
.zx-tab-button {
  appearance: none;
  border: 1px solid var(--zx-line);
  border-radius: var(--zx-r-sm);
  background: transparent;
  color: var(--zx-accent);
  font: inherit;
  font-size: var(--zx-fs-sm);
  padding: var(--zx-s1) var(--zx-s3);
  cursor: pointer;
}
.zx-tab-button:hover { background: var(--zx-bg-hover); }
.zx-tab-button:focus-visible { outline: 2px solid var(--zx-accent); outline-offset: 1px; }

.zx-drawer-backdrop {
  position: fixed;
  inset: 0;
  background: color-mix(in srgb, var(--zx-bg) 55%, transparent);
  z-index: 40;
}
.zx-drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  /* A GOOD WIDTH: wide enough for a task and its commentary, and
     narrow enough to leave the page visible behind it. */
  width: min(30rem, 94vw);
  z-index: 41;
  display: flex;
  flex-direction: column;
  background: var(--zx-raised);
  border-left: 1px solid var(--zx-line);
  box-shadow: var(--zx-shadow-portal);
}
.zx-drawer[hidden] { display: none; }
.zx-drawer-head {
  display: flex;
  align-items: flex-start;
  gap: var(--zx-s3);
  padding: var(--zx-s4);
  border-bottom: 1px solid var(--zx-line);
}
.zx-drawer-head h2 { margin: 0; border: 0; padding: 0; font-size: var(--zx-fs-title); }
.zx-drawer-head p { margin: var(--zx-s1) 0 0; color: var(--zx-fg-dim); font-size: var(--zx-fs-sm); }
.zx-drawer-head .zx-close { margin-left: auto; }
.zx-drawer-body {
  display: grid;
  gap: var(--zx-s4);
  padding: var(--zx-s4);
  overflow-y: auto;
  align-content: start;
}
.zx-drawer .scribble-panel { background: transparent; padding: 0; border: 0; }
.zx-drawer .page-note h4 { margin: 0; border: 0; padding: 0; font-size: var(--zx-fs-prose); font-weight: 650; }
.zx-drawer .page-note { padding: var(--zx-s3); border: 1px solid var(--zx-line); border-radius: var(--zx-r-sm); }
.zx-drawer .page-note-form label { display: grid; gap: var(--zx-s1); color: var(--zx-ink-dim); font-size: var(--zx-fs-sm); }
.zx-drawer .page-note-form input,
.zx-drawer .page-note-form textarea,
.zx-drawer .page-note-form select {
  width: 100%;
  padding: var(--zx-s2);
  border: 1px solid var(--zx-line);
  border-radius: var(--zx-r-sm);
  background: var(--zx-bg);
  color: var(--zx-fg);
  font: inherit;
}
.zx-drawer .page-note-form input:focus-visible,
.zx-drawer .page-note-form textarea:focus-visible,
.zx-drawer .page-note-form select:focus-visible { outline: 2px solid var(--zx-accent); outline-offset: 1px; }
.zx-drawer .page-note-actions .zx-raised { width: auto; }
.zx-drawer .scribble-task-fields { display: grid; gap: var(--zx-s2); grid-template-columns: repeat(auto-fit, minmax(min(100%, 9rem), 1fr)); }

/* ---- THE FLOATING ACTION BUTTON ----------------------------------

   The scribble without hunting for the tab bar: one press, bottom
   right, from anywhere - and it is scoped to the page you are on.
 */
.zx-fab {
  position: fixed;
  right: var(--zx-s5);
  bottom: var(--zx-s5);
  z-index: 30;
  width: 3.25rem;
  height: 3.25rem;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--zx-accent-line);
  border-radius: 999px;
  background: var(--zx-accent);
  color: var(--zx-accent-ink);
  font-size: 19px;
  line-height: 1;
  cursor: pointer;
  box-shadow: var(--zx-shadow-portal);
}
.zx-fab:hover { background: var(--zx-accent-hi); }
.zx-fab:focus-visible { outline: 2px solid var(--zx-accent); outline-offset: 2px; }
/* On a phone the tab bar is at the bottom, so the button stands above it
   rather than on it. */
@media (max-width: 1004px) {
  .zx-fab { bottom: calc(var(--zx-tabbar-h) + var(--zx-s4)); }
}

/* ---- WHERE EACH SEAT STANDS ---------------------------------------

   A shared item has a standing per seat. Your own is a control; the
   others are words to read.
 */
.zx-standing {
  display: flex;
  flex-wrap: wrap;
  gap: var(--zx-s2);
  margin-top: var(--zx-s2);
  padding-top: var(--zx-s2);
  border-top: 1px solid var(--zx-line);
}
.zx-standing-seat {
  display: inline-flex;
  align-items: center;
  gap: var(--zx-s1);
  font-size: var(--zx-fs-meta);
  color: var(--zx-ink-soft);
}
.zx-standing-seat b { color: var(--zx-fg); font-weight: 650; }
.zx-standing-state {
  font-family: var(--zx-font-mono);
  color: var(--zx-fg-dim);
}
.zx-standing-select {
  padding: 2px 6px;
  border: 1px solid var(--zx-line);
  border-radius: var(--zx-r-xs);
  background: var(--zx-bg);
  color: var(--zx-fg);
  font: inherit;
  font-size: var(--zx-fs-meta);
}
.zx-standing-select:focus-visible { outline: 2px solid var(--zx-accent); outline-offset: 1px; }

.orientation { max-width: var(--zx-frame); }
.orientation .board-h1 { margin-top: var(--zx-s1); }
.orientation-lead {
  max-width: var(--zx-measure);
  color: var(--zx-fg-dim);
  font-family: var(--zx-font-serif);
  font-size: var(--zx-fs-prose);
}
.orientation-stats {
  display: flex;
  gap: var(--zx-s2);
  flex-wrap: wrap;
  margin: var(--zx-s5) 0;
}
.orientation-stats span {
  padding: var(--zx-s1) var(--zx-s3);
  border: 1px solid var(--zx-line);
  border-radius: var(--zx-r-pill);
  color: var(--zx-ink-dim);
  font-size: var(--zx-fs-meta);
}
.orientation-stats b { color: var(--zx-fg); }
.orientation-panel { margin: var(--zx-s6) 0; }
.orientation-map { display: grid; gap: var(--zx-s2); }
.orientation-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: var(--zx-s3);
  align-items: center;
  padding: var(--zx-s3);
  border: 1px solid var(--zx-line);
  border-radius: var(--zx-r-lg);
  background: var(--zx-panel);
  color: var(--zx-fg);
  text-decoration: none;
}
.orientation-row:hover { border-color: var(--zx-accent-line); background: var(--zx-bg-hover); }
.orientation-copy { display: grid; gap: 2px; min-width: 0; }
.orientation-copy b { font-family: var(--zx-font-sans); font-size: var(--zx-fs-md); }
.orientation-copy span { color: var(--zx-fg-dim); font-family: var(--zx-font-serif); font-size: var(--zx-fs-sm); }
.orientation-count { color: var(--zx-ink-soft); font-size: var(--zx-fs-meta); white-space: nowrap; }
.orientation-count b { color: var(--zx-fg); }

/* --- the tab bar, on a phone --------------------------------------------- */

.tabbar { display: none; }

/* --zx-phone-w. A media query cannot read a custom property, so the
   number is written here and the contract test holds it to the token. */
@media (max-width: 1004px) {
  #app {
    /* minmax(0, 1fr), NOT 1fr. A `1fr` track is `minmax(auto, 1fr)`, and
       `auto` means the track cannot be smaller than the content's
       min-content width - so a wide app bar would set the PAGE's width
       and push the whole portal sideways on a phone. This was measured:
       a 390px viewport was laying out at 478px. */
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: var(--zx-appbar-h) 1fr var(--zx-tabbar-h);
    /* the frame is the screen: the content scrolls inside it, so the tab
       bar stays at the bottom instead of being pushed off by a long page */
    height: 100dvh;
    overflow: hidden;
  }
  #doc { padding: var(--zx-s5) var(--zx-s4) var(--zx-s8); overflow: auto; }
  .scope { display: none; }

  /* THE APP BAR, ON A PHONE. It carries the identity, the state and the
     controls, and there is not room for all of it at 390px. So the pieces
     that are duplicated elsewhere go first: the mark (the toggle button
     already identifies the portal), the signed-in chip (the notes surface
     names the seat), and the small letters of the wordmark. What is left
     is the wordmark, the save control and the gear. */
  .appbar { gap: var(--zx-s2); padding-left: var(--zx-s3); }
  .appbar .mark { display: none; }
  .wordmark .wm-sm { display: none; }
  .who { display: none; }
  .zx-save-ui { padding: 0.25em 0.5em; font-size: var(--zx-fs-meta); }

  .rail {
    position: fixed;
    top: var(--zx-appbar-h);
    left: 0;
    width: min(84vw, var(--zx-sidebar-w));
    z-index: 30;
    transform: translateX(-102%);
    transition: transform var(--zx-dur-2) var(--zx-ease);
    box-shadow: var(--zx-sh-2);
  }
  /* BOTH surfaces open the rail the same way. The portal's own script
     puts `rail-open` on the BODY; the React client puts `open` on the
     rail itself. Accepting both means one rule serves both, and the
     client's menu button is not dead. */
  body.rail-open .rail,
  .rail.open { transform: none; }

  .tabbar {
    grid-column: 1;
    grid-row: 3;
    display: flex;
    align-items: stretch;
    height: var(--zx-tabbar-h);
    background: var(--zx-panel);
    border-top: 1px solid var(--zx-line);
  }
  .tabbar button {
    flex: 1;
    border: 0;
    background: none;
    color: var(--zx-ink-dim);
    font: inherit;
    font-size: var(--zx-fs-sm);
    cursor: pointer;
  }
  .tabbar button:active { background: var(--zx-bg-hover); }
}

/* --- the gate's own page, on the same tokens ----------------------------- */

body.gated {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  background: radial-gradient(1200px 600px at 50% -10%, var(--zx-canvas-top) 0%, var(--zx-canvas-bottom) 55%);
  color: var(--zx-card-fg);
  padding: var(--zx-s6);
}
.card {
  width: min(94vw, 420px);
  background: linear-gradient(180deg, var(--zx-card-top), var(--zx-card-bottom));
  border: 1px solid var(--zx-card-line);
  border-radius: 18px;
  padding: var(--zx-s6);
  box-shadow: var(--zx-shadow-portal), var(--zx-inset-top);
  color: var(--zx-card-fg);
}
.brand { display: flex; align-items: center; gap: var(--zx-s3); margin: 0 0 var(--zx-s4); }
.brand img {
  width: 46px; height: 46px; flex: none; object-fit: contain;
  filter: drop-shadow(0 4px 14px var(--zx-glow-accent));
}
.brand h1 { display: flex; align-items: baseline; gap: 1px; font-size: 20px; margin: 0; letter-spacing: .01em; }
.brand h1 .wm-word { color: var(--zx-wm-word); }
.brand h1 .wm-sm { font-size: .72em; }
.brand h1 .wm-pipe { color: var(--zx-wm-pipe); font-weight: 900; margin: 0 .35em; }
.brand h1 .wm-product { color: var(--zx-wm-product); }
/* the same wordmark, under the names the estate's own login page uses */
.brand h1 .wm { color: var(--zx-wm-word); }
.brand h1 .pipe { color: var(--zx-wm-pipe); font-weight: 900; margin: 0 .35em; }
.brand h1 .product { color: var(--zx-wm-product); }
.kicker { margin: 3px 0 0; font-size: 10px; letter-spacing: .16em; text-transform: uppercase; color: var(--zx-accent-mid); font-weight: 600; }
.sub { color: var(--zx-card-dim); font-size: var(--zx-fs-sm); margin: 0 0 var(--zx-s4); }
label { display: block; font-size: var(--zx-fs-sm); color: var(--zx-card-dim); margin: var(--zx-s3) 0 var(--zx-s1); }
input {
  width: 100%;
  height: var(--zx-control-h);
  padding: 0 var(--zx-s3);
  border-radius: 12px;
  border: 1px solid var(--zx-input-line);
  background: var(--zx-input-bg);
  color: var(--zx-card-fg);
  font: inherit;
  outline: none;
}
input:focus-visible { outline: none; box-shadow: var(--zx-glow-focus); border-color: var(--zx-accent); }
.row { display: flex; gap: var(--zx-s2); }
.row input { flex: 1; }
button {
  font: inherit;
  border-radius: var(--zx-r-sm);
  border: 1px solid var(--zx-line);
  cursor: pointer;
  transition: background var(--zx-dur-1) var(--zx-ease);
}
button:disabled { opacity: .6; cursor: progress; }
.btn {
  width: 100%;
  height: var(--zx-control-h);
  margin-top: var(--zx-s4);
  font-weight: 650;
  color: var(--zx-accent-ink);
  background: linear-gradient(180deg, var(--zx-accent-top), var(--zx-accent-mid) 45%, var(--zx-accent-bottom));
  border: 1px solid var(--zx-accent-line);
  box-shadow: 0 10px 24px var(--zx-glow-accent);
}
.btn:hover { filter: brightness(1.06); }
.pencil { width: var(--zx-control-h); background: transparent; color: var(--zx-accent); }
.pencil:hover { background: var(--zx-accent-soft); }
.hint { font-size: var(--zx-fs-meta); color: var(--zx-card-dim); margin: var(--zx-s4) 0 0; }
.hint code { font-size: .92em; }
.msg { margin-top: var(--zx-s3); font-size: var(--zx-fs-sm); min-height: 20px; }
.ok { color: var(--zx-card-ok); }
.err { color: var(--zx-card-err); }
.spin {
  display: inline-block;
  width: 12px; height: 12px;
  border: 2px solid var(--zx-accent-ink);
  border-top-color: var(--zx-accent);
  border-radius: 50%;
  animation: sp .7s linear infinite;
  margin-right: 6px;
  vertical-align: -1px;
}
@keyframes sp { to { transform: rotate(360deg); } }

/* the gate's page does not need the app shell */
body.gated #app { display: none; }

/* ============================================================================
   the Now board.
   ----------------------------------------------------------------------------
   The landing page is a READ MODEL over four sources, not a document. So it
   reads as a board: a truth strip, then what needs attention, then what
   exists, then the two logs that are deliberately kept apart.

   It sits in the CONTENT area, which follows the theme, so it uses the
   themed tokens -- --zx-fg, --zx-fg-dim, and the --zx-state-* palette the
   host declares for exactly this. The gate page is a DARK CARD in both
   themes and keeps the --zx-card-* palette; the two must not be mixed.

   Every colour here is a token. The contract test fails the build if one of
   these lines names a colour of its own.
   ========================================================================== */

.board { max-width: var(--zx-frame); }

.board-h1 {
  font: var(--zx-fs-title) / var(--zx-lh-tight) var(--zx-font-serif);
  margin: 0 0 var(--zx-s3);
}

/* the truth strip - freshness, and RED when a source could not be read */
.board-truth {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--zx-s2);
  padding: var(--zx-s3) var(--zx-s4);
  margin: 0 0 var(--zx-s5);
  border: 1px solid var(--zx-line);
  border-radius: var(--zx-r-md);
  background: var(--zx-panel);
  color: var(--zx-fg-dim);
  font-size: var(--zx-fs-sm);
}
.board-when { color: var(--zx-fg-dim); }
.board-when b { color: var(--zx-fg); font-weight: 650; }
.board-sep { color: var(--zx-fg-dim); }
.board-red {
  display: block;
  border-color: var(--zx-state-bad);
  color: var(--zx-state-bad);
}
.board-red ul { margin: var(--zx-s2) 0 0; padding-left: var(--zx-s5); }
.board-red code { color: var(--zx-fg); }

/* the sections */
.board-sec { margin: 0 0 var(--zx-s5); }
.board-sec h2 {
  font: var(--zx-fs-md) / var(--zx-lh-tight) var(--zx-font-sans);
  font-weight: 650;
  margin: 0 0 var(--zx-s3);
  padding-bottom: var(--zx-s2);
  border-bottom: 1px solid var(--zx-line);
}
.board-note {
  margin: 0 0 var(--zx-s3);
  color: var(--zx-fg-dim);
  font-size: var(--zx-fs-sm);
}
.board-empty { color: var(--zx-fg-dim); font-size: var(--zx-fs-sm); }
.board-dim { color: var(--zx-fg-dim); }

/* THE BOARD'S PROSE IS PROSE, AND PROSE HAS A MEASURE.
   The board is the portal's front door, and its notes were running the
   full frame - 1440px of a 13px line. The board's DATA (the attention
   table, the orientation rows) uses the whole frame, because data may
   be wide. Its PROSE does not, because prose that is 169 characters to
   the line is a ribbon, not a paragraph.
   This is the same split the pages make, applied to the board. */
.board-note,
.board-empty,
.board-list,
.orientation-panel p { max-width: var(--zx-measure); }

/* a pill - a count or a state */
.pill {
  display: inline-block;
  padding: 1px var(--zx-s2);
  margin: 0 var(--zx-s1);
  border-radius: var(--zx-r-pill);
  border: 1px solid var(--zx-line);
  background: transparent;
  color: var(--zx-fg);
  font-size: var(--zx-fs-meta);
  white-space: nowrap;
}
.pill-quiet { color: var(--zx-fg-dim); }
.pill-ok { border-color: var(--zx-state-ok); color: var(--zx-state-ok); }
.pill-warn { border-color: var(--zx-state-bad); color: var(--zx-state-bad); }

/* the attention table */
.board-table {
  /* The SAME treatment as every other table in the ddb: the width its
     content wants, capped at the column. `width: 100%` here was the
     very thing the table rule above forbids - a table stretched to
     1440px, with the space between its cells doing nothing. */
  width: auto;
  max-width: 100%;
  border-collapse: collapse;
  font-size: var(--zx-fs-sm);
}
.board-table th {
  text-align: left;
  font-weight: 600;
  color: var(--zx-fg-dim);
  padding: var(--zx-s2) var(--zx-s3);
  border-bottom: 1px solid var(--zx-line);
  white-space: nowrap;
}
.board-table td {
  padding: var(--zx-s2) var(--zx-s3);
  border-bottom: 1px solid var(--zx-line);
  vertical-align: top;
}
.board-table .nowrap { white-space: nowrap; }
.board-ref code { font-size: .92em; word-break: break-all; }

/* the lists */
.board-list { margin: 0; padding-left: var(--zx-s5); font-size: var(--zx-fs-sm); }
.board-list li { margin: 0 0 var(--zx-s2); }
.board-list code { font-size: .92em; }
.board-list .board-red, .board-list .board-warn { list-style: none; margin-left: calc(-1 * var(--zx-s5)); }
.board-list .board-warn code { color: var(--zx-state-warn); }

/* the two feeds */
.board-feed { margin: 0; padding: 0; list-style: none; font-size: var(--zx-fs-sm); }
.board-feed li {
  margin: 0;
  padding: var(--zx-s2) 0;
  border-bottom: 1px solid var(--zx-line);
  line-height: var(--zx-lh-body);
}
.board-ev { font-size: .9em; color: var(--zx-fg-dim); }

/* on a phone the table is too wide to read, so it becomes a list */
/* --zx-phone-w. A media query cannot read a custom property, so the
   number is written here and the contract test holds it to the token. */
@media (max-width: 1004px) {
  .board-table thead { display: none; }
  .board-table, .board-table tbody, .board-table tr, .board-table td { display: block; width: 100%; }
  .board-table tr { padding: var(--zx-s3) 0; border-bottom: 1px solid var(--zx-line); }
  .board-table td { border: 0; padding: 2px 0; }
  .board-table td::before {
    content: attr(data-label) " ";
    color: var(--zx-fg-dim);
    font-size: var(--zx-fs-meta);
  }
}

/* ============================================================================
   the search results, under the rail's search box.
   The box filters the nav by title (instant, local) AND asks the server for
   the pages' own text (the same engine the agent API uses). One search, two
   audiences.
   ========================================================================== */
.filter-results {
  margin: var(--zx-s2) 0 var(--zx-s3);
  max-height: 40vh;
  overflow-y: auto;
  font-size: var(--zx-fs-sm);
}
.filter-hit {
  display: block;
  padding: var(--zx-s2) var(--zx-s3);
  margin-bottom: var(--zx-s1);
  border-left: 2px solid var(--zx-accent-line);
  border-radius: var(--zx-r-xs);
  text-decoration: none;
  color: var(--zx-fg);
}
.filter-hit:hover { background: var(--zx-bg-hover); }
.filter-hit-t { display: block; font-weight: 600; }
.filter-snip {
  display: block;
  color: var(--zx-fg-dim);
  font-size: var(--zx-fs-meta);
  line-height: var(--zx-lh-body);
}
.filter-none { color: var(--zx-fg-dim); font-size: var(--zx-fs-meta); margin: var(--zx-s2) 0; }
