
/* ══════════════════════════════════════════════════════════════════════════════════════
 * D49 · THE PUBLISHED SCREEN OWNS THE VIEWPORT — the app-viewport law, on bisbe.xyz.
 *
 * FOUNDER, 2026-08-20: "all pages should take full screen lik a desktop or mobile app and not
 * scroll like a web page" — asked for "both in dev and on the bisbe.xyz public demo".
 *
 * Measured here before these rules: the app frame was a correct 100dvh box, and the DOCUMENT
 * around it was 1246px tall at 1440 — 132px of demo chrome above, 188px of explanation below —
 * so the page scrolled 346px and both bars scrolled away with it.
 *
 * The two bars stand. The app takes exactly one screenful of what is left.
 * Structural only — no colour, no type, nothing this sheet did not already own.
 *
 * ── ★ AND THEN THE APP ITSELF SLID, WHICH IS THE SAME COMPLAINT ONE LAYER DOWN. ★ ────
 * FOUNDER, 2026-08-21 ~08:30 ET, on the published demo: *"the Rooms page still scroll on
 * bisbe.xyz demo"* — with the rail sliding up under the demo's own banner.
 *
 * The first cut of this block put the explanation INSIDE `.bw-app` and made `.bw-app` a
 * scroller, reasoning that the note should be "one gesture away rather than pinned or lost".
 * That is a defensible sentence and it produced an indefensible screen: measured on the shipped
 * bundle, `.bw-app` was 768px tall over 982px of content, so ONE wheel gesture anywhere on the
 * page moved the whole app — surface top 132 → −82, rail top 203 → −11. The document did not
 * scroll and the APP did, which is the complaint wearing the fix's own clothes. It also produced
 * every L10b refusal on the world subjects: the harness opens the menus AFTER driving the
 * scrollers, so rows the app had already carried off the top read as clipped sheets.
 *
 * ★ THE APP REGION IS NOT A SCROLLER. ★ It is the app's screen, and nothing in it can drag the
 * app. The explanation keeps every word, every link and its place in the served HTML — it moves
 * into the demo's own bar as a disclosure whose panel is POSITIONED, so opening it costs the app
 * zero pixels and closing it changes nothing. That is what a desktop app does with an
 * "about this window" note, and the law FOUNDER stated is the desktop-app law.
 * ════════════════════════════════════════════════════════════════════════════════════ */
body.bw-screen{height:100vh;height:100dvh;overflow:hidden;display:flex;flex-direction:column}
body.bw-screen>.bisbe-bar,body.bw-screen>.bw-bar{flex:none}
/* `overflow:hidden`, not `auto`: there is nothing under the app to reach, so a scroller here
   could only ever move the app. `overscroll-behavior` stays as the second fence — an inner pane
   that reaches its end must not chain OUT of the app at all. */
.bw-app{flex:1 1 0%;min-height:0;overflow:hidden;overscroll-behavior:contain;
  display:flex;flex-direction:column}
/* ONE SCREENFUL OF APP, and now it is the WHOLE of the region rather than 100% of a region that
   was taller than itself. `flex:1 1 0%; min-height:0` with `height:auto` overriding the app
   sheet's own `height:100dvh` — the attribute keeps the specificity so this wins on weight and
   not on which `<link>` happens to come last. */
/* BOTH shell roots. Customers is served from the other route group and has its own root class —
   naming only one of them published a 554px app in a 768px space on 21 of the 49 screens. */
