/* ap-shell.css — the app shell's STATIC dress: the rail, the editor tabs,
 * and the cross-document view-transition opt-in.
 *
 * This used to be injected by menu.js, which runs after the session fetch —
 * long after the new page's first paint. Cross-document view transitions
 * judge the opt-in AT REVEAL, so a rule that arrives by JS misses the
 * moment and every navigation white-flashed, then shifted right when the
 * rail finally landed. A render-blocking <link> in each page's head is the
 * fix: the rule and the rail's dress are simply there before anything
 * paints. menu.js still injects all of this as a fallback on any page that
 * forgot the link. */

@media (min-width: 921px) { body.ap-railed { padding-left: 212px; } }

/* ── STANDING IN SOMEBODY ELSE'S WORKSPACE ───────────────────────────────────
 *
 * A bar across the top of EVERY page, not a panel on the console.
 *
 * It was on the console only, and that made a trap out of the thing it was
 * supposed to make obvious: staff hold no seat in the workspace they step
 * into, so they read as a viewer — which means most floors render their
 * empty state AND the rail tears itself down (one door is "nothing to
 * navigate"). You landed on /dashboard, saw "the dashboard rides a working
 * seat", and had no rail, no banner, and no way back to the console except
 * typing /platform into the address bar.
 *
 * So the bar ships in the shell's static dress, beside the rail's, and
 * carries the two things you always need from inside somebody else's
 * workspace: the way out, and the wheel. Amber while you are only looking,
 * red once the wheel is taken — the same two words the console uses.
 *
 * Fixed, above the rail's z-index, and the rail is pushed down by its height
 * rather than sliding under it. */
#ap-stepin { position: fixed; left: 0; right: 0; top: 0; z-index: 60;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 7px 14px; font-family: var(--font); font-size: 11.5px; letter-spacing: .4px;
  background: var(--panel); border-bottom: 1px solid var(--amber); color: var(--amber); }
#ap-stepin.ap-wheel { border-bottom-color: var(--red); color: var(--red); }
#ap-stepin b { font-weight: 700; }
#ap-stepin .ap-stepin-acts { margin-left: auto; display: flex; gap: 6px; }
#ap-stepin button { font: inherit; font-size: 10.5px; letter-spacing: 1px; cursor: pointer;
  padding: 4px 9px; border-radius: 6px; background: transparent;
  border: 1px solid currentColor; color: inherit; }
#ap-stepin button:hover { background: color-mix(in srgb, currentColor 12%, transparent); }
body.ap-stepped { padding-top: 34px; }
/* Everything else that pins itself to the top of the viewport starts below
   the bar rather than under it. The rail is the shell's own; `.side` is the
   full-height drawer the deals board opens, and its heading and close control
   live in exactly the 34px the bar would have covered. Any future panel that
   pins to top: 0 belongs on this list. */
body.ap-stepped #ap-rail,
body.ap-stepped .side { top: 34px; }
/* The VIEW AS hairline keeps its own warning, just under the bar instead of
   across it — a preview inside a step-in is two different things to know. */
body.ap-stepped:has(.ap-viewas-on)::before { top: 34px; }
@media (max-width: 920px) {
  body.ap-stepped { padding-top: 52px; }
  body.ap-stepped #ap-rail,
  body.ap-stepped .side,
  body.ap-stepped:has(.ap-viewas-on)::before { top: 52px; }
}
/* THE RAIL'S RIGHT EDGE IS ONE ACCENT LINE, and the selected door is a
   detour in it. The line runs the full height of the rail, reaches the door
   you are standing on, turns and goes around it — over its top, down its
   rounded left corner, back along its bottom — and carries on down. The door
   is filled with the page's own ground, so what you see is the canvas
   pushing INTO the rail with the line drawn around the shape it makes.
   Selection and divider stop being two marks that happen to be near each
   other. */
#ap-rail { position: fixed; left: 0; top: 0; bottom: 0; width: 184px; background: var(--panel);
  border-right: 1px solid var(--accent); display: flex; flex-direction: column; gap: 2px;
  padding: 16px 10px; z-index: 50; font-family: var(--font); }
/* EVERY door reserves the 1px the selected one paints, so nothing shifts by
   a pixel when the selection moves. The right side is 0 on all of them: that
   edge belongs to the rail's own line. */
