    /* --titlebar-scale: at low zoom, scale the titlebar UP so the
       title stays readable; clamp by tile-width / title-natural-
       width so the visual never overflows the tile.  Driven by
       --titlebar-effective-zoom (set by ensureEffectiveTitlebarZoom
       on inner pages, by pan-zoom on top-level pages — falls back
       to --zoom if effective isn't set), and per-tile by
       --card-w-num + --titlebar-natural-w-num.

       --titlebar-painted-ratio: the titlebar's APPARENT width as a
       multiple of its title's natural width — what the chief
       actually sees.  Card-resize shrinks --card-w-num; zoom-out
       shrinks the cumulative zoom factor; both contribute the same
       way to the ratio, so resize and zoom drive the same fade.
       Every opacity rule below keys off this one var; changing the
       cumulative-zoom convention here updates the whole slot-fade
       behaviour uniformly. */
    /* --titlebar-effective-zoom (set on #root) is the CUMULATIVE outer
       apparent scale up to and INCLUDING this kernel's chrome.  It
       does NOT include this kernel's own local pan-zoom (which is
       published separately as --zoom on the same #root by pan-zoom).
       For a TILE's apparent scale we multiply both — without that,
       boot.js anchoring effective-zoom to 1 on top-level pages would
       short-circuit the fallback chain and freeze tile-titlebar scaling
       no matter how far the chief zoomed out.
       Scale is clamped to [1, 4]: never below 1 (don't shrink the
       title font — wrap is the answer when it doesn't fit), and never
       above 4x (chief zooming out far enough to need >4x scale is
       arranging the diagram, not reading titles).
       The CHROME titlebar (#root, which is .node.fullpage) doesn't
       sit on the pan-zoomed stage — its scaling depends on
       --titlebar-effective-zoom ONLY, never the local --zoom.  Tile
       formula is therefore scoped to :not(.fullpage); the chrome
       formula below overrides for #root. */
    /* Title scale — ONE declarative expression, exact, no iteration.
       The title block is a SIMILARITY TRANSFORM of its nominal
       layout: .titlebar-text below is sized in em, so its wrap width
       scales with the font and the line count is invariant under
       scale.  Both block dimensions therefore scale linearly with
       --titlebar-scale, and "does it fit the card" is a pure ratio
       of nominal extents to card extents — no measurement of the
       scaled result, hence no feedback loop to iterate.

         desire  = max(1, 1/z)                     hold screen size at zoom-out
         h-stop  = card_w / (line_w + slots_w)     row fits the card's width
         v-stop  = (card_h - border) / text_h      block fits the card's height
         scale   = min(desire, h-stop, v-stop)

       The v-stop subtracts the bar's border, which does NOT scale with
       the font and so must come off the card before the ratio is taken.

       The h-stop divides by the whole row's nominal extent, title plus
       slots, because the buttons and the AGENT/STATUS slots really do
       occupy that row: sized in the same font, they scale with the
       title, so the sum scales uniformly and the ratio stays exact.
       Sizing the title against the bare card instead lets it push the
       buttons out of the bar — measured at 16px of overflow on a
       narrow code card, which no slot-fade threshold reclaimed.

       Slots mount asynchronously, though (a type spec registers,
       agent-field or flipcard adds its element), so slots_w is briefly
       understated when a tile first paints and the title is briefly
       sized a little large.  That window is covered in LAYOUT, not
       here: the flex rules below let every slot shrink and the title
       not, so during the window the buttons give up their room and the
       bar still fits its card.  Steady-state exactness comes from this
       expression; transient safety comes from the priority in the flex
       rules.  Both say the same thing — the title outranks the slots.

       The two stops depend only on AUTHORED card geometry and the
       nominal text extents — both zoom-independent.  So the ceiling
       never changes as the chief zooms; only the desire term moves,
       and min() clamps it.  That is why nothing needs recomputing on
       zoom, and why the correct value is available at FIRST PAINT
       (the vars are published at mount, before the tile is shown) —
       no flicker, no post-hoc correction.

       Geometric consequence, stated plainly: a title that already
       fills its card at nominal size CANNOT grow.  The stops pin it
       at 1 and it shrinks with the card on zoom-out.  A title with
       headroom grows until it fills the card and then stops.  "The
       card becomes the titlebar" is the h-stop and v-stop meeting.
       Supersedes the iterative fitter (binary search on measured bar
       height) which could not be right at first paint and went stale
       whenever zoom changed without a tile resize. */
    .node {
      /* The zoom cap, named so the sign form can lift it.  At nominal
         zoom this evaluates to 1, which is what stopped a title ever
         growing past its nominal font size however much card it had —
         fine for a notecard, whose title shares the card with a body,
         and wrong for a sign, where the title IS the card.  See
         tile-render.css. */
      --titlebar-zoom-cap: max(1, calc(1 / var(--cumulative-zoom, 1)));
      --titlebar-scale: min(
        var(--titlebar-zoom-cap),
        calc(var(--card-w-num, 260)
             / (var(--natural-line-w-num, 260)
                + var(--titlebar-chrome-em, 0) * var(--title-font-px, 11))),
        calc((var(--card-h-num, 100) - var(--titlebar-border-px, 1))
             / var(--natural-text-h-num, 100))
      );
    }
    /* Slot-fade model — chief 2026-07-10 (slot-agent card in
       /titlebar.tvg4.json):
         "Shall fade and vanish when the card's PAINTED width is small
          relative to its natural title width — not when the camera
          zoom changes.  Zoom-invariant cards hold their painted size,
          so the slot stays lit regardless of camera scale."
       Two pieces to make the code match:
         (A) --titlebar-painted-w — the card's ACTUAL on-screen width
             in the chief's eye.  Set per node type by whichever inputs
             actually contribute to its painted size.
         (B) --titlebar-painted-ratio — painted width divided by the
             title's natural width.  ONE formula, shared by every
             slot's opacity rule downstream. */
    .node {
      /* Default (regular tile on the pan-zoomed stage): painted width
         is authored card width × cumulative outer zoom × local pan-
         zoom.  As the chief zooms out, the tile visibly shrinks; the
         ratio shrinks with it. */
      /* --card-lod-zoom, not --zoom: the camera scale QUANTISED for
         this card, written on the card itself by pan-zoom when the
         card crosses a size level.  Reading the scene-wide --zoom here
         made one camera move invalidate every card at once; reading a
         per-card value means a card recomputes only when its own
         apparent size actually changed.  --zoom is registered
         non-inherited (pan-zoom.css) and would resolve to 1 here. */
      --titlebar-painted-w: calc(
        var(--card-w-num, 260)
        * var(--titlebar-effective-zoom, 1)
        * var(--card-lod-zoom, 1)
      );
    }
    /* Fullpage tiles are the viewport chrome — no local pan-zoom
       transform applied.  Painted width = card width × outer zoom. */
    .node.fullpage,
    /* Hail cards apply transform: scale(1 / --zoom) so their painted
       width holds constant across camera zoom — see hail-card.tvg4
       .json's ZOOM-INVARIANT requirement.  Same formula as fullpage:
       drop the local --zoom factor. */
    .node.type-hail-card {
      --titlebar-painted-w: calc(
        var(--card-w-num, 260)
        * var(--titlebar-effective-zoom, 1)
      );
    }
    /* One ratio for everyone: available row width divided by the
       title's ACTUAL rendered width (measured, not derived).  Chief
       2026-07-14: the measuring stick is how much SPACE is available
       — not scale, not zoom, not any indirect derivation.  So this
       ratio compares two physical widths in the same viewport
       pixels: the row's own painted width, and what the title
       actually needs to display without wrapping at whatever font-
       size it happens to be rendering at.
       The divisor is --natural-line-w-num: COMPUTED from the file by
       text-metrics, present before the card is in the document.  It
       was --titlebar-rendered-w, MEASURED off the rendered title, and
       that made every slot's fade threshold key off a value that
       landed at a variable time.  Each slot then crossed its threshold
       when the measurement arrived and, because these rules transition
       opacity, ANIMATED across.  Compositing during an opacity
       animation switches text from subpixel to grayscale antialiasing,
       so the whole scene's lettering changed weight with nothing
       moving — and the moment it happened depended on cache state, so
       it differed between a cold refresh and a warm one.  Chief
       2026-07-26: "it depends on the order in which i refresh".
       A computed divisor is the same number at every moment, so no
       threshold is crossed during load and nothing animates. */
    .node {
      --titlebar-painted-ratio: calc(
        var(--titlebar-painted-w)
        / var(--natural-line-w-num, var(--titlebar-natural-w-num, 100))
      );
    }
    /* cursor: grab — drag handle.  See web/affordances.md, "cursor
       matches click action".  The titlebar is the canonical drag
       handle at high zoom; finger affordance for the link region is
       layered on TOP via the .titlebar-link-target class (owned by
       titlebar-link.js).
       The title text MAY wrap onto multiple lines when there's no
       horizontal room — like an ordinary textbox titlebar already
       does.  The titlebar's height grows to fit (see min-height
       below); right-side slots collapse out of layout (via the
       max-width fade rules below) so the wrapping title doesn't
       fight them for space. */
    .node > .titlebar {
      /* Default: no padding.  Chief 2026-07-20: "padding is
         unhelpful when cards are small."  Chrome (fullpage) gets
         padding back below via a .fullpage override — the chrome
         bar has viewport-scale room and reads better with breathing
         space. */
      padding: 0;
      background: #0c1018; color: #88ddc0;
      font-family: ui-monospace, monospace;
      /* font-size scales with --titlebar-scale so the title text
         stays visually large enough to read as the chief zooms out.
         Pure font-size scaling means the titlebar's LAYOUT height
         tracks its VISUAL height — body's flex:1 sees the real
         remaining space and shrinks/grows accordingly.  No transform,
         no width inversion, no margin-top compensation; layout is the
         truth.  This replaces an earlier transform:scale approach
         that decoupled visual from layout and had the body painting
         over the wrapped-title region (six revisions of the same
         bug: "fix titlebar scaling such that titles are always
         legible"). */
      font-size: calc(var(--title-font-px, 11) * 1px * var(--titlebar-scale, 1) * var(--text-nominal-scale, 1));
      line-height: 1.2;
      letter-spacing: 0.06em; text-transform: uppercase;
      border-bottom: 1px solid #1a2030;
      cursor: grab; user-select: none;
      box-sizing: border-box;
      /* Centered layout — chief 2026-07-12: "as we zoom out, the
         card becomes the titlebar, with good centered layout."
         align-items: center keeps a single-line title vertically
         centered in the bar; when the bar dominates the whole card,
         the title lands in the visual middle of the card, not
         hugging the top edge. */
      display: flex; align-items: center;
      min-width: 0; width: 100%;
      /* No min-height floor.  Bar height = 1.2em × lines from text. */
      position: relative; z-index: 1;
      /* No opacity fade on the titlebar itself.  The old fade was
         tied to --titlebar-painted-ratio and was meant to hide the
         titlebar when it couldn't fit at low zoom — but the
         font-size scaling above keeps titles legible at any zoom,
         and wrap takes care of the "doesn't fit" case.  Fading the
         titlebar at low zoom now hides chief-relevant labels (iframe
         titles especially) when there's no fit problem to solve. */
    }
    .node > .titlebar:active { cursor: grabbing; }
    /* Chrome (fullpage) padding restored — chief 2026-07-20: "the
       fullpage titlebar needs padding".  Tiles do not; the override
       is targeted to .fullpage only. */
    .node.fullpage > .titlebar { padding: 0.36em; }
    /* Title text — the most important slot — is ALWAYS visible.  It
       wraps to multiple lines when the titlebar narrows; the titlebar
       grows vertically (min-height above) to host the wrap.  The
       finger-link affordance (cursor:pointer + underline) only covers
       actual letters, regardless of how many lines they occupy.

       flex: 1 — the title-text claims every horizontal pixel the
       right-side slots aren't using.  Without this, the spacer's
       flex: 1 was absorbing the empty row width, pinning the title
       at its content width and forcing it to wrap inside a narrow
       column even when the row had room to spare.  Giving the title
       the flex-grow instead means: as AGENT / STATUS / flip collapse
       (max-width 0), the title expands to fill the released space,
       and wrapping only happens when the row literally can't host
       the title on one line.

       word-break: normal + overflow-wrap: normal — wrap between
       words ONLY, never inside a word (chief 2026-07-20: "title
       text words shall wrap at word boundaries").  A word too wide
       to break is folded into --natural-line-w-num, so the h-stop
       above shrinks the scale until that word fits.

       width in em — THE INVARIANT.  The box is exactly the nominal
       ink width of the wrapped block, expressed in em, so it scales
       with the font size.  Line breaks therefore never change as the
       scale changes: the text has proportionally the same room at
       every scale, and the rendered block is a similarity transform
       of the nominal one.  This is what makes the fit ratios above
       exact rather than approximate.

       A PIXEL width here would re-flow the text on every scale change
       (more lines as the font grows), making block height a step
       function of scale — the implicit relation that forced the old
       numeric fitter and its 1.35x fudge.  Keep this in em. */
    .node > .titlebar > .titlebar-text {
      /* OUT OF FLOW.  The title does not compete with the buttons and
         the AGENT slot for the row's width, and cannot push them out
         of the bar.  That competition is what forced a chrome
         allowance — a reservation that had to be either measured off
         the mounted slots (order-dependent) or declared per type
         (which starved small cards until their titles were
         unreadable).  A title that takes no row width needs neither:
         there is nothing to reserve because nothing is being taken.

         Overlap is prevented by the slot-fade thresholds below rather
         than by geometry, which is what the priority chain already
         says: every non-title threshold is > 1, so by the time the
         title needs the full width the slots have faded.  When they
         are shown, the ratio is > 1 and this centred box is narrower
         than the bar, so it does not reach them.

         The width stays em-proportional — that is what keeps the line
         count invariant under scale and the fit exact.  Centred by
         translate rather than by the spacers, since an out-of-flow box
         is not something flex can centre. */
      /* OUT OF FLOW — restored 2026-07-27, and the overlap it causes
         is a KNOWN, UNFIXED defect.
         
         The chief's report stands: a centred out-of-flow title can be
         narrower than the bar and still print straight through a slot
         sitting at the bar's left end.  Clearance from the edges is not
         clearance from the slots, and a fade threshold cannot express
         clearance because it knows a ratio, not a position.
         
         Putting the title back IN the flex row does prevent the overlap
         — verified on screen, no card overlapped — but it then competes
         for the row's width, and the fit has no reservation for the
         slots, so titles escaped their bands by 2-5px from zoom 0.75
         down.  Letting the box shrink instead lets the BROWSER re-wrap
         into more lines than text-metrics computed while the bar's
         height still comes from the computed extents, which overflows
         the other way.
         
         The fix is a slot reservation inside titleExtents, so the
         ARITHMETIC chooses a wrap that already excludes the slots and
         the height it reports matches the lines it chose.  That reserve
         costs LINES, not font size — which is why the earlier attempt
         at a chrome allowance made titles illegible: it was applied to
         the SCALE instead of to the WRAP. */
      position: absolute;
      left: 50%;
      transform: translateX(-50%);
      /* Centred LINES, not merely a centred box.  The box has always
         been centred on the card, but without this the lines set flush
         left inside it, so a title that wrapped — "DOWNLINK / BUDGET" —
         read as left-aligned text that happened to sit in the middle.
         Signage is centred by convention; chief 2026-07-27.  The rule
         existed only on the fullpage chrome, which is why the page
         header looked right while every card did not. */
      text-align: center;
      width: calc(var(--natural-line-w-num, 100)
                  / var(--title-font-px, 11) * 1em);
    }
    /* The page chrome keeps its title IN FLOW.
       Taking the title out of flow solves a problem CARDS have — a
       title claiming the row's width pushes the buttons out of the bar
       — and the chrome bar does not have it: it is viewport-wide, so
       its title and its slots never compete.  What it does have is a
       height that must be settled at first paint, and an out-of-flow
       title contributes none, leaving the bar to be sized by slots
       that mount later: measured 15.44px at first paint, 25.25px once
       the AGENT slot arrived, which changed the viewport and shifted
       every card 5px.  In flow, the title the composer emits sizes the
       bar immediately and nothing that mounts later can change it. */
    .node.fullpage > .titlebar > .titlebar-text {
      position: static;
      left: auto;
      transform: none;
      width: auto;
      flex: 0 1 auto;
      text-align: center;
      white-space: normal;
      overflow-wrap: normal;
      word-break: normal;
    }
    /* The bar's height comes from the SAME computed extents the fit
       uses, not from whatever its content happens to lay out to.  With
       the title out of flow the bar has no content to be sized by, and
       deriving it here keeps one source for the number: text-metrics
       computed natural_text_h from the file, and the painted height is
       that times the scale. */
    .node:not(.fullpage) > .titlebar {
      min-height: calc(var(--natural-text-h-num, 16) * 1px
                       * var(--titlebar-scale, 1));
    }
    /* The page chrome sizes from its own font, not from a card's
       extents.  --natural-text-h-num is emitted per CARD by the
       composer; #root has none, so the rule above would size the
       chrome bar from its fallback at first paint and from the real
       value once something stamped one — 4.5px of viewport change,
       which refits the camera and shifts every card 5px.  The chrome
       title is a single line, so one line-height in em says it with
       no measured input to arrive late. */
    .node.fullpage > .titlebar {
      min-height: 1.2em;
    }
    /* Both spacers absorb whatever room the row has left after the
       title and side-content take theirs.  flex-basis: 0 means the
       spacer's contribution to the initial layout budget is ZERO,
       so the title never has to fight the spacer for its content
       width — the wrap-line was showing up because the earlier
       mirror-basis trick reserved the OPPOSITE side's fixed content
       width on each spacer, which forced the title-budget = viewport
       - 2·(left + right) and often went negative for medium cards.
       Perfect symmetry (left content = right content) still produces
       a centred title with flex:1 grow.  Asymmetric layouts (AGENT on
       left, FLIP+MAX on right) drift a little from centre, which is
       the accepted trade-off for never wrapping when the row has
       room to hold the title. */
    .node > .titlebar > .titlebar-lspacer,
    .node > .titlebar > .titlebar-spacer {
      flex: 1 1 0;
      min-width: 0;
    }

    /* The button GROUP — a grid of as many ROWS as the title has lines,
       filled COLUMN by column.
       
       One declared number does the whole job.  A one-line title gives a
       single row, so the buttons sit along the bar exactly as before; a
       three-line title gives three rows, so the buttons stack three deep
       and begin a second column rather than stretching a tall bar out
       sideways.  Chief 2026-07-26: "buttons shall stack into columns &
       grids".
       
       No breakpoint and no branch: grid-auto-flow:column with an
       N-row template IS the stacking rule, and --title-lines is known
       from the arithmetic before the card is drawn, so the layout is
       right in the first painted frame. */
    .node > .titlebar > .titlebar-btns {
      display: grid;
      grid-template-rows: repeat(var(--title-lines, 1), auto);
      grid-auto-flow: column;
      gap: 2px 0;
      align-content: center;
      justify-items: stretch;
      min-width: 0;
      flex: 0 0 auto;
    }
    /* An empty group must not occupy the row it would otherwise hold
       open — a card with no buttons should look as though the mechanism
       is not there. */
    /* Unscoped on purpose: an empty group must vanish in the page
       chrome too, not only on cards.  A zero-button wrapper that still
       counts as a flex item shifts everything the spacers were
       balancing. */
    .titlebar-btns:empty, #legend > .titlebar-btns:empty { display: none; }
    /* Slot priority, in layout.  The title is flex: 0 0 auto above —
       it does not shrink.  Everything else in the row may, down to
       nothing.  So when the row is over-subscribed the BUTTONS give up
       their room and the title keeps the size the fit gave it; the bar
       never grows past the card to host them.
       This is the same priority the fade thresholds express (title
       first, every other slot's threshold > 1) applied at a different
       moment: the thresholds handle the steady state, these rules
       handle the frames before an asynchronously-mounted slot has been
       measured into --natural-slots-w-num. */
    /* Chrome allowance — how much of the row the title must leave for
       the buttons and the AGENT slot, in em, DECLARED PER TYPE.
       
       This was previously measured off the mounted titlebar and cached.
       That made the fit ORDER-DEPENDENT: the composer now emits the
       titlebar's slots, so the measurement began succeeding before the
       buttons had mounted, read zero chrome, cached it, and every code
       card's title then claimed the whole row and pushed its buttons
       out — 18px of overflow on planet.js.
       
       A card's slot set is a property of its TYPE, and the type is on
       the element as a class from the first byte the server sends.  So
       the allowance is stated here rather than discovered: asked at
       parse, at mount, or a second later, the answer is the same, and
       there is no second party to agree with.  The slots are sized in
       em, so one number covers every zoom.
       
       Values measured once from the rendered chrome (iframe 8.38em:
       AGENT + flip; code 3.77em: download + max; textbox none), then
       rounded up so a slot cannot be clipped by a rounding edge.  A
       type that gains a button updates its number here, next to the
       spec that declares the button. */
    /* REVERTED 2026-07-26, same day it landed.  Reserving chrome
       unconditionally is wrong: the slots it reserves for FADE OUT on
       small cards (that is what the thresholds below are for), so on a
       small card the reservation buys nothing and costs most of the
       width.  The title then scales to near-zero and the card reads as
       an empty rectangle.  Chief, with a screenshot of link-model:
       "app pretty illegible here" — most cards showing no title at all.
       
       I had measured the cost as fill 0.61 -> 0.32 and called it "a
       real loss" while landing it anyway.  That was under-reading the
       number: it is not a smaller title, it is no title.  An
       unreadable card is worse than the 18px of button overflow this
       was fixing.
       
       Left at 0 rather than deleted so the mechanism and this note
       stay together.  The real fix is for the slots not to push the
       title at all — out of flow, or clipped — so nothing needs
       reserving and nothing needs measuring.  Until then the buttons
       on a narrow code card can overhang, which is visible but legible.
       Written up in scene-draw-requirements.md. */
    .node                 { --titlebar-chrome-em: 0; }
    .node.type-iframe     { --titlebar-chrome-em: 0; }   /* REVERTED — see below */
    .node.type-code       { --titlebar-chrome-em: 0; }   /* REVERTED — see below */

    .node > .titlebar > *:not(.titlebar-text) {
      flex-shrink: 1;
      min-width: 0;
      overflow: hidden;
    }
    /* Context-aware picker.  Each right-side slot fades in as the
       card grows wide enough to comfortably host it.  Ratio is the
       card's painted width relative to its title's natural width.
       Below threshold → opacity 0 + pointer-events:none (un-hittable);
       past threshold → opacity 1.  Smooth via opacity transition.
       Page-as-card (#root.fullpage) hand-sets card-w-num=9999 and
       natural-w-num=1, so the ratio is large → all slots visible. */
    /* Thresholds mirror the slot-priority chain in titlebar.tvg4.json:
       title > flip > AGENT > STATUS > spacer.  More-important slots
       get a LOWER threshold (they persist further into narrowness);
       less-important slots get a HIGHER threshold (they require more
       room to show).  When the chain changes, swap these in lockstep.

       --titlebar-painted-ratio is painted card width / TITLE natural
       width (NOT full-titlebar natural width — see measureNatW).  At
       ratio = 1 the title fills the available width and starts
       wrapping.  Per spec, the title has priority over every other
       slot: any room goes to it first.  So every non-title slot's
       threshold is > 1, guaranteeing the slot is fully faded before
       the title is under stress.

       Each fade rule ties BOTH opacity AND max-width to the same
       clamp amount.  Without the max-width collapse, a slot with
       opacity:0 still occupies its natural width in the flex row and
       pushes more-important slots off the right edge.  max-width: 0
       removes the slot from the layout once it's fully faded; the
       chain order then matches the visual collapse order. */
    /* Thresholds are read from /titlebar.tvg4.json's slot cards by
       ensureSlotThresholds (above) and published as --threshold-*
       on the document root.  The fallbacks here match the topology
       so the rules behave correctly even before the fetch lands.

       Layout-collapse on fade: when --slot-amount is 0, the slot's
       max-width, padding, margin, and border-width all collapse to
       0 so the surrounding flex layout actually reclaims the room.
       Without padding/margin/border-width collapsing too, slots
       with chrome (e.g. .flipcard-btn's border + padding + margin)
       would still occupy ~20 px even at opacity 0 — visible as the
       chief's "unused space at the right of the titlebar".
       min-width: 0 and flex: 0 1 0% let the flex item actually
       shrink past its content min-size. */
    /* Every slot yields on the SAME basis: how many pixels of titlebar
       the card actually paints.  A slot needs room, room is a physical
       width, and that width is already quantised per card by the mipmap
       level — so each slot has a precomputed answer at each level
       rather than a ratio recomputed against the title's natural width.
       
       The ratio was the defect: it divided painted width by the TITLE's
       natural width, so a card with a long title reported a small ratio
       however wide it was, while a card with a short title kept its
       slots lit at sizes far too small to read them.  Chief 2026-07-26,
       on a card whose title and AGENT slot had collided: "things should
       be hiding here."
       
       Thresholds are the room each slot needs, in painted pixels, in
       priority order — the title is served first and every other slot
       gives way to it, which is what the fade chain has always meant. */
    .node > .titlebar > .titlebar-status,
    .node > .titlebar > #status {
      --slot-room-px: 300;
      --slot-amount: clamp(0,
        calc((var(--titlebar-painted-w) - var(--slot-room-px)) / 60), 1);
      opacity: var(--slot-amount);
      min-width: 0;
      max-width: calc(var(--slot-amount) * 99vw);
      overflow: hidden;
      transition: opacity 0.15s, max-width 0.15s;
    }
    .node > .titlebar > .titlebar-agent,
    .node > .titlebar > #legend-agent {
      --slot-room-px: 260;
      --slot-amount: clamp(0,
        calc((var(--titlebar-painted-w) - var(--slot-room-px)) / 60), 1);
      opacity: var(--slot-amount);
      min-width: 0;
      max-width: calc(var(--slot-amount) * 99vw);
      overflow: hidden;
      transition: opacity 0.15s, max-width 0.15s;
    }
    /* Flip / cycle buttons are navigational chrome, not decorative
       — they let the chief move between a card's faces at any zoom.
       Unlike status/agent (informational, fade with painted-ratio),
       the flip stays fully visible so the chief can always find it.
       The topology's slot-flip threshold=0 encodes this intent; the
       CSS below just ignores --ramp-flip for the flip button. */
    .node > .titlebar > .flipcard-btn {
      min-width: 0;
    }
    /* Generic titlebar button — the mechanism a card type extends
       via spec.titlebarButtons to add its own affordance to the
       titlebar.  Mint border, 3 px radius, 0-6 padding, mint-tinted
       hover fill, cursor:default (pointer is reserved for links per
       affordances.md).  Chief 2026-07-13: card types shall be able
       to define a titlebar button; abstracting the buttons we do
       have into this shared class is that abstraction.  The maxi-
       btn is now the first citizen of the mechanism, declared as
       a spec.titlebarButtons entry in code.tvg4.json and
       iframe.tvg4.json rather than a bespoke populateTitlebar
       branch. */
    .node .titlebar-btn {
      /* Rectangular box — chief 2026-07-14 "the earlier rectangle
         buttons were fine".  Box inherits the titlebar's font so its
         line-height and padding stay at the same nominal size the
         chief remembers; the glyph inside is scaled up separately
         (see .titlebar-btn-glyph below) so the visual weight of the
         symbol is high without the box changing dimensions. */
      background: transparent;
      border: 1px solid #88ddc0;
      color: #88ddc0;
      border-radius: 3px;
      padding: 0 6px;
      margin-left: 6px;
      font: inherit;
      cursor: default;
      user-select: none;
      line-height: 1.2;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      transition: background 0.12s, opacity 0.15s, max-width 0.15s;
      /* Buttons yield to BOTH signals: zoom-out (--ramp-overview,
         driven by cumulative zoom) AND row narrowness (--ramp-agent,
         driven by painted / rendered-title ratio).  min() picks
         whichever is smaller, so a button hides as soon as EITHER
         signal falls — chief 2026-07-14: on narrow cards the max /
         flip / download shall yield to the title, not stay lit while
         the title wraps around them.  Buttons and the AGENT slot
         then vanish together as the row runs out of room. */
      /* A button needs ROOM, and room is a PHYSICAL WIDTH — how many
         pixels of titlebar the card actually paints — not a ratio
         against the title's natural width.
         
         The ratio was the defect the chief reported as "titlebars have
         lost their buttons".  --titlebar-painted-ratio divides painted
         width by the TITLE's natural width, so a card carrying a long
         title reports a small ratio however wide the card is, and its
         buttons fade out on a card with ample room for them.  A card
         whose title wraps to three lines extinguished its buttons
         entirely.  Two quantities were being compared that measure
         different things.
         
         --titlebar-painted-w IS the physical width, already quantised
         per card by the mipmap level, so this is a precomputed
         per-level answer rather than a continuous recomputation — the
         same axis the rest of the card's chrome is on.  A button
         appears when its card paints wider than the room the button
         needs, which is what "needs room" means. */
      --btn-room-px: var(--btn-threshold-px, 150);
      --slot-amount: clamp(0,
        calc((var(--titlebar-painted-w) - var(--btn-room-px)) / 60), 1);
      opacity: var(--slot-amount);
      min-width: 0;
      max-width: calc(var(--slot-amount) * 99vw);
      overflow: hidden;
    }
    /* Navigational buttons are findable at EVERY level.
       
       A flip button is how the chief reaches a card's other faces; a
       card showing "FACE B" with no way back is a dead end, at any
       size.  /titlebar.tvg4.json's slot-flip card already declares
       threshold 0 for exactly this reason, and titlebar.css already
       said so in prose — but the exemption was written against
       .flipcard-btn, the class the flip button had BEFORE it moved to
       the scene.titlebarButtons registry.  After the move the flips
       rendered as .titlebar-btn, missed the exemption they were
       written for, and faded with everything else.
       
       Keyed on the DECLARED slot rather than on a class, so a button
       states its own kind and no future migration can silently strand
       it again. */
    .node .titlebar-btn[data-button-slot="flip"],
    .node > .titlebar > .flipcard-btn {
      --btn-threshold-px: 0;
      --slot-amount: 1;
    }
    /* Glyph wrapper — visually enlarged via transform:scale so the
       button's layout box stays at its rectangular size (padding,
       inherited line-height) while the symbol reads as a bold,
       prominent affordance.  Chief 2026-07-14: "the glyph should be
       large and centered".  translateY compensation offsets the
       character baseline: monospace fonts place symbol glyphs
       (arrows, geometric brackets) at the letter baseline, which
       sits ~78 % from the top of the em-square — so a scale-only
       transform centres the em-square on the button's centre but
       the CHARACTER pixels land in the bottom half.  translateY
       lifts the visual centre of the ink onto the button's mid-line. */
    .node > .titlebar > .titlebar-btn > .titlebar-btn-glyph {
      display: inline-block;
      transform: scale(1.5) translateY(-0.08em);
      transform-origin: 50% 50%;
      font-weight: 600;
      line-height: 1;
    }
    .node > .titlebar > .titlebar-btn:hover {
      background: rgba(136,221,192,0.12);
    }
    /* Left-anchored buttons (flex order < 0) sit at the very start
       of the titlebar.  Swap the margin so the 6 px gap is on the
       RIGHT (away from the agent slot to their right) instead of the
       LEFT (which would push them off the titlebar's left edge). */
    .node > .titlebar > .titlebar-btn[data-button-side="left"] {
      margin-left: 0;
      margin-right: 6px;
    }
    /* Legacy .maxi-btn class carries the same box treatment.
       Retained for any callers that still create .maxi-btn directly.
       Flex order for the generalised path is assigned per-button by
       renderTitlebarButtons via inline style. */
    .node > .titlebar > .maxi-btn {
      background: transparent;
      border: 1px solid #88ddc0;
      color: #88ddc0;
      border-radius: 3px;
      padding: 0 6px;
      margin-left: 6px;
      font: inherit;
      cursor: default;
      user-select: none;
      line-height: 1.2;
      min-width: 0;
      transition: background 0.12s;
    }
    .node > .titlebar > .maxi-btn:hover { background: rgba(136,221,192,0.12); }
    /* When opacity is 0 the slot stops accepting clicks too —
       no phantom hits over an invisible affordance. */
    .node > .titlebar > .titlebar-status[style*="opacity: 0"],
    .node > .titlebar > .titlebar-agent[style*="opacity: 0"] {
      pointer-events: none;
    }