.bw-app>.atlas-surface[data-posture],.bw-app>.crm-surface[data-posture]{flex:1 1 0%;min-height:0;height:auto}
/* ★ NOTHING STICKS IN A WORLD THAT DOES NOT SCROLL. ★ (REQ-461 Q42, Walker defect #2.)
   Measured on the published Ask door: `.at-actions` — "Release all 4 / Release only the ones I
   tick / Change who can read this" — overlapped `ul.at-qlist` by 322×108px at 1280 and the
   queue's own header by 349×55px at 375. It printed straight over the draft it is about.

   The cause is that `position:sticky` resolves against the nearest SCROLL CONTAINER, and the
   containers differ between the two places this markup renders. atlas.css says so in its own
   words at `.atlas-surface`: *"`overflow-x: hidden` makes this element a SCROLL CONTAINER,
   which silently captures every `position: sticky` descendant … The Add-a-job review's pinned
   action row was inert on the real app for exactly this reason"* — which is why the app sheet
   uses `clip`. This published world then re-introduces the very ancestor that sheet avoided:
   `.bw-app` is `overflow:hidden` DELIBERATELY (the block above — the app region is not a
   scroller), so the row sticks to the bottom of a box that never moves and lands on the content.

   The app-side rule is right for the app and must not move: in the live product the queue pane
   genuinely scrolls and keeping the release row in reach is the point. In THIS world the row is
   three DISABLED buttons on a page that cannot scroll, so it has nothing to stay in reach of —
   and it is annotating a draft it is currently covering up. Un-stuck, scoped to `.bw-app`, so
   the app keeps its behaviour and the world stops printing over itself.

   TWO CLASSES ARE NAMED, AND THE SECOND ONE IS WHY THE GATE EXISTS. `.at-actions-sticky` was
   the Walker's finding; `.crm-q-bar` — Contacts' approve-all bar, `position:sticky; bottom:0`
   in its own sheet — was found by `demo-world-375.mjs` on the same run, printing over the
   queue list by 307×57px at 375 and 968×75px at 1280 on a page nobody had reported. Those are
   the only two in the 111 published pages, measured, so they are named rather than swept: a
   `[class*="sticky"]` rule would silently un-stick a pane head that is behaving perfectly.
   The GATE is what generalises — it reds on ANY sibling overlap on any page of any world, so
   the third one is caught by measurement rather than by somebody guessing a selector. */
.bw-app .at-actions-sticky,.bw-app .crm-q-bar{position:static;background:none}
/* ★ THE DEMO-MODE CONTRACT'S OWN THREE MARKS. ★ See `honestOffControls` /
   `honestFenceFamilies` in this file for what puts them on the page and why.

   THEY ARE DELIBERATELY QUIET AND DELIBERATELY NOT HIDDEN. The walk that produced them
   describes both failure directions in one door: a dead control that says nothing reads as
   broken, and a wall of repeated notices reads as noise ("eight identical rows to get here").
   So they sit at 12.5px in the dimmed ink — present at a glance, never competing with the row
   they annotate.

   NO HUE CARRIES MEANING. Each mark is glyph + words; the colour is the same dimmed ink for
   all three. FOUNDER is red/green colourblind, so a green "opens" against a red "dead" would
   be two identical marks to him. `◇` and `◉` are the world's own shapes for the same two
   states, already used on its rows. */
.bw-off,.bw-fence-tag,.bw-open-tag{font-size:12.5px;font-weight:500;
  font-family:'Familjen Grotesk',system-ui,sans-serif;line-height:1.35}
/* The control's own reason is a sibling of the control, on the page's ground — the dimmed ink. */
.bw-off{color:var(--bw-dim)}
/* ★ THE ROW MARKS TAKE THEIR HOST'S INK, NOT THE PAGE'S. ★ These live INSIDE the anchor, and
   an anchor is not always on the page's ground: `crm-cta-p` is a filled pill — white type on
   #824032 — and a mark set in the page's dimmed grey rendered as near-illegible slate ON that
   brown (measured: rgb(84,91,104) on rgb(130,64,50)). `currentColor` at reduced opacity is
   correct on every ground by construction, including the dark theme, and needs no per-control
   rule. It is also the only form that cannot be broken by a control this world has not met yet. */