#ap-rail a { display: block; color: var(--dim); text-decoration: none; font-size: 11px; letter-spacing: .6px;
  padding: 8px 10px; border: 1px solid transparent; border-right: 0;
  border-radius: 8px; white-space: nowrap; }
#ap-rail a:hover { color: var(--fg); background: color-mix(in srgb, currentColor 6%, transparent); }
/* THE SELECTED DOOR IS A HOLE IN THE RAIL, not a highlight on it.
 *
 * It used to be painted in the accent — teal text, teal edge, a teal wash —
 * which made the one thing you are already looking at the loudest thing on
 * the screen, and spent the accent on information nobody needs: you know
 * which floor you are on, you are standing on it.
 *
 * So it wears the PAGE's own ground instead and runs to the rail's right
 * edge, over the divider, with only its left corners rounded. The effect is
 * that the canvas reaches back into the rail and takes the row with it, so
 * the door reads as continuous with what it opened rather than as a chip
 * sitting beside it. Nothing is coloured; the connection does the work.
 *
 * -11px is the rail's 10px of padding plus its 1px border — the exact
 * distance to the edge. Painted over the parent's border on purpose: the
 * divider is what the selected row has to break to join the page. */
#ap-rail a.on { color: var(--fg); border-color: var(--accent);
  background: var(--bg); margin-right: -11px; padding-right: 21px;
  border-radius: 8px 0 0 8px; }
#ap-rail a.on:hover { background: var(--bg); color: var(--fg); }
/* THE MARK GETS THE RAIL'S FULL WIDTH. It used to be inset 10px on each
   side to line up with the doors' text, which cost it a fifth of the only
   dimension it had — a wordmark is width-bound here, and 10px of tidiness
   on each side was 20px off the logo. The doors indent; the mark does not. */
.ap-rail-brand { font-size: 12px; color: var(--accent); padding: 2px 0 16px; font-weight: 700; letter-spacing: 2px; }
/* A wordmark is wide: let it take the rail's width and cap the HEIGHT,
   rather than capping it small enough to be unreadable. */
.ap-rail-brand img { max-height: calc(58px * var(--ap-mark-scale, 1));
  max-width: calc(100% * var(--ap-mark-scale, 1)); width: auto; display: block; }
/* The mark, wherever it stands alone — the rail's brand slot caps it its
   own way. On the sign-in door it is the whole of the identity, so it gets
   room: taller, and never wider than the card. */
.ap-mark img { max-height: calc(38px * var(--ap-mark-scale, 1));
  max-width: calc(100% * var(--ap-mark-scale, 1)); width: auto; display: block; }
#ap-signedout .ap-mark img { max-height: calc(52px * var(--ap-mark-scale, 1)); }
/* TWO MARKS, ONE CHOICE, MADE IN CSS. A logo drawn for a white page
   disappears on a dark one, so an org may set both — and the pick has to
   survive the rail's cached markup and a theme toggle that only flips an
   attribute. Light is the app's default; dark is the absence of it. */
img.ap-mark-dark { display: none; }
html:not([data-theme="light"]) img.ap-mark-light { display: none; }
html:not([data-theme="light"]) img.ap-mark-dark { display: block; }
.ap-rail-sep { height: 1px; background: var(--line); margin: 8px 6px; flex: none; }
/* The foot gathers what the headers used to scatter: identity, the way
 * out, the theme, and the deploy stamp. */
.ap-rail-foot { margin-top: auto; display: flex; flex-direction: column; gap: 7px; padding: 0 6px; }
.ap-rail-foot button { background: none; border: 1px solid var(--line); color: var(--fg); border-radius: 8px;
  font: inherit; font-size: 13px; padding: 3px 9px; cursor: pointer; }
.ap-rail-foot-row { display: flex; align-items: center; gap: 8px; }
.ap-rail-user { font-size: 10px; color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* THE WAY OUT IS A BUTTON — a link was something browsers would follow on
   their own. It carries an icon and its words, so the narrow rail can drop
   the words and keep the control. Scoped under #ap-rail so the collapsed
   rules below can outrank it; the old pair lost to this selector's id and
   stuck a full-width SIGN OUT pill out of a 52px rail. */
#ap-rail .ap-rail-foot button.ap-rail-out { display: inline-flex; align-items: center; gap: 7px;
  background: none; border: 1px solid var(--line); border-radius: 8px; padding: 5px 11px;
  font: inherit; font-size: 10px; letter-spacing: 1px; color: var(--dim); cursor: pointer; flex: none; }