.bw-fence-tag,.bw-open-tag{color:currentColor;opacity:.7}
.bw-off{display:inline-block;margin:6px 0 0 8px;max-width:44ch}
/* Keep a form's read-only note below its controls, never between a field and Apply. */
.bw-app form[data-demo-inert]>.bw-off{order:1}
/* ★ THE REASON MAY NEVER TAKE WIDTH FROM THE CONTROL IT EXPLAINS. ★
   Customers' search row (`.fld-find`) is `display:flex; flex-wrap:nowrap`, so the note joined
   it as a fourth item and NEGOTIATED for width: measured at 375, the search field went from
   ~250px to 113px and its placeholder was cut from "Find a property, a person, a pie" to "Find
   a pro". Explaining a control by shrinking it is a worse defect than the one being closed.
   So on a flex row the note takes a full basis and lands on its own line under the row, and the
   row is made to wrap so it can. `flex-basis` is inert off a flex parent, which is every other
   place this note appears, so nothing else moves.

   ── ★ AND IT HAS TO OUTWEIGH THE APP SHEET, WHICH THE FIRST FORM DID NOT. ★ ───────────
   Measured on the published world at 375 (REQ-461 Q42, Walker defect #1): Ask's `.at-app`
   rendered 419px wide inside a 375px phone, both panes with it, the desk strip cut mid-word.
   The cause was THIS RULE LOSING A SPECIFICITY CONTEST AND SAYING NOTHING ABOUT IT.
   `div:has(>.bw-off)` weighs (0,1,1); the app's own `.atlas-surface .at-bar-controls
   {flex-wrap:nowrap}` weighs (0,2,0) and wins — so the header row never wrapped, the note
   kept `flex:0 0 100%` (shrink 0) on a NOWRAP row, and a 312px unshrinkable item pushed the
   row's min-content to 670px. The note did exactly what the paragraph above forbids: it took
   width from the controls it explains, and took the whole app sideways with it.

   The declaration was right and only its weight was wrong, so the fix is weight. The second
   selector is scoped to `.bw-app` — the publisher's own wrapper, present on every page of
   this world and on nothing else — which makes it (0,2,1): heavier than any two-class app
   rule, and inert everywhere outside the published world. `demo-world-375.mjs` is the
   falsifier; it reds on this exact geometry.

   ── ★ AND THE TAG LIST WAS THE THIRD MEMBER OF THE SAME FAULT (REQ-461 round 2). ★ ────
   FOUND BY LOOKING at the rendered 375 frame of `customers-tasks.html` after every gate on
   this branch was green — eleven notes, each one measured 275px wide starting at x=101, so
   each RIGHT EDGE landed at 376 inside a 341px card and the sentence was cut mid-word at the
   screen edge ("…nothing in this demo write"). Their parent is an `<li>`, and `li` was not
   in the tag list, so the wrap never applied and `flex:0 0 100%` sat on a `nowrap` row —
   byte-for-byte the fault the paragraph above describes, one tag along.

   NOBODY'S GATE SAW IT, and the reason is worth writing down: `demo-world-375.mjs` clause 1
   compares an element's WIDTH to the viewport (275 < 375, pass) and the document's own
   scrollWidth (clipped, so unchanged); the Walker's harness measured past-the-VIEWPORT with a
   1px tolerance and the overflow was exactly 1px past 375. An element narrower than the screen
   can still be 35px outside the card it belongs to. Clause 4 of that sweep now measures the
   note against ITS OWN PARENT, which is the thing that was actually wrong.

   THE LIST IS GONE, because a list is a thing to keep in step with every door built after
   this — the same argument `familyKeyFor` and `CONTROL_LABEL_CHARS` already make. Any
   element holding one of these notes wraps, whatever its tag. The weight is kept by naming
   the note's class twice inside `:has()`: `.bw-off.bw-off` is (0,2,0) and matches exactly
   the same element, so with `.bw-app` the rule weighs (0,3,0) — still heavier than the app's
   (0,2,0) `.atlas-surface .at-bar-controls`, with none of the tag enumeration. */
:has(>.bw-off){flex-wrap:wrap}
.bw-app :has(>.bw-off.bw-off){flex-wrap:wrap}
/* ★ A FULL LINE, SAID IN THE INLINE AXIS — NOT IN "WHICHEVER AXIS THIS PARENT HAPPENS TO
   RUN". ★ (REQ-461 round 2, the last finding of clause 4.)
   `flex:0 0 100%` was the right idea in the wrong vocabulary: `flex-basis` is the MAIN
   axis, so on the one COLUMN container in the world that holds a note — `._probe_4038f7` on
   `customers-tech-integrations.html` — it read as "100% of the parent's HEIGHT", the note
   could not fit, and the wrap permission granted right above did the only thing wrap can do
   on a column: it started a SECOND COLUMN. Measured at 1280 in both themes, the note landed
   115–123px outside the box it explains. The rule intended to keep the note off its control's
   width was pushing it clean out of its parent.
   `width:100%` is the same instruction stated in the axis that was always meant: on a row it
   forces the note onto its own line exactly as before (basis `auto` resolves to `width`),
   and on a column it is the stretch the item already had, so the basis stops overflowing and
   the wrap has nothing to do. One declaration, correct in both directions, no direction test —
   which is fortunate, because CSS has no way to ask. */
.bw-off{flex:0 0 auto;width:100%;margin-left:0}
/* ★ THE SHORT FORM (CPO cap, PR #2343 §4). ★ Two words and a shape, so it needs neither the
   sentence's reading measure nor permission to break across lines — "not in this / demo" over
   two lines is a longer mark than the sentence it replaced. It KEEPS the full-basis rule above:
   the width-stealing defect that rule closes is about the note being a flex ITEM at all, not
   about how many characters it holds, and a short mark on its own line under the control is
   the same shape the row marks already use. */
.bw-off-short{max-width:none;white-space:nowrap}
/* Inside the anchor, so it moves with the row and is read out as part of the link's own name —
   a mark a screen reader meets AFTER following the link is not a mark.

   TWO FITS, because "inside an anchor" is two different places. See `CONTROL_LABEL_CHARS`.
     row   — a multi-line row (an account, a conversation). It is the row's FIRST child and gets
             a full-width line of its own above the row's content. Placed LAST it was a 33-line
             row's last item across an 18px grid gap, sitting 45px below its own content and
             15px above the rule under it — reading as a heading for the row BELOW. First is
             also the scannable position: every row now opens with its own answer, or nothing.
     label — a pill or a short text link (CHECK IN, New property, "Check in here →"). Beside the
             words. `display:inline` is not written here on purpose: these anchors are often
             `display:flex`, which BLOCKIFIES its children, so the declaration would compute to
             `block` and read as a promise the layout does not keep. Flex items do not overlap,
             so the default placement is already right; only the ink needed fixing (above). */
.bw-fence-tag[data-fit="row"],.bw-open-tag[data-fit="row"]{display:block;grid-column:1/-1;margin-bottom:2px}
.bw-fence-tag[data-fit="label"],.bw-open-tag[data-fit="label"]{margin-left:7px;white-space:nowrap}
/* ★ AND THE ROW MARKS NEED THE SAME PERMISSION THE NOTE HAS. ★ (REQ-461 round 2, clause 4.)
   The wrap rule above was written for `.bw-off` and stopped there, so the fence marks kept
   hitting the other half of the identical fault: measured on the published `rooms` doors, a
   96px `◇ not in this demo` inside a 172px `a.bx-win-rm` — the anchor is `display:flex`
   with `nowrap`, so the mark was laid beside "# Finance · starts soon" and ended 46px past
   the row it belongs to, on seven pages in both themes. It FITS; it was simply never allowed
   to drop.

   Wrapping is permission, not instruction: where the mark fits beside the words it still sits
   beside them (which is what `data-fit="label"` is for), and only where it does not does it
   take the line below. `white-space:nowrap` therefore stays and now helps — the mark moves
   as one thing rather than splitting into "not in this / demo". Same weight idiom as above,
   for the same reason: (0,3,0) beats any two-class app rule without naming a single tag. */
:has(>.bw-fence-tag),:has(>.bw-open-tag){flex-wrap:wrap}
.bw-app :has(>.bw-fence-tag.bw-fence-tag),.bw-app :has(>.bw-open-tag.bw-open-tag){flex-wrap:wrap}
/* The one that opens is the only one a reader is being pointed AT, so it is the only one set
   in the full ink. Weight and ink, not hue. */