#ap-rail .ap-rail-foot button.ap-rail-out:hover { color: var(--fg); border-color: var(--dim); }
#ap-rail .ap-rail-out-ico { font-size: 12px; letter-spacing: 0; }
#ap-rail .ap-rail-foot-row > button { flex: none; }
.ap-rail-ver { font-size: 9px; letter-spacing: 1px; color: var(--dim); opacity: .7; font-variant-numeric: tabular-nums; cursor: help; }
/* COLLAPSE IS ITS OWN ROW, sized like a door because it is used like one.
   In the brand's corner it was 13px of glyph beside a wordmark: hard to
   hit, and it read as something about the logo. */
.ap-rail-hide { display: flex; align-items: center; gap: 8px; width: 100%; margin-top: 6px;
  background: none; border: 0; border-top: 1px solid var(--line); color: var(--dim);
  cursor: pointer; font: inherit; font-size: 9.5px; letter-spacing: 1.2px; font-weight: 600;
  padding: 10px 10px 10px 12px; border-radius: 0; }
.ap-rail-hide:hover { color: var(--fg); background: color-mix(in srgb, currentColor 5%, transparent); }
.ap-rail-hide:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.ap-rail-hide-ico { font-size: 13px; line-height: 1; font-weight: 400; }
/* The square mark only exists for the narrow rail. */
.ap-rail-brand .ap-mark-sq { display: none; }
/* ── THE BRANCH: one room, its sections under it ─────────────────────────
 * The editor is a two-level thing and used to be drawn as one item, so the
 * other two sections only existed once you had already landed on the
 * first. They hang under it now — SUBORDINATE, per the rule that a
 * secondary item should read as one: smaller, lighter, indented, and only
 * while the branch is open.
 */
.ap-rail-tree { position: relative; }
.ap-rail-caret { position: absolute; top: 4px; right: 4px; width: 22px; height: 22px;
  display: flex; align-items: center; justify-content: center; background: none; border: 0;
  color: var(--dim); font: inherit; font-size: 11px; line-height: 1; cursor: pointer;
  border-radius: 5px; }
.ap-rail-caret:hover { color: var(--fg); background: color-mix(in srgb, currentColor 8%, transparent); }
.ap-rail-subs { display: none; flex-direction: column; gap: 1px; margin: 1px 0 4px; }
/* Each branch answers to its OWN class — one for both meant opening the
   editor's sections opened the org room's too. */
body.ap-branch-open-editor [data-branch="editor"] .ap-rail-subs,
body.ap-branch-open-admin [data-branch="admin"] .ap-rail-subs { display: flex; }
/* Subordinate by every measure the eye reads: size, weight, colour, indent. */
#ap-rail a.ap-rail-sub { font-size: 10px; letter-spacing: .4px; color: var(--dim);
  padding: 5px 10px 5px 26px; border-left: 2px solid transparent; opacity: .82; }
#ap-rail a.ap-rail-sub:hover { opacity: 1; color: var(--fg); }
#ap-rail a.ap-rail-sub.on { color: var(--accent); opacity: 1;
  border-left-color: color-mix(in srgb, var(--accent) 55%, transparent); background: none; }

/* ── NARROW, DON'T HIDE ──────────────────────────────────────────────────
 * « used to remove the rail and leave a 15px edge to hunt for — which
 * gives the width back and takes the orientation with it. Narrowed, every
 * door is still on screen and still lit where you are; the label goes and
 * the title attribute carries the name.
 */
@media (min-width: 921px) {
  body.ap-rail-min.ap-railed { padding-left: 76px; }
  body.ap-rail-min #ap-rail { width: 52px; padding-left: 6px; padding-right: 6px; }
  body.ap-rail-min #ap-rail a { padding: 9px 0; text-align: center; }
  /* THE SELECTED ICON DOES NOT MOVE. Narrowed, the selected door still runs
     out to the rail's edge so the accent line can detour around it — but that
     extra 11px is width added on ONE side, and a centred icon inside it
     drifts half of that to the right. Standing on a floor should not nudge
     its own icon: the eye tracks the column, and a row that shifts when you
     arrive reads as the rail settling badly.
     The padding gives the 11px back to the content box, so the icon centres
     over exactly the width it had before it was chosen. */
  body.ap-rail-min #ap-rail a.on { padding-right: 11px; }
  /* A COLLAPSED RAIL HAS NO ROOM FOR A BRANCH, and this has to say so
     LOUDER than the rules that open one: those carry a body class and a
     [data-branch] attribute, which outranked a plain body.ap-rail-min — so
     an open branch stayed open at 52px and its sections hung off the side
     of the rail across the page. #ap-rail buys the id that settles it.
     Nothing is FORGOTTEN, only hidden: the open/shut class stays on the
     body, so widening the rail brings the branch back exactly as it was. */
  body.ap-rail-min #ap-rail .ap-rail-subs,
  body.ap-rail-min #ap-rail .ap-rail-caret { display: none; }
  body.ap-rail-min .ap-nav-txt,
  body.ap-rail-min .ap-rail-user,
  body.ap-rail-min .ap-rail-ver,
  /* NARROW: the words go, the control stays, and the two icons sit side by
     side. Both selectors carry #ap-rail so they beat the rule above —
     specificity, not source order, is what decides this one. */
  body.ap-rail-min #ap-rail .ap-rail-out-txt { display: none; }
  body.ap-rail-min #ap-rail .ap-rail-foot button.ap-rail-out { padding: 5px 0; width: 100%;
    justify-content: center; gap: 0; }
  body.ap-rail-min .ap-rail-foot-row { flex-direction: column; gap: 6px; width: 100%; }
  /* THE SAME SIZE NARROW AS WIDE. Collapsing the rail is meant to take the
     words away, not to change the drawing — an icon that grows when the rail
     narrows reads as a different icon, and the eye re-learns the row every
     time you collapse. Only the margin goes. */
  body.ap-rail-min .ap-nav-ico { margin-right: 0; }
  /* The wordmark steps aside for the square one; an org that hasn't drawn
     a square version shows none rather than a smudge. */
  body.ap-rail-min .ap-rail-brand { padding: 0 0 10px; text-align: center; font-size: 0; }
  body.ap-rail-min .ap-rail-brand > img:not(.ap-mark-sq) { display: none; }
  body.ap-rail-min .ap-rail-brand .ap-mark-sq { display: block;
    max-height: calc(34px * var(--ap-sq-scale, 1));
    max-width: calc(100% * var(--ap-sq-scale, 1)); width: auto; margin: 0 auto; }
  /* AND THE SQUARE MARK COMES IN A PAIR TOO. The rule above is specific
     enough to beat img.ap-mark-dark on its own, so both halves would show
     at once — the theme gate has to be restated at this weight. */
  body.ap-rail-min .ap-rail-brand .ap-mark-sq.ap-mark-dark { display: none; }
  html:not([data-theme="light"]) body.ap-rail-min .ap-rail-brand .ap-mark-sq.ap-mark-light { display: none; }
  html:not([data-theme="light"]) body.ap-rail-min .ap-rail-brand .ap-mark-sq.ap-mark-dark { display: block; }
  body.ap-rail-min .ap-rail-hide { justify-content: center; padding: 10px 0; gap: 0; }
  body.ap-rail-min .ap-rail-hide-txt { display: none; }
  body.ap-rail-min .ap-rail-foot { padding: 0; align-items: center; }
  body.ap-rail-min .ap-rail-foot-row { justify-content: center; }
}
/* On a laptop the icon and its words are one line, exactly as before —
   and the phone's short name is not in the room at all. */
/* THE DOORS' ICONS are drawn (menu.js AP_ICON_SVG), not typed: the Unicode
   glyphs they replaced came from four different blocks and were rendered by
   the font at four different weights and cap heights, which no choice of
   character could have evened out. Sized in ems so one rule moves the set. */
.ap-nav-ico { margin-right: 8px; font-size: 15px; display: inline-block; vertical-align: -.18em; }
.ap-nav-ico svg { width: 1em; height: 1em; display: block; }
.ap-nav-short { display: none; }