.bw-open-tag{color:var(--bw-ink);font-weight:700}
/* THE LIST-SCOPED FENCE (REQ-477) is the same mark speaking about a whole list instead of one
   row, so it is the same type at the same size. It is set in the full ink for the same reason
   `.bw-open-tag` is: on a list of a hundred rows it is the ONLY mark there is, and a mark
   nobody reads is the fence a person has to go and find. */
.bw-fence-tag[data-scope="list"]{color:var(--bw-ink);opacity:1;font-weight:600}

/* ════════════════════════════════════════════════════════════════════════════════════
   ★ NOTHING DEAD IS DRAWN AS PRESSABLE. ★ (REQ-477)
   ════════════════════════════════════════════════════════════════════════════════════
   FOUNDER, walking the demo: *"when I click a page nothing happens"* — and the reason he
   clicked is that the row said, in the only language a pointer has, that it would do
   something. The app's own sheets set `cursor:pointer` on these classes unconditionally,
   because in the app every one of them acts.

   The honest note beside a dead control (the demo-mode contract) corrects the belief AFTER
   the reader has formed it. This corrects it BEFORE — at the moment the pointer crosses the
   control, which is the only moment that stops the click.

   IT IS KEYED ON THE DISABLED STATE, NEVER ON A CLASS LIST. Every control this world kills
   carries the `disabled` attribute (`world-is-read-only.test.mjs` proves that on the
   committed bytes), so this reaches all of them — including the ones on a door that does not
   exist yet. A list of class names here would be a list to keep in step with the app.

   ★ FIVE, AND THE NUMBER IS MEASURED. ★ Repeating an attribute selector is this file's
   established way of buying specificity without `!important` and without naming a single
   app class (see the `:has()` wrap rules above, which repeat a class for the same reason).
   The number is not a guess: every `cursor:pointer` rule in the published sheets was
   enumerated and ranked, and the strongest is `.atlas-surface .wk-make-review
   .wk-promise-row .wk-make-label` at (0,4,0). Five attribute selectors is (0,5,0), which
   beats it — and `scripts/world-walks-like-the-app.test.mjs` re-runs that ranking over the
   COMMITTED sheets, so a future app rule that climbs past this fails there instead of
   quietly restoring the pointer.

   IT CANNOT REACH ANYTHING LIVE. Every control this wave re-arms is emitted as a fresh
   `<a>` or `<summary>` carrying neither attribute, so the rule is keyed on exactly the
   state it is about. */
[disabled][disabled][disabled][disabled][disabled],
[aria-disabled="true"][aria-disabled="true"][aria-disabled="true"][aria-disabled="true"][aria-disabled="true"]{cursor:default}

/* ════════════════════════════════════════════════════════════════════════════════════
   ★ THE DISCLOSURES THAT WORK AGAIN. ★ (REQ-477 — "everything except write capability")
   ════════════════════════════════════════════════════════════════════════════════════
   A `<details>` needs no script, which is what makes it the only shape available here: the
   read-only guarantee is that this folder ships no code, and buying a working control with a
   line of JavaScript would trade the guarantee for the convenience.

   The summary keeps the app's OWN class (`.crm-q-toggle`, `.bx-openwork-toggle`) so it keeps
   the app's own look; what this block adds is only what a `<details>` needs and the app's
   sheet cannot know: the marker off, the pointer back ON (this control is alive again, so the
   rule above must not reach it — a summary is not disabled and carries neither attribute), and
   the two labels swapped by OPEN state instead of by `aria-expanded`. */
.bw-disclosure>summary{cursor:pointer;list-style:none}
.bw-disclosure>summary::-webkit-details-marker{display:none}
.bw-when-shut{display:none}
.bw-disclosure:not([open])>summary .bw-when-open{display:none}
.bw-disclosure:not([open])>summary .bw-when-shut{display:inline}
/* The app rotates this glyph on [aria-expanded="true"]; frozen, OPEN is the state. */
.bw-disclosure[open]>summary i{transform:rotate(180deg)}
/* The rooms toggle ships both labels already and swaps them on the app's own attribute,
   which a <details> does not have. Same two labels, re-keyed on the state that does exist. */