/* ── PHONES GET THE SAME NAV, NOT A DIFFERENT ONE ────────────────────────
 * The rail used to stand down under 920px and hand navigation to the ✦
 * wheel — a ring of cards at angles, labels rotated, the longest one
 * ("◈ ACCOUNT PLANS") truncated and the last card off the edge. Fun in a
 * personal tool; on a phone in front of a customer it is a puzzle where a
 * menu should be.
 *
 * So the rail STAYS — the same doors, the same order, the same gating, the
 * same lit-current-page — and only its GEOMETRY changes: the left rail lies
 * down as a bottom tab bar, icon over label, safe-area padded, the current
 * page wearing an accent edge the way it wears one on a laptop. Same nav,
 * two personalities. (The idiom is TabNav's, from the kit: a bottom bar is
 * where a thumb already is, which a top-left ☰ is emphatically not.)
 *
 * What the bar can't carry — the wordmark, and the foot's identity, theme
 * and sign-out — steps aside on a phone rather than shrinking to nothing.
 * The ✦ already deals THEME and SIGN OUT under ⚙ SETTINGS, so nothing is
 * lost; there is simply one door to each thing instead of two.
 */
@media (max-width: 920px) {
  /* A bottom bar cannot nest, so the editor's sections stay on the pill
     strip there — the same strip desktop keeps. */
  .ap-rail-tree { display: contents; }
  /* The open-branch rules win on specificity, so the phone has to name
     them to shut them — a bar has nowhere to nest. */
  .ap-rail-subs, .ap-rail-caret,
  body.ap-branch-open-editor [data-branch="editor"] .ap-rail-subs,
  body.ap-branch-open-admin [data-branch="admin"] .ap-rail-subs { display: none; }
  /* A PHONE'S GUTTER. The desktop rail rule no longer reaches here, so the
     page keeps its own padding — but 28px is a lot of a 390px screen, and
     the bar needs clearing at the bottom. */
  body.ap-railed { padding: 16px 14px calc(64px + env(safe-area-inset-bottom)); }
  #ap-rail { left: 0; right: 0; top: auto; bottom: 0; width: auto; flex-direction: row;
    gap: 0; padding: 0 0 env(safe-area-inset-bottom); border-right: 0;
    border-top: 1px solid var(--line); }
  /* A hidden rail is a desktop CHOICE about a rail standing beside the
     content. There is no such rail here, so the bar stays. */
  body.ap-rail-hid #ap-rail { display: flex; }
  /* Every tab an equal share of the width, icon over label, and the active
     one marked along its TOP edge — the bottom-bar reading of the accent
     border the rail wears down its left. */
  #ap-rail a { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 3px; text-align: center;
    padding: 8px 2px 9px; border-left: 0; border-top: 2px solid transparent;
    border-radius: 0; font-size: 8.5px; letter-spacing: .8px; }
  #ap-rail a.on { border-left-color: transparent; border-top-color: var(--accent); }
  .ap-nav-ico { font-size: 19px; line-height: 1; vertical-align: 0; }
  /* The SHORT name — its own element, so it can ellipsize like text rather
     than clip like generated content ("PROPOS" with no ellipsis was the
     tell). The rail says "ACCOUNT PLANS"; five tabs sharing 390px say
     "PLANS". */
  .ap-nav-txt { display: none; }
  .ap-nav-short { display: block; max-width: 100%; overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap; }
  .ap-nav-ico { margin-right: 0; }
  /* No room, and no need: the ✦ carries theme and sign-out already. */
  .ap-rail-brand, .ap-rail-sep, .ap-rail-foot, .ap-rail-hide { display: none; }
  .ap-mark-sq { display: none; }
  /* The ✦ steps up so the bar isn't underneath it. */
  .rvw-fab { bottom: calc(74px + env(safe-area-inset-bottom)) !important; }
  /* A PAGE TITLE at desktop weight eats a phone's first line. */
  body.ap-railed h1 { font-size: 14px; letter-spacing: 1.2px; }
  /* THE HEADER'S CROSS-PAGE DOORS STAND DOWN. ✎ EDITOR, ◫ FORECAST and
     their kind are navigation, and on a phone navigation already has two
     homes it can't lose — the bar along the bottom and the ✦. A third copy
     is what was crowding the title off its own line. Page ACTIONS (buttons)
     stay exactly where they are. */

  /* ✥ ARRANGE AND ◉ VIEW AS… HAVE MOVED INTO THE ✦. They are page
     controls rather than doors, so the bottom bar can't take them — but a
     phone's header has room for a title and little else, and the ✦ is
     already where a page's verbs live. menu.js deals them there; these are
     the copies standing down. */

  /* ROWS THAT WERE BUILT FOR A ROOM, IN A PHONE'S WIDTH. The admin rooms
     lay a label and its controls across one line with a 150px label
     column; under 400px that pushed the inputs past the card's edge (and
     the page sideways with them). The label takes its own line instead. */
  .fa-row { flex-wrap: wrap; }
  .fa-row > b { flex: 1 1 100%; min-width: 0; }
  .fa-row > i { flex: 1 1 100%; }
  .fa-row input, .fa-row select, .fa-row .fa-inp { min-width: 0; max-width: 100%; }
}

/* A FLOOR IN PAGER MODE has no arranging to do: the kit hides every grip,
   handle and reset pill there, so ✥ ARRANGE would toggle a mode with
   nothing in it. Pages each declared this for themselves and one of them
   forgot, which is exactly the kind of thing the shell should hold. */
/* THE HEADER HANDS THESE OVER, AT EVERY WIDTH. ✥ ARRANGE, the VIEW AS
   picker and the ✎ EDITOR link were drawn into every page's header AND
   dealt by the ✦ — two copies of one control, and the header's copy was
   the one that had to be re-styled per page. They live in the ✦ now, which
   follows you across every surface; the elements stay in the DOM because
   the ✦'s cards press the real controls rather than reimplementing them.
   Only where the shell is installed, so a page opened without it still
   works on its own. */
/* A DIRECT CHILD, and that combinator is load-bearing. As a descendant
   selector this also caught the ☁ chip, whose href is
   /sf/connect?back=forecast.html — it ENDS in .html, so an attribute match
   meant to strip the old page links hid the only way in to Salesforce, on
   every page whose filename is in that URL. It survived on the proposals
   sheet alone, because that page is served at / and its back= is %2F.
   Nothing legitimate is hurt by requiring a direct child: the links this
   is for sit straight in the header, and a control the shell does not know
   about is never one of them. */
body.ap-railed #viewas,
body.ap-railed > header > a[href$=".html"],
body.ap-railed > header > a[href="./"],
body.ap-railed > header > a[href="/"] { display: none !important; }
/* ONE HEADER BUTTON, ONE SIZE. Every page inline-styled its own ✥ and
   they drifted — 10px here, 11px there, 4px of padding against 5, two
   different corner radii. A control that is the same control on six pages
   should not be six declarations. .ap-hbtn is the shape; a page that needs
   a different COLOUR still says so, because the editor rooms are amber. */
.ap-hbtn { background: none; border: 1px solid var(--line); color: var(--dim);
  border-radius: 8px; font: inherit; font-size: 10.5px; letter-spacing: .5px;
  padding: 5px 12px; cursor: pointer; line-height: 1.5; }
.ap-hbtn:hover { color: var(--fg); border-color: var(--dim); }
/* The same button carrying one glyph instead of a word. */
/* A glyph is taller than 10.5px type, so the icon button matches the
   worded one by HEIGHT rather than by padding — side by side, two buttons
   that differ by four pixels look like a mistake. */