.bw-disclosure-room>summary .bx-openwork-lbl[data-when="closed"]{display:none}
.bw-disclosure-room:not([open])>summary .bx-openwork-lbl[data-when="open"]{display:none}
.bw-disclosure-room:not([open])>summary .bx-openwork-lbl[data-when="closed"]{display:flex}

/* The product's own voice is drawn by the app's own sheet — see components/bisbe. */
:root{--bw-ink:#14171D;--bw-dim:#545B68;--bw-line:rgba(20,23,29,.14);--bw-paper:#FFFFFF;--bw-g0:#FAFBFD}
[data-theme="dark"]{--bw-ink:#F2F4F8;--bw-dim:#A9B0BC;--bw-line:rgba(242,244,248,.18);--bw-paper:#1A1D24;--bw-g0:#101318}
.bw-bar{position:relative;z-index:59;background:var(--bw-paper);border-bottom:1px solid var(--bw-line);
  font-family:'Familjen Grotesk',system-ui,sans-serif}
.bw-bar-in{max-width:1180px;margin:0 auto;display:flex;align-items:center;gap:12px;padding:10px 14px;flex-wrap:wrap}
/* ONE MARK PER PLACEMENT (FOUNDER, 2026-08-12). The lockup used to be the B monogram with
   the word "Bisbe" set beside it in Cinzel — the product's name said twice in one control,
   once as artwork and once as type. The compact artwork IS the wordmark, so it is the whole
   lockup now and the anchor's aria-label carries the accessible name. The reversed file is
   the correct mark on the dark ground; swapping in CSS keeps the choice off the first paint,
   which the mark-light-only lockup got wrong (forest green on #1A1D24, near invisible). */
/* THE READ-ONLY FENCE PILL — 14px, not 12.5px. It is the one sentence on the frame that
   tells a visitor nothing here writes, and at 12.5px it read as decoration beside a 18px
   lockup. A fence a person does not read is not a fence. (Its ≤700px override still steps
   to 12px; raising that is the pill lane's own call, not this regen's.)
   NO THEME PREFIX: a dangling [data-theme="dark"] from a deleted lockup-swap rule sat in
   front of this selector — CSS ignores the comment between them — so the pill was styled
   ONLY in dark theme and rendered completely unstyled in the default light theme on every
   published page (review finding, 2026-08-13). The rule is theme-agnostic by its vars. */
.bw-ro{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--bw-line);border-radius:999px;
  padding:5px 11px;font-size:14px;font-weight:600;color:var(--bw-ink);white-space:nowrap}
.bw-ro b{font-weight:700}
.bw-spacer{flex:1 1 auto}
.bw-tools{display:flex;align-items:center;gap:4px;flex-wrap:wrap}
.bw-tools a,.bw-bar button{display:inline-flex;align-items:center;justify-content:center;min-height:44px;
  padding:8px 12px;border-radius:10px;border:1px solid transparent;background:none;color:var(--bw-ink);
  font:inherit;font-size:14px;text-decoration:none;cursor:pointer}
.bw-tools a:hover,.bw-bar button:hover{border-color:var(--bw-line)}
.bw-bar button{border-color:var(--bw-line)}
.bw-note{max-width:1180px;margin:0 auto;padding:10px 14px 0;color:var(--bw-dim);font-size:13.5px;
  font-family:'Familjen Grotesk',system-ui,sans-serif;line-height:1.5}
.bw-note a{color:inherit}
.bw-foot{max-width:1180px;margin:0 auto;padding:26px 14px 44px;color:var(--bw-dim);font-size:13.5px;
  font-family:'Familjen Grotesk',system-ui,sans-serif;line-height:1.6;border-top:1px solid var(--bw-line);margin-top:26px}
.bw-foot h2{font-size:14px;color:var(--bw-ink);margin:0 0 8px}
.bw-doors{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}
.bw-doors a{display:inline-flex;min-height:44px;align-items:center;padding:8px 12px;border:1px solid var(--bw-line);
  border-radius:10px;text-decoration:none;color:var(--bw-ink);font-size:14px}
.bw-doors a[aria-current="page"]{font-weight:700;box-shadow:inset 0 -2px 0 var(--bw-ink)}
.bw-lede a,.bw-sub a,.bw-foot p a{color:var(--bw-ink);text-decoration:underline;
  text-underline-offset:2px;text-decoration-thickness:1px}
@media (max-width:700px){.bw-ro{font-size:12px}}

/* ══════════════════════════════════════════════════════════════════════════════════════
 * ★ "WHAT YOU ARE LOOKING AT" — the same words, in a control instead of under the app. ★
 *
 * The panel is ABSOLUTELY POSITIONED. That is the whole point and it is the one property that
 * must never be relaxed: in flow it would take height from the app, and a note that shrinks the
 * screen when you read it is the scrolling complaint with extra steps. Positioned, it costs the
 * app nothing open or closed.
 *
 * It is a `<details>`, so it works with no JavaScript — the demo's own standing rule is that
 * every sentence is readable in the bytes a crawler is served, and the words are in the document
 * whether the disclosure is open or not.
 *
 * The cap is `min(60vh, 420px)` with its own scroller and `overscroll-behavior:contain`: on a
 * short screen the note scrolls ITSELF and still cannot reach past its own edge into the app.
 * ════════════════════════════════════════════════════════════════════════════════════ */
.bw-why{position:relative}
.bw-why>summary{display:inline-flex;align-items:center;gap:7px;min-height:44px;padding:8px 12px;
  border-radius:10px;border:1px solid transparent;color:var(--bw-ink);font:inherit;font-size:14px;
  cursor:pointer;list-style:none;white-space:nowrap}
.bw-why>summary::-webkit-details-marker{display:none}
.bw-why>summary::after{content:"\203A";display:inline-block;transform:rotate(90deg);
  font-size:15px;line-height:1;color:var(--bw-dim)}
.bw-why[open]>summary::after{transform:rotate(-90deg)}
.bw-why>summary:hover,.bw-why[open]>summary{border-color:var(--bw-line)}
.bw-why-panel{position:absolute;right:0;top:calc(100% + 8px);z-index:5;
  width:min(520px,calc(100vw - 28px));max-height:min(60vh,420px);overflow-y:auto;
  overscroll-behavior:contain;background:var(--bw-paper);border:1px solid var(--bw-line);
  border-radius:12px;padding:16px 18px;box-shadow:0 12px 32px rgba(20,23,29,.16);
  color:var(--bw-dim);font-size:13.5px;line-height:1.6;text-align:left}
.bw-why-panel h2{font-size:14px;color:var(--bw-ink);margin:0 0 8px}
.bw-why-panel p{margin:0 0 10px}
.bw-why-panel p:last-child{margin-bottom:0}
.bw-why-panel b{color:var(--bw-ink)}
.bw-why-panel a{color:var(--bw-ink);text-decoration:underline;text-underline-offset:2px;
  text-decoration-thickness:1px}
/* ★ ON A PHONE THE PANEL IS ANCHORED TO THE BAR, NOT TO THE CONTROL. ★
   The first cut kept `.bw-why` as the containing block and centred with
   `left:50%; transform:translateX(-50%)` — but 50% of a 122px SUMMARY is not 50% of the
   screen, so a 355px panel hung ~120px off the left edge at 375 and its first words were
   unreadable. That is the #2131 class exactly ("The More menu drew off the side of the phone"),
   caught here by reading the render rather than by trusting the rule.
   `position:static` hands the containing block up to `.bw-bar` (which is already
   `position:relative`), so `left/right` are the SCREEN's edges and `top:100%` is the bar's
   own bottom — the panel spans the width under the bar and cannot hang off either end. */
@media (max-width:700px){
  .bw-why{position:static}
  .bw-why-panel{left:10px;right:10px;width:auto;max-height:min(70vh,420px);top:calc(100% + 6px)}
  .bw-why>summary{padding:8px 10px}
  /* ★ AND THE THIRD ITEM LEAVES THE ROW, KEEPING THE PHONE'S BAR AT FOUR (CD R14). ★ The
     same words are inside the panel above, which is open to one tap and is where "show me
     another one" belongs. Measured cost of leaving it in the row: ~45px of a 844px first
     screen, on the door "Walk it" lands on. */
  /* `a.` ON PURPOSE. `.bw-tools a` two blocks up is (0,2,0) and sets `display:inline-flex`,
     so a bare `.bw-kinds` (0,1,0) loses to it inside this query and the item stayed in the
     row — rendered, measured, and only visible in the frame. (0,2,1) wins without
     `!important`, which is the same arithmetic `world-walks-like-the-app.test.mjs` does to
     the dead-control cursor rule. */
  .bw-tools a.bw-kinds{display:none}
}

/* ════════════════════════════════════════════════════════════════════════════════════
   ★ THE DOOR SAYS ITS OWN NAME, AND ITS OWN SENTENCE. ★ (CD GATE, REQ-477 final R7)
   ════════════════════════════════════════════════════════════════════════════════════
   *"The page's own subtitle is a disclaimer … the `Ask` title sits below and smaller than
   the pills, with 'Works in the live app. Nothing in this demo writes.' indented under it in
   the slot where the door's own sentence belongs. The page title is the least prominent thing
   in its own header."*

   Two facts behind that, both read off the app's own sheet rather than guessed:
     · `.atlas-surface .at-h1` is 15px/800 in a `flex-wrap:nowrap` bar, and a narrow-width
       rule takes it to `position:absolute; width:1px; clip` — so on the phone the entry door
       has NO TITLE AT ALL, and the two pills lose their words in the same rule.
     · the only full sentence in that header was the demo disclaimer, which R1 has now taken
       off the current tab entirely.
   So the title comes back at every width and the door's own sentence goes where the
   disclaimer was. This is the product's own header, not chrome added above it — the
   NO-ORPHAN rule (nothing between `.bw-app` and the app frame) is untouched. */
/* ★ BOTH ROWS WRAP, AND THE SECOND ONE IS NOT OPTIONAL. ★ `.at-bar-controls` is
   `flex-wrap:nowrap` in the app, which is safe there because the app HIDES the button words
   below 430 — the same rule that hid the title. Give the words back without giving the row
   permission to wrap and its min-content is 577px inside a 375px phone: measured, the whole
   `.at-app` laid out at 595px and the door, the ask button and the marked row were all cut
   off at the right edge. Caught in the frame, which is the only place it exists. */
.bw-screen .atlas-surface .at-bar{flex-wrap:wrap}
.bw-screen .atlas-surface .at-bar-controls{flex-wrap:wrap;row-gap:6px}
.bw-screen .atlas-surface .bw-door-head{display:flex;flex-direction:column;gap:1px;min-width:0;flex:1 1 auto}
.bw-screen .atlas-surface .bw-door-head .at-h1{position:static;width:auto;height:auto;overflow:visible;
  clip:auto;white-space:normal;font-size:16px;line-height:1.2}
.bw-door-lede{margin:0;font-size:12.5px;line-height:1.35;color:var(--bw-dim)}
/* ★ AND THE CONTROLS BESIDE IT GET THEIR WORDS BACK. ★ The same app rule that hid the title
   also hides `.at-btn-word` and `.at-more-word`, so on the phone the two header pills
   rendered as `● 13` and `◇ 4` — the CD gate: *"two numeric pills lose their words at 390
   and become unreadable glyph-and-digit tokens."* In the APP that is a reasonable trade: the
   pill opens a panel that says what it is. Frozen, the panel never opens, so the abbreviation
   has nothing behind it and the reader is left with a digit. */
.bw-screen .atlas-surface .at-btn-word,.bw-screen .atlas-surface .at-more-word{
  position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:nowrap}

body{margin:0;background:var(--bw-g0);color:var(--bw-ink);font-family:'Familjen Grotesk',system-ui,sans-serif}
.bw-wrap{max-width:70ch;margin:0 auto;padding:40px 16px}
.bw-h1{font-family:'Cinzel',Georgia,serif;font-size:clamp(26px,4vw,38px);margin:0 0 14px;line-height:1.15}
.bw-wrap p{font-size:16.5px;line-height:1.65;margin:0 0 14px}
.bw-wrap a{color:inherit}