.ap-hbtn.ap-hico { display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 28px; padding: 0; font-size: 13px; letter-spacing: 0; }
#syncBtn.spin { animation: ap-spin 1.1s linear infinite; }
@keyframes ap-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { #syncBtn.spin { animation: none; opacity: .6; } }

/* ✥ ARRANGE STAYS IN THE TOP RIGHT, beside the floor it changes — it is a
   thing you reach for while looking straight at what you want to move, and
   two taps into a menu chamber is a long way to go for that. Only a phone
   hands it over, because a phone header has room for a title and nothing
   else; the ✦ deals it there instead (see apMenu). ⇪ SET AS DEFAULT goes
   with it, being the same gesture's second half. */
@media (max-width: 920px) {
  body.ap-railed #arrBtn, body.ap-railed #apPubBtn { display: none !important; }
}

/* SALESFORCE CHROME, ON A WORKSPACE THAT HAS NO SALESFORCE.
 *
 * Everything marked data-sf belongs to the Salesforce seam — the header
 * cloud, the ↻ that pulls its mirror, the seat editor's SALESFORCE USER row,
 * the CONNECTIONS card, the columns named after it. A workspace keeping its
 * own deals has none of that and never will, and a control for a system you
 * do not have is worse than clutter: it is a question with no answer.
 *
 * !important because several of these are shown by their own scripts with an
 * inline style — the seam is older than the choice. */
body.ap-sf-off [data-sf] { display: none !important; }

/* ── THE PAGE CUTS. IT DOES NOT CROSS-FADE. ────────────────────────────────
 *
 * A cross-fade paints BOTH pages at once for its whole duration, and two
 * pages differ most in exactly the place the eye is: the title. DASHBOARD
 * dissolving through ACCOUNT PLANS is not a fade, it is two words of
 * different lengths overlapping letter by letter — which is precisely the
 * jumble it looked like. There is no duration at which that reads well; the
 * problem is the overlap, not its length.
 *
 * So nothing cross-fades. The old page is dropped in one frame and the new
 * one is there. What remains of the transition is the part worth having:
 * the rail is PINNED — named on both sides, no animation — so it is not
 * repainted at all, and the group itself is pinned too so nothing morphs
 * between two layouts of different sizes (that morph is what slid ACCOUNT
 * PLANS in from the left).
 *
 * A cut sounds like less than a fade and is more: the frame holds still, the
 * content is simply there, and the one motion in the app — the cards rising,
 * below — is the only thing that moves. Motion that means something, once,
 * rather than motion everywhere meaning nothing.
 *
 * Browsers without the API get a plain navigation, which is also a cut.
 */
@view-transition { navigation: auto; }
#ap-rail { view-transition-name: ap-rail; }
::view-transition-group(ap-rail) { animation: none; }
::view-transition-old(ap-rail), ::view-transition-new(ap-rail) { animation: none; }
::view-transition-group(root) { animation: none; }
/* BOTH IMAGES STAY OPAQUE, and the new one is painted on top of the old by
   the transition's own stacking order — so it simply covers it. That is what
   makes this a cut: no blending, so no two titles overlapping, and no gap.
   The old image is NOT hidden. Dropping it to opacity 0 seems equivalent and
   is not: it retires the outgoing page before the incoming one has painted,
   and what fills the hole is the bare canvas — a full-screen white blink on
   every click, which is far worse than the fade it replaced. Hold, cover,
   done. */
::view-transition-old(root) { animation: none; opacity: 1; }
::view-transition-new(root) { animation: none; opacity: 1; }
/* AND THE NEW IMAGE HAS TO BE OPAQUE, or covering it achieves nothing.
 *
 * Every page sets its background on <body>, and a background on <body> with
 * none on <html> PROPAGATES to the canvas — it is painted behind the whole
 * document rather than inside the body's own box. Which means the root
 * snapshot, which captures the document and not the canvas, comes out
 * TRANSPARENT wherever there is no content. Two transparent snapshots
 * stacked are two pages showing through each other: the ghosted, doubled
 * text that looked like a jumble of letters. Holding the old image and
 * putting the new one on top could never have fixed that on its own.
 *
 * Giving <html> the background stops the propagation, so <body>'s own paint
 * lands inside the snapshot and the incoming page is a solid sheet. Same
 * colour, same tokens; the only difference is which box it is painted in. */
html { background: var(--bg); }

/* ── ONE ENTRANCE, EVERYWHERE ───────────────────────────────────────────────
 *
 * Navigation used to be whatever the browser decided. The rail was pinned and
 * the root cross-faded, but everything else — the title, the view strip, the
 * cards — rode inside the root snapshot, and a root group whose two sides
 * differ in size MORPHS: it animates position and dimensions between them, so
 * the whole page drifts in from wherever the last one ended. Floors whose
 * content happened to be the same width appeared to "just pop up"; ACCOUNT
 * PLANS, whose container is not width-capped like the others, slid in from
 * the left. Nobody chose either of those.
 *
 * So the frame is pinned and the contents are given one entrance:
 *
 *   THE FRAME NEVER MOVES — the rail is named on both sides with its
 *   animation off, and the page cuts rather than fading (above), so the nav
 *   and the title are simply still there. A title that slides is a title you
 *   re-read; a title that dissolves through another one is unreadable.
 *
 *   THE CONTENTS RISE ONCE — every card, 6px and 180ms, one ease. Small
 *   enough to read as the page settling rather than as an effect, and the
 *   same on every floor, which is the entire point: consistency IS the
 *   language. Opacity and transform only, so it never fights the grid, which
 *   owns top/left/width and animates them itself.
 *
 * §1 of the design language: motion explains where something came from, and
 * is never decoration. Reduced motion gets the arrival without the travel.
 */
/* The header carries no name of its own. It needed one while the root
   cross-faded — to be lifted out of the fade — and a cut leaves nothing for
   it to be lifted out of. One fewer named group is one fewer thing that can
   fall out of step between two pages. */

/* ── AND NOTHING RISES ─────────────────────────────────────────────────────
 *
 * The cards had an entrance — 6px, 180ms — and it was worse than none, for a
 * reason that is only visible frame by frame. ModuleGrid positions cards
 * ABSOLUTELY, and it can only do that after it has measured them: for the
 * first few frames of a floor every card sits at the same origin, stacked,
 * and then the pack moves them out. That has always happened. What the
 * entrance added was VISIBILITY — 180ms of fading in while stacked, so the
 * settling that used to flash past became a legible pile of overlapping
 * text, which is what "the whole screen resets" looked like.
 *
 * The honest fix is not a longer animation or a cleverer curve. It is that a
 * floor should not animate its arrival at all while its own layout is still
 * being computed. The page cuts, the cards are packed, and that is the whole
 * of it — which is also the crispest thing available.
 *
 * Motion is kept for what motion is FOR: the grid's own top/left/width
 * transition when a card is dragged or a floor re-packs, where it explains
 * that a thing moved and where it went. */

/* Nothing here animates on arrival any more, so reduced motion has nothing
   left to stand down: a cut is already the least motion there is. */

/* THE EDITOR's section strip — one room, its sections as pills. The RAIL
   carries these on a wide screen now, so the strip stands down there: two
   copies of one nav is one too many. It stays on a phone, where a bottom
   bar has nowhere to nest them. */
/* THE SECTION STRIP STANDS ON THE PAGE, rail or no rail. Nesting the
   sections under EDITOR in the rail is how you get TO one; the strip is how
   you move BETWEEN them once you are in the room, without steering back out
   to the rail for every hop. Desktop used to retire it as a duplicate — but
   a tab strip at the top of the thing it belongs to is not the same control
   as a branch in the nav, and the room lost its shape without it. */
#ap-edtabs { display: flex; gap: 6px; margin: 0 0 14px; }
#ap-edtabs a { font-size: 10px; letter-spacing: 1px; color: var(--dim); text-decoration: none;
  padding: 5px 12px; border: 1px solid var(--line); border-radius: 999px; }
#ap-edtabs a.on { color: var(--amber); border-color: var(--amber); }
#ap-edtabs a:hover { color: var(--fg); }

/* VIEW AS — an admin looking through somebody else's seat. It has to be
 * unmistakable from across the room: everything on the page is that
 * person's, and none of it can be touched. The strip along the top edge
 * says so even when the header has scrolled away. */
.ap-viewas-on { display: inline-flex; align-items: center; gap: 9px; font-size: 10px; letter-spacing: 1px;
  color: var(--amber); border: 1px solid var(--amber); border-radius: 999px; padding: 4px 6px 4px 12px;
  background: color-mix(in srgb, var(--amber) 12%, transparent); white-space: nowrap; }
.ap-viewas-on button { background: none; border: 1px solid var(--amber); color: var(--amber);
  border-radius: 999px; font: inherit; font-size: 9px; letter-spacing: 1px; padding: 3px 9px; cursor: pointer; }
.ap-viewas-on button:hover { background: var(--amber); color: var(--bg); }
body:has(.ap-viewas-on)::before { content: ''; position: fixed; left: 0; right: 0; top: 0; height: 3px;
  background: var(--amber); z-index: 90; pointer-events: none; }
