/* ============================================================================
   BLUE VISION AI — application stylesheet.

   Ported from Docs/ui-templates/nova-pro.html, the approved design direction
   (D-36). This is the ONLY stylesheet: every screen is written against the
   vocabulary below rather than against per-page classes, so a design change is
   made here once instead of in fifteen files.

   Two deliberate differences from the template:
     - The preview chrome (.pv*, the screen switcher and accent dots) is gone.
       It was review scaffolding and the template says so itself.
     - The default accent is named "ink" rather than "indigo", because that is
       the value the appearance.default-accent setting has always stored. The
       colours are unchanged.

   Everything after "BLUE VISION ADDITIONS" at the end is ours: the template
   covers home, workspace, compare, pricing and analytics, but has no auth or
   admin screens. Those are built from the primitives above, not from a second
   style.
   ============================================================================ */

/* ============================================================================
   NOVA PRO  (v4)
   Shell: WIDE LABELLED LEFT SIDEBAR, light chrome, card-based panels.
   (Aurora Pro deliberately uses a different shell — a narrow icon rail with a
   dark chrome panel — so the two are structurally distinct, not just recoloured.)

   v4 changes: left navigation on every screen · three independently collapsible
   side panels, all persisted · light theme by default.
   Chart palette validated with the dataviz validator (light + dark).
   Logical properties throughout => RTL-ready.
   ============================================================================ */

:root {
  /* ---- surfaces ---- */
  /* Warm paper, like Serene — it is what makes both feel expensive. Nova keeps
     a DISTINCT accent (deep ink, not terracotta) so the two directions never
     collapse into the same product at a glance. */
  /* THE WHOLE LADDER MOVES TOGETHER. Deepened from #f7f6f2 on the client's call
     ("too light, hard to see things") - and the first attempt moved --bg alone,
     which was wrong: --surface-3 was #f0eee8, so once the paper reached #f1efe8
     the hover tone and the page were 1.005:1 apart and every hover state in the
     light theme quietly stopped existing.

     The text was never the problem - #1c1b19 on paper is about 14.5:1 here. The
     STRUCTURE was, and structure is a ladder, not one value:

        white card on paper    1.08  ->  1.19
        border on paper        1.16  ->  1.26
        border inside a card   1.20  ->  1.50
        hover on a card        1.10  ->  1.22
        selected on a panel    1.10  ->  1.29

     Same warm paper (D-36) - the hue is unchanged, only the distance from white.
     Every surface in the product is drawn from these six values, so this is the
     entire change; there is no page that needs touching afterwards. */
  --bg:            #eeebe3;
  --surface:       #ffffff;
  --surface-2:     #f8f6f0;
  --surface-3:     #ece8dd;
  --surface-4:     #dfdacb;
  --border:        #d8d3c4;
  --border-strong: #c9c2b0;
  --text:          #1c1b19;
  --muted:         #5d5a54;
  --faint:         #6e6b63;

  /* ---- accent (swappable) ---- */
  --accent:        #34478c;
  --accent-hover:  #2a3a75;
  --accent-soft:   #edeef5;
  --accent-line:   #d2d6e6;
  --on-accent:     #ffffff;
  /* The accent as a filled surface with text on it. It is the accent itself unless a theme and accent together need
     a different one to keep the text readable — see ruby in the dark theme below. */
  --accent-fill:   var(--accent);
  --accent-fill-hover: var(--accent-hover);

  /* ---- status (fixed, never themed) ----
     TWO values for the two statuses that are printed as words, and the split is
     not tidying - it is the fix for the client's second complaint ("the yellow
     one needs more contrast"). --critical needs no pair: #d03b3b is 4.8:1 on a
     card, which already passes.

       --ok / --warning / --critical      the SHAPE colour: a meter fill, a dot, a
                                          border, a star. Sits on a large area, so
                                          it only has to be recognisable.
       --ok-fg / --warning-fg / …         the TEXT colour, on a light ground.

     They cannot be the same value in the light theme. #fab219 on white is
     1.83:1 - the sentence under the composer telling somebody to pick an answer
     was, measured, the least legible text in the product. The darkened amber is
     5.54:1 on a card and 4.65:1 on paper, so it passes AA on both grounds a
     warning is ever printed on. --ok had the same defect more quietly: 3.35:1,
     under AA for body text, on every "included"/"up" line in pricing and usage.

     Four files already worked around this by hard-coding #8a6100 beside a
     [data-theme="dark"] override; those are now one token. In the dark theme the
     fg IS the shape colour - a light-on-dark page wants the bright amber. */
  --ok:        #0ca30c;
  --ok-fg:     #0a7d0a;
  --ok-bg:     #eaf7ea;
  --warning:   #fab219;
  --warning-fg:#8a6100;
  --warning-bg:#fdf5e4;
  --critical:  #d03b3b;
  --critical-bg:#fdeeee;

  /* ---- found text ----
     Its own pair rather than a borrowed one. Reusing --warning-bg would have said
     "something is wrong with this sentence"; reusing --accent-soft would put find
     results in the same colour as the selected conversation, and then the sidebar
     and the thread would be making the same statement about different things.
     A search match is neither a status nor a selection, so it gets its own two
     tokens and nothing else in the product may use them. */
  --find:      #fbe38a;
  --find-fg:   #3a2f05;

  /* ---- only in this answer ----
     Its own pair too, and for the same reason as find's. It cannot be --find:
     the two would be the same statement about different things, and both can be
     painted on one screen. It cannot be a status colour: a passage only one
     model wrote is neither a warning nor a pass — the two readings are "it
     spotted something" and "it made it up", and the product must not pick one.
     And it cannot be --accent-soft, which is themed: this must look the same
     under all six accents, because it is a finding about the text and not a
     piece of our chrome. */
  --diff:      #ece5fb;
  --diff-fg:   #332755;

  /* ---- chart series — VALIDATED, do not eyeball-edit ----
     node scripts/validate_palette.js "#2a78d6,#c9552b,#1a8f6a,#b07d10,#a8497d" --mode light --surface "#ffffff"  → ALL PASS (incl. 3:1 contrast on every slot)
       --mode light --surface "#ffffff"   → all checks pass
     Slot 4 (yellow) is sub-3:1 on white: the relief rule applies, so every
     series always ships a visible text label + value beside its mark. */
  --s1: #2a78d6;  --s2: #c9552b;  --s3: #1a8f6a;  --s4: #b07d10;  --s5: #a8497d;
  --grid:     #edeae2;
  --tip-bg:   #1c1b19;
  --tip-fg:   #ffffff;

  /* ---- type ---- */
  --fs-micro: 10.5px;
  --fs-xs:    11.5px;
  --fs-sm:    12.5px;
  --fs-md:    13.5px;   /* base — change here if the client wants 13 or 12.5 */
  --fs-lg:    15px;
  --fs-xl:    17px;
  --fs-2xl:   20px;
  --fs-3xl:   25px;

  /* ---- space ---- */
  --sp-1: 4px; --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px; --sp-5: 20px;
  --sp-6: 28px; --sp-7: 40px; --sp-8: 56px; --sp-9: 80px;

  /* Icon box, in whole pixels. Icons sized in `em` inherit a fractional font size
     (--fs-md is 13.5px) and render their strokes on half-pixels, which reads as a
     soft, slightly uneven icon set rather than as a rounding error. */
  --ico: 18px;

  /* ---- motion ----
     Three durations, because there are three sizes of change and using one number
     for all of them is what makes an interface feel either sluggish or twitchy:

       dur-1  a state change on something already under the cursor. It must not be
              perceptible as a duration at all — only as "not a jump".
       dur-2  something arriving, or moving a short distance.
       dur-3  a panel. Big travel needs longer or it reads as a snap.

     Two easings, and the distinction matters: `ease` decelerates only, for things
     ARRIVING — they should look like they were already moving. `ease-io` has
     acceleration at both ends, for things moving BETWEEN two states, where a
     standing start is what you want.

     Everything below is gated by the prefers-reduced-motion rule further down,
     which switches every animation and transition in the product off at once. */
  --dur-1: 120ms;
  --dur-2: 180ms;
  --dur-3: 260ms;
  --ease:    cubic-bezier(.2,.8,.3,1);
  --ease-io: cubic-bezier(.4,0,.2,1);
  --r1: 6px; --r2: 8px; --r3: 11px; --r4: 14px; --rf: 999px;

  --font: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, Helvetica, Arial, sans-serif;

  /* THE ONE PLACE THIS PRODUCT USES A SERIF, and it is used for exactly one thing: a sentence
     somebody might say, set apart from the interface talking about itself.

     A SYSTEM STACK, NOT A WEBFONT. The mockup used Instrument Serif, loaded from Google's servers on
     a page that had no CSP; this product blocks outside origins, and adding a font file to the
     bundle for one line of text is a page-weight cost paid on every screen. Georgia and its Windows
     and Apple equivalents are already on the machine, and the italic is what carries the character. */
  --font-quote: Georgia, "Iowan Old Style", "Palatino Linotype", "Times New Roman", serif;
  --mono: ui-monospace, SFMono-Regular, "Cascadia Mono", Consolas, monospace;

  --sh-1: 0 1px 2px rgba(28,27,25,.05);
  --sh-2: 0 4px 12px -4px rgba(19,23,37,.10);
  --sh-3: 0 16px 40px -14px rgba(19,23,37,.20);

  /* ---- collapsible panel widths (driven by the data-* attributes) ---- */
  --nav-w:  228px;
  --list-w: 250px;
  --insp-w: 288px;

  /*
   * The workspace bar's height, published as a token.
   *
   * The App Hub floats BELOW it (see .hub), and needed a number to sit under. Measuring it in
   * script and writing it back would be a second source of truth that disagrees for one frame on
   * every load; pinning it with min-block-size below and naming it here is one truth in two places
   * that cannot drift, because the same token sets both.
   */
  --wsbar-h: 45px;

  /* There was a --sbw here: this browser's scrollbar width, measured in JS,
     because `scrollbar-gutter: stable` reserves that width inside the scroller
     and nowhere else, and every sibling of one had to pad its inline-end by
     `calc(<its own padding> + var(--sbw))` to end on the same line.

     It is gone, along with the probe in _Layout.cshtml that fed it, and the
     panels use `scrollbar-gutter: auto` instead — see the scrolling section
     further down. One client complaint arrived four times through that variable:
     first because the probe never ran, then because it ran and measured a bar of
     a width nothing was drawing, and finally because what it correctly reserved
     was empty space the client could see. If two edges have to agree, make them
     children of one box (D-229) — do not compute the difference. */

  /* ---- the parts of the window the stylesheet does not draw ----------------
     Scrollbars, the dropdown a native <select> opens, the calendar behind a
     date input, the X inside a search box, the caret, spellcheck underlines,
     the flash between page loads. None of those are ours to style; they are
     painted by the browser, and WITHOUT THIS DECLARATION it paints all of
     them light — which is why a dark app had bright chrome scrollbars sitting
     in its sidebar. One line, and every one of them follows the theme.

     Then the scrollbar itself: thin and in the surface colours, because a
     16px chrome bar down the middle of a two-panel layout reads as a seam
     between the panels rather than as a control.

     `scrollbar-color` inherits and does reach every scroller from here.
     `scrollbar-width` DOES NOT INHERIT — this declaration styles the page
     scroller and nothing else, so for a long time every panel in the product
     still had a full-width system bar. It is repeated on the scrollers
     themselves, next to `scrollbar-gutter`, where the two have to agree. */
  color-scheme: light;
  scrollbar-width: thin;

  /* --surface-4 WAS A SURFACE, and a thumb the colour of a surface is a thumb nobody can see
     (Syed, 24 September 2026: "Scroll bar colour not visible", reported in Rooms, which is its own
     page and so never got the desk's fix for the same fault). Measured against the page it sits on:
     --surface-4 gave 1.34:1 in light and 1.01:1 in dark, where a control needs 3:1 to be seen at
     all. --muted is ink rather than paper and carries the theme with it, so one declaration serves
     both; the value below resolves per theme like everything else here.

     This is the SITE's scrollbar. The desk sets its own on `.dk` from --dk-ink-3, because the desk
     has a different palette and a var() resolved here would arrive there already wrong. */
  scrollbar-color: var(--muted) transparent;
}

/* =================================================================================================
   A SHORT SCREEN GETS A SMALLER SCALE (BUG-0015)

   The client's words: "everything too big on a 14 inch laptop". The backlog is explicit that this
   is a type and spacing pass rather than a zoom hack, and it is also NOT a global shrink: the base
   is already 13.5px, and taking that down for everybody would fix a laptop by spoiling a monitor.

   WHAT THE COMPLAINT ACTUALLY IS is vertical room. A 14" laptop running 1920x1080 at the 150%
   Windows sets by default has a CSS viewport 720px tall; at 125% it is 864. A desktop 1080p screen
   at 100% is 1080 and is untouched by the rule below, which is the point — nobody asked for that
   screen to change.

   Only the SIZE tokens are redefined. Colours, radii and durations are the same design at any
   scale, and a media query that reached into them would be a second theme maintained by accident.

   The values are written out rather than computed with calc() for two reasons: they can be read at
   a glance, and each one is rounded to a whole or half pixel. A multiplier gives 10.5 * 0.87 =
   9.135px, and text laid out on thirds of a pixel is the "soft, slightly uneven" rendering the
   --ico note above this already warns about.
   ============================================================================================== */
@media (max-height: 900px) {
  :root {
    /* 87-89% of the desktop scale, which is the 80-90% he asked for. An earlier draft of this
       block took the base from 13.5 to 12.5 — 93%, which is not the range he named, and the check
       that passed it had its own upper bound set to 94%. Moving a threshold to fit the result is
       how a request gets quietly answered with something else. */
    --fs-micro: 9.5px;
    --fs-xs:    10px;
    --fs-sm:    11px;
    --fs-md:    12px;
    --fs-lg:    13px;
    --fs-xl:    15px;
    --fs-2xl:   17.5px;
    --fs-3xl:   22px;

    /* sp-1 stays at 4px. It is already the smallest gap that reads as a gap, and taking it to 3
       makes touching elements look like a rendering fault rather than a tighter layout. */
    --sp-1: 4px;  --sp-2: 7px;  --sp-3: 10px; --sp-4: 13px; --sp-5: 16px;
    --sp-6: 22px; --sp-7: 32px; --sp-8: 44px; --sp-9: 64px;

    /* Whole pixels, for the reason given where --ico is defined. */
    --ico: 16px;

    /* The rails come in with the rest. They are the widest fixed things on the screen, and leaving
       them at full width while everything inside them shrank would give back the space as padding. */
    --nav-w:  204px;
    --list-w: 224px;
    --insp-w: 262px;

    --wsbar-h: 40px;
  }
}

/* --- the four widths ---------------------------------------------------------
   There were ten: 560, 620, 700, 760, 780, 860, 900, 980, 1080, 1240. Each was
   chosen for one component in isolation, which is defensible per component and
   awful in aggregate — dragging the window reflowed the page ten times at ten
   unrelated widths, and nothing ever moved together. That is most of what reads
   as "unpolished" before anyone can name a specific fault.

   Four now. There are two families rather than one scale because the difference
   is real: a three-panel workspace runs out of room long before a settings form
   does, so forcing both onto the same number makes one of them wrong.

     SHELL — how many panels fit. About the application chrome.
       1240   the workspace drops its inspector; three panels become two
        900   the nav and the conversation list become drawers; compare panes
              stack; the top bar scrolls sideways instead of wrapping

     CONTENT — when a grid gives up a column. About the page inside the chrome.
       1024   every two-column content grid becomes one: admin master/detail,
              pricing, checkout, billing card, settings rows
        560   phone. Padding compresses, secondary labels drop

   The cascade is deliberate: content grids stack at 1024, BEFORE the nav becomes
   a drawer at 900. So a two-column page is never squeezed by a sidebar that is
   still taking 228px — the column gives up first, the sidebar second.

   These cannot be custom properties: a media query is evaluated before the
   cascade runs, so `var()` is not available inside one. The numbers are written
   out at each site; this comment is their definition. */

[data-nav="collapsed"]  { --nav-w: 58px; }
[data-list="collapsed"] { --list-w: 0px; }
[data-insp="closed"]    { --insp-w: 0px; }

[data-theme="dark"] {
  /* The other half of the line above. The theme is chosen by an attribute, not by the OS, so
     the browser has to be told which one it is now in — otherwise it keeps painting the light
     scrollbars, dropdowns and form chrome underneath a dark page. */
  color-scheme: dark;

  /* THE APPROVED DARK THEME (Syed, 17 September), and the reason this block was rewritten.
     The desk shipped the signed-off near-black that morning under `[data-theme="dark"] .dk`,
     and the product pages kept the older warm brown-grey — #232220 ground, #2e2d2a card,
     #f3f1ec ink. Opening Ask AI or Mail in a desktop window put the two side by side, and the
     window read as an older build of the same product. It was not: it was the same build with
     two dark themes in it.

     So these are now the desk's numbers, name for name. #08090B is the ground, #0A0B0D a rail
     or a list column, #0D0E11 a card or the top bar, #111216 a hover and #17181C the highest
     surface a control sits on. #1B1C21 is the hairline between two cards and #23252B the edge
     of something somebody types in. The ink is #F4F5F7, a second line #9A9DA6 and a hint
     #8A8D97. The ORDER is the one this file already had — bg darkest, then surface-2, surface,
     surface-3, surface-4 — because every rule below depends on it; only the values moved.

     THE LIGHT THEME IS NOT PART OF THIS and is untouched. */
  --bg:            #08090B;
  --surface:       #0D0E11;
  --surface-2:     #0A0B0D;
  --surface-3:     #111216;
  --surface-4:     #17181C;
  --border:        #1B1C21;
  --border-strong: #23252B;
  --text:          #F4F5F7;
  --muted:         #9A9DA6;
  --faint:         #8A8D97;

  --accent-soft:   #2b2f45;
  --accent-line:   #3d4260;

  --ok-bg:      #0f2410;
  --warning-bg: #2a2208;
  --critical-bg:#2c1414;

  /* THE STATUS TRIO IS THE DESK'S, for the same reason the greys above are: the desk paints a
     green tick in #3ECF8E and a product page opened beside it was painting one in #0ca30c, and
     two greens on one screen is exactly what "old build" looks like.

     --critical also had to move on its own account. #d03b3b is 4.02:1 on the new card and was
     2.87:1 on the old one, and `color: var(--critical)` appears eighty-two times in this file —
     it is body text, not decoration. #FF5F6D is 6.53:1 on a card and 5.84:1 on --critical-bg. */
  --ok:        #3ECF8E;
  --warning:   #FBBF24;
  --critical:  #FF5F6D;

  /* On a dark ground the darkened text variants disappear - #8a6100 on #2e2d2a
     is 1.9:1. Here the text colour and the shape colour are the same value, and
     that is why the split above is a token pair rather than a per-rule override:
     the theme decides, not thirty selectors. */
  --ok-fg:       var(--ok);
  --warning-fg:  var(--warning);

  /* Dark keeps the same idea and inverts the pair: a light-on-dark page cannot
     take a pale yellow band without the text under it going to mud. */
  --find:      #6b5410;
  --find-fg:   #fdf3cf;

  /* Inverted the same way, and it has to stay clearly the other colour from
     find: on a dark page two dim bands are one band. */
  --diff:      #332a52;
  --diff-fg:   #e2dbf7;

  /* dark series — separately validated against surface #0D0E11. Every one of the five was
     already lighter than the surface it is drawn on, so taking the surface DOWN to near-black
     only raises their contrast; the lowest of the five is --s2 at 5.2:1. They are unchanged. */
  --s1: #5b93e6;  --s2: #d1653e;  --s3: #28a882;  --s4: #b9871c;  --s5: #d16b9e;

  /* The gridline used to be --surface-3's twin. It cannot be any more: --surface-3 is #111216
     now, which on a #0D0E11 chart is not a line, it is nothing. This is the control-border
     grey, which is what a gridline should have been all along — the faintest mark the theme
     still calls a mark. */
  --grid:   #23252B;
  --tip-bg: #F4F5F7;
  --tip-fg: #0D0E11;

  /* Deepened to the desk's, because a shadow is what happens on top of the ground and the
     ground moved. They will not do much work on #08090B — nothing black does — which is why
     every elevated thing in this file also carries a --border or --border-strong hairline.
     That hairline, not the shadow, is what separates a menu from the page in dark. */
  --sh-1: 0 1px 2px rgba(0,0,0,.6);
  --sh-2: 0 4px 12px -4px rgba(0,0,0,.7);
  --sh-3: 0 16px 40px -14px rgba(0,0,0,.85);
}

/* accent presets — the token layer proves colour is cheap to change later.
   --a-<name> is declared once per theme and used BOTH by the preset below and by the
   swatch in the appearance picker, so a swatch can never show a colour the picker does
   not actually apply. That is the one bug a colour picker must not have.

   RUBY is the brand's own red (Syed, 17 September: the approved dark theme). It is the default
   accent for an account that has never chosen one, and it is also the colour the NETFIVE mark is
   drawn in on both themes — so `--a-ruby` is read directly by `.dk-brand-mark`, which must stay
   red whatever accent the person has picked. */
:root {
  --a-ink:  #34478c; --a-ocean: #0b6fbf; --a-pine:  #0c7f62;
  --a-clay: #b45a2b; --a-plum:  #8b3a86; --a-slate: #41506b;
  --a-ruby: #e11d33;
  --a-indigo: #4f46e5;

  /* THE LINK COLOUR, and deliberately NOT the accent (Syed, 21 September: "Lets us go with new UI design -
     indigo"). The NETFIVE-AI-Handoff keeps red for the logo, Ask and Send, and asks for indigo links. That
     reverses his 19 September request to turn blue and purple text red, for links only - so this comment is
     here to stop that being "fixed" back.
     A token of its own rather than a new accent value, because the accent is each person's choice from the
     appearance menu; overriding it would quietly take that choice away from everybody. This is an ink - text,
     borders, a tint behind an active tab - so it is the darker #3730A3 the handoff specifies (8.6:1 on white)
     rather than the #4F46E5 indigo used to FILL a button. */
  --link: #3730a3;
  --link-hover: #27237a;
  --link-soft: #eef2ff;
  --link-line: #c7d2fe;
  --link-ring: rgba(55, 48, 163, .15);
}
[data-theme="dark"] {
  --a-ink:  #8f9ce8; --a-ocean: #4aa5ef; --a-pine:  #33c39b;
  --a-clay: #e2865a; --a-plum:  #cd7ac6; --a-slate: #93a1bd;
  --a-ruby: #ff3b50;
  --a-indigo: #818cf8;

  --link: #a5b4fc;
  --link-hover: #c7d2fe;
  --link-soft: #1e1b4b;
  --link-line: #3730a3;
  --link-ring: rgba(165, 180, 252, .22);
}

/* RUBY IS THE ACCENT AGAIN (Syed, evening of 19 September: "as the logo is red, can you change the Blue / purple
   text to the same logo color red", for the whole desk; every account moved to it). White on #E11D33 is 4.75:1, and
   it reads 4.55:1 as a link on the rails; dark carries deep red on #FF3B50.
   INDIGO (that morning: "Accent - indigo, and only indigo") stays on offer. Light is his #4F46E5 with white on it,
   6.3:1. Dark takes his hover shade, #818CF8, with deep indigo on it, 5.5:1: his #6366F1 carries white at 4.47:1,
   just under AA, and reads at 3.7:1 as a link on a dark card. */

[data-accent="ink"]    { --accent:var(--a-ink);   --accent-hover:#2a3a75; --accent-soft:#edeef5; --accent-line:#d2d6e6; }
[data-accent="ocean"]  { --accent:var(--a-ocean); --accent-hover:#0a5c9e; --accent-soft:#e8f2fb; --accent-line:#c9e0f4; }
[data-accent="pine"]   { --accent:var(--a-pine);  --accent-hover:#0a6a51; --accent-soft:#e7f5f1; --accent-line:#c4e6dc; }
[data-accent="clay"]   { --accent:var(--a-clay);  --accent-hover:#984a22; --accent-soft:#faf0e9; --accent-line:#efd8c8; }
[data-accent="plum"]   { --accent:var(--a-plum);  --accent-hover:#742f70; --accent-soft:#f8ecf7; --accent-line:#ecd2ea; }
[data-accent="slate"]  { --accent:var(--a-slate); --accent-hover:#334059; --accent-soft:#eef0f4; --accent-line:#d7dce5; }
[data-accent="ruby"]   { --accent:var(--a-ruby);  --accent-hover:#b81628; --accent-soft:#fdecee; --accent-line:#f6c9cf; }
[data-accent="indigo"] { --accent:var(--a-indigo); --accent-hover:#4338ca; --accent-soft:#eef2ff; --accent-line:#c7d2fe; }
[data-theme="dark"][data-accent="ink"]    { --accent:var(--a-ink);   --accent-hover:#a8b3ef; --on-accent:#12162c; --accent-soft:#2b2f45; --accent-line:#2b3163; }
[data-theme="dark"][data-accent="ocean"]  { --accent:var(--a-ocean); --accent-hover:#77bcf4; --on-accent:#04162a; --accent-soft:#12233a; --accent-line:#1d3a5c; }
[data-theme="dark"][data-accent="pine"]   { --accent:var(--a-pine);  --accent-hover:#63d5b5; --on-accent:#04231b; --accent-soft:#0e2a23; --accent-line:#17453a; }
[data-theme="dark"][data-accent="clay"]   { --accent:var(--a-clay);  --accent-hover:#eaa483; --on-accent:#2a1207; --accent-soft:#2c1a11; --accent-line:#4a2b1a; }
[data-theme="dark"][data-accent="plum"]   { --accent:var(--a-plum);  --accent-hover:#dc9dd7; --on-accent:#260a24; --accent-soft:#2a132a; --accent-line:#452041; }
[data-theme="dark"][data-accent="slate"]  { --accent:var(--a-slate); --accent-hover:#b0bbd1; --on-accent:#10141f; --accent-soft:#1e2434; --accent-line:#333c52; }
/* Ruby in the dark theme, the only accent whose filled buttons carry white (Syed, 19 September 2026: "can you change
   colour of text in red box to white.. black is not looking good"). The bright #ff3b50 stays for text, links and
   marks on the dark page, where it is 5.2:1; a button filled with it and lettered in white would be 3.5:1, so a
   filled button takes the logo's own red, the one the light theme uses, and reads 4.75:1 in white. */
[data-theme="dark"][data-accent="ruby"]   { --accent:var(--a-ruby);  --accent-hover:#ff6c7d; --on-accent:#ffffff; --accent-fill:#e11d33; --accent-fill-hover:#b81628; --accent-soft:#2a1116; --accent-line:#4a1d26; }
[data-theme="dark"][data-accent="indigo"] { --accent:var(--a-indigo); --accent-hover:#a5b4fc; --on-accent:#1e1b4b; --accent-soft:#2e2a4a; --accent-line:#3f3a63; }

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

/* `hidden` MEANS HIDDEN, and it needs !important to mean it.

   The browser hides `[hidden]` from its own stylesheet, and ANY author rule that sets `display`
   beats a browser stylesheet outright — specificity never even enters into it. So the moment an
   element carries a class with a `display` in it, `hidden` silently stops working, and the JavaScript
   that sets `el.hidden = true` looks broken while doing exactly what it was told.

   This has now bitten three separate features: the appearance menu, the notification bell, and the
   Design / Edit-as-HTML switch on the email template screen — where BOTH panes stayed on screen and
   the tab buttons appeared to do nothing at all. The first two were patched one class at a time
   (`.appearance-menu[hidden]`, `.bell-menu[hidden]`), which is the shape of a missing rule rather
   than a missing patch. This is the rule. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--fs-md);
  line-height: 1.58;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;

  /* The app fills the viewport EXACTLY and never scrolls as a whole.
     Scrolling belongs to the panes inside it. Measured by the browser, not
     by a hard-coded number — that assumption is what caused two scrollbars. */
  display: flex;
  flex-direction: column;
  block-size: 100vh;
  block-size: 100dvh;   /* dvh accounts for mobile browser chrome */
  overflow: hidden;
}

a { color: inherit; text-decoration: none; }
button, input, textarea, select { font: inherit; color: inherit; }
button { background: none; border: none; cursor: pointer; padding: 0; }
h1, h2, h3, h4 { margin: 0; line-height: 1.25; letter-spacing: -.016em; font-weight: 600; }
p { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }
table { border-collapse: collapse; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r1); }
.sr-only { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); }


/* ---------------------------------------------------------------------------
   PRIMITIVES
   --------------------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 7px 13px; border-radius: var(--r2);
  font-size: var(--fs-sm); font-weight: 550; white-space: nowrap;
  border: 1px solid transparent;
  transition: background var(--dur-1), border-color var(--dur-1),
              color var(--dur-1), transform var(--dur-1) var(--ease-io);
}

/* Press feedback. A colour change on :active is invisible under a fingertip and
   nearly invisible under a cursor, because the pointer is on top of the thing
   that changed. Scale is visible from the edges of the control, which are the
   only part not covered.
   .97 rather than .95: this fires on every click in the product, and a press
   anyone actually notices is a press that feels mushy by the twentieth. */
.btn:active, .icon-btn:active { transform: scale(.97); }
.icon-btn { transition: background var(--dur-1), color var(--dur-1), transform var(--dur-1) var(--ease-io); }
.btn-primary { background: var(--accent-fill); color: var(--on-accent); }
.btn-primary:hover { background: var(--accent-fill-hover); }
.btn-default { background: var(--surface); border-color: var(--border-strong); }
.btn-default:hover { background: var(--surface-3); }
.btn-subtle { background: var(--surface-3); }
.btn-subtle:hover { background: var(--surface-4); }
.btn-quiet { color: var(--muted); }
.btn-quiet:hover { color: var(--text); background: var(--surface-3); }
.btn-lg { padding: 10px 18px; font-size: var(--fs-md); }
.btn-xs { padding: 4px 9px; font-size: var(--fs-xs); }
.car { color: var(--faint); font-size: 11px; margin-inline-start: 2px; }

.icon-btn {
  inline-size: 28px; block-size: 28px; border-radius: var(--r1);
  display: grid; place-items: center; color: var(--muted); font-size: var(--fs-md);
  transition: background var(--dur-1), color var(--dur-1); flex-shrink: 0;
}
.icon-btn:hover { background: var(--surface-3); color: var(--text); }
.icon-btn.sm { inline-size: 22px; block-size: 22px; font-size: var(--fs-xs); }

/* Icon-ONLY boxes: the glyph is the whole control, so it is sized in whole pixels
   and centred as a box. `.gi` defaults to 1em with a baseline nudge, which is
   right when an icon sits inline beside a label and wrong when it is alone in a
   square — there the inherited font size is arbitrary and the baseline is noise.
   `.stepper button` had no centring at all and was relying on the line box. */
.stepper button { display: grid; place-items: center; }
.icon-btn > .gi, .stepper button > .gi {
  inline-size: var(--ico); block-size: var(--ico); vertical-align: initial;
}
.icon-btn.sm > .gi { inline-size: 14px; block-size: 14px; }

.tag {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 8px; border-radius: var(--r1);
  background: var(--surface-3); font-size: var(--fs-xs); font-weight: 550; color: var(--muted);
  white-space: nowrap;
}
.tag-accent   { background: var(--accent-soft); color: var(--accent); }
.tag-ok       { background: var(--ok-bg); color: var(--ok-fg); }
.tag-warning  { background: var(--warning-bg); color: var(--warning-fg); }
.tag-outline  { background: none; border: 1px solid var(--border-strong); }
/* The state a row is in when somebody has deliberately switched the account off. Distinct from
   tag-warning, which is a condition that clears itself. */
.tag-critical { background: var(--critical-bg); color: var(--critical); }

.lbl { font-size: var(--fs-micro); font-weight: 650; letter-spacing: .08em; text-transform: uppercase; color: var(--faint); }
.mono { font-family: var(--mono); font-size: var(--fs-xs); }
.num  { font-variant-numeric: tabular-nums; }
.muted { color: var(--muted); }
.rule { block-size: 1px; background: var(--border); border: 0; margin-block: var(--sp-4); }
.spacer { margin-inline-start: auto; }

.field {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 10px; border: 1px solid var(--border-strong); border-radius: var(--r2);
  background: var(--surface); font-size: var(--fs-sm); color: var(--muted);
  transition: border-color var(--dur-1);
}
.field:hover { border-color: var(--faint); }
.field input { border: 0; background: none; inline-size: 100%; font-size: var(--fs-sm); min-inline-size: 0; }
.field input:focus { outline: none; }
.field input::placeholder { color: var(--faint); }

.select {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 9px;
  border: 1px solid var(--border-strong); border-radius: var(--r2);
  background: var(--surface); font-size: var(--fs-sm); white-space: nowrap;
}
.select:hover { border-color: var(--faint); }

.seg { display: inline-flex; background: var(--surface-3); padding: 2px; border-radius: var(--r2); }
/* Links as well as buttons: a time range that changes the page is a navigation, and making
   it an anchor is what gives it a URL you can bookmark, share and open in a new tab. */
.seg button, .seg a { padding: 4px 11px; border-radius: var(--r1); font-size: var(--fs-sm); color: var(--muted); font-weight: 500; transition: all var(--dur-1); }
.seg a { text-decoration: none; }
.seg button[aria-pressed="true"], .seg a[aria-pressed="true"] { background: var(--surface); color: var(--text); font-weight: 600; box-shadow: var(--sh-1); }

.switch { inline-size: 30px; block-size: 17px; border-radius: var(--rf); background: var(--border-strong); position: relative; transition: background var(--dur-1); flex-shrink: 0; }
.switch::after { content: ""; position: absolute; inset-block-start: 2px; inset-inline-start: 2px; inline-size: 13px; block-size: 13px; border-radius: var(--rf); background: #fff; transition: transform var(--dur-1); }
.switch[aria-checked="true"] { background: var(--accent); }
.switch[aria-checked="true"]::after { transform: translateX(13px); }
[dir="rtl"] .switch[aria-checked="true"]::after { transform: translateX(-13px); }

.stepper { display: inline-flex; align-items: center; border: 1px solid var(--border-strong); border-radius: var(--r2); overflow: hidden; }
.stepper button { inline-size: 26px; block-size: 26px; color: var(--muted); }
.stepper button:hover { background: var(--surface-3); color: var(--text); }
.stepper span { min-inline-size: 30px; text-align: center; font-size: var(--fs-sm); font-weight: 600; font-variant-numeric: tabular-nums; }

.toggle-ctl { display: inline-flex; align-items: center; gap: 7px; font-size: var(--fs-sm); color: var(--muted); }

/* ===========================================================================
   APP SHELL — left navigation on every screen
   =========================================================================== */
/* flex:1 + min-block-size:0 — the shell takes whatever height is left after the
   preview bar, whatever that turns out to be. No magic number to get wrong. */
/* `grid-template-rows: minmax(0, 1fr)` is what makes the sidebar scroll at all,
   and it is not a tidy-up.

   A grid ROW is auto-sized by default, and an auto row is sized to the tallest
   item's max-content height. `overflow: hidden` on .appnav sets its automatic
   minimum size to zero, but it does NOT reduce its max-content contribution —
   so a nav with twelve items and a footer reported ~640px, the row grew to
   ~640px, and the whole grid overflowed a body that is `100dvh; overflow:
   hidden`. The bottom of the nav was clipped by the body, and .nav-scroll never
   overflowed its own box, so there was nothing to scroll: the content was
   simply gone. On a short window that swallowed the account row entirely.

   `minmax(0, 1fr)` gives the row a ZERO minimum and a share of the container's
   definite height, so the row can never exceed the viewport. The nav then does
   overflow its own box, and .nav-scroll's `overflow-y: auto` finally has
   something to do. min-block-size on the two children is the same argument one
   level down. */
.app {
  flex: 1 1 auto; min-block-size: 0; position: relative;
  display: grid; grid-template-columns: var(--nav-w) 1fr;
  grid-template-rows: minmax(0, 1fr);
}

/* Scrim behind an overlay panel on small screens — tap anywhere to close. */
.scrim { position: absolute; inset: 0; z-index: 110; background: rgba(8,10,20,.42); display: none; }
.scrim.on { display: block; }

.appnav {
  background: var(--surface-2); border-inline-end: 1px solid var(--border);
  display: flex; flex-direction: column; overflow: hidden; min-block-size: 0;
}

/* Collapsing the nav moved 170px of layout in a single frame. The eye cannot
   follow a jump, so the rail did not read as the sidebar having narrowed — it
   read as the page having been replaced. Animating the TRACK rather than the
   panel is what makes it legible: the content edge and the sidebar edge stay
   joined for the whole 260ms, which is the thing that says these are one
   layout and not two states.
   `grid-template-columns` is animatable wherever the values are both lengths,
   which they are — `--nav-w` is 228px or 58px. Browsers that decline still get
   the jump they had before, so this is additive.
   Not at ≤900px: there the nav is an overlay drawer and the track is always
   1fr, so animating it would fight the drawer's own transform transition. */
@media (min-width: 901px) {
  .app { transition: grid-template-columns var(--dur-3) var(--ease-io); }
}

/* --- arriving on a page ------------------------------------------------------
   This is a multi-page application: every navigation is a real document load,
   and the browser paints the new background before it has content to put on it.
   That flash is the single biggest reason a server-rendered app feels older
   than a single-page one, and it is not a rendering problem — it is the absence
   of any signal that the arrival was intentional.

   Short and small deliberately. 8px and 180ms is under the threshold at which
   anyone can describe what happened, which is the point: it should register as
   the page having settled, never as an animation worth waiting through. Longer
   or further and every navigation in the product acquires a tax. */
.pad, .home { animation: page-in var(--dur-2) var(--ease) both; }
@keyframes page-in { from { opacity: 0; transform: translateY(8px); } }
.nav-brand { display: flex; align-items: center; gap: 9px; padding: var(--sp-3); block-size: 49px; flex-shrink: 0; }
/* The mark and the word are one link home (Syed, 22 September 2026). The collapse button beside them
   is NOT inside it, so this takes the row's width rather than the whole block, and inherits the
   rail's colour rather than the link blue every other anchor gets. */
.nav-home {
  display: flex; align-items: center; gap: 9px; min-inline-size: 0;
  color: inherit; text-decoration: none; border-radius: var(--r2);
}
.nav-home:hover { color: inherit; text-decoration: none; }
.nav-home:focus-visible { outline: 2px solid var(--link); outline-offset: 2px; }
/* The mark is an <img> of Brand.Tile, not a coloured square with "BV" set into it — so this rule
   is a box and nothing else. It does NOT take the accent: every other colour in the product
   follows the accent the user picked (D-51), and a brand that turns clay-orange because somebody
   liked orange is not a brand. The SVG carries its own tile and its own corner radius. */
.nav-mark {
  inline-size: 26px; block-size: 26px; flex-shrink: 0; display: block;
}
.nav-word { font-weight: 650; font-size: var(--fs-md); letter-spacing: -.02em; white-space: nowrap; }

/* WHOSE DASHBOARD IT IS (BUG-0014). A second line inside .nav-word, so the rail's collapse rule
   takes both away together — a name left beside a 26px logo would be the one piece of text in a
   rail of icons. Truncated rather than wrapped: a long name must not make the brand two rows tall. */
.nav-whose {
  display: block;
  font-size: var(--fs-micro); font-weight: 500; letter-spacing: 0;
  color: var(--faint);
  max-inline-size: 148px; overflow: hidden; text-overflow: ellipsis;
}
.nav-collapse { margin-inline-start: auto; }

.nav-cta { padding: 0 var(--sp-3) var(--sp-3); }
.nav-cta .btn { inline-size: 100%; }

/* The wrapper exists so the "more below" buttons have something to be absolute
   inside. They cannot be absolute inside .nav-scroll itself: an absolutely
   positioned child of a scroll container is positioned against the scrolled
   content and travels with it, so a button pinned to the bottom would slide
   out of view at exactly the moment it is needed. */
.nav-scrollwrap { position: relative; flex: 1; min-block-size: 0; display: flex; flex-direction: column; }
.nav-scroll { flex: 1; min-block-size: 0; overflow-y: auto; overflow-x: hidden; padding: var(--sp-2) var(--sp-2) var(--sp-4); }
.nav-sect { padding: var(--sp-4) var(--sp-3) var(--sp-1); }
.nav-item {
  display: flex; align-items: center; gap: 10px; inline-size: 100%;
  padding: 7px 9px; border-radius: var(--r2); margin-block-end: 1px;
  font-size: var(--fs-sm); color: var(--muted); position: relative;
  transition: background var(--dur-1), color var(--dur-1);
}
.nav-item:hover { background: var(--surface-3); color: var(--text); }
.nav-item[aria-current="page"] { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
/* A grid with `place-items: center`, not a text box with `text-align: center`.
   The icon is an inline-block <svg> carrying `vertical-align: -.13em`, so inside a
   text box its vertical position depends on the line box — which depends on the
   inherited line-height and font size, and therefore differs between the rail, the
   expanded nav and the footer. Grid centring has no baseline in it at all.

   The size is whole pixels rather than 1em. At `--fs-md` the icons were rendering
   13.5px, so every 1.8px stroke landed on a half-pixel and the whole set read
   slightly soft and slightly uneven — the icons looked like different weights when
   they are in fact the same weight badly sampled. */
.nav-ico {
  inline-size: var(--ico); block-size: var(--ico); flex-shrink: 0;
  display: grid; place-items: center;
}
.nav-ico .gi { inline-size: var(--ico); block-size: var(--ico); vertical-align: initial; }
.nav-txt { flex: 1; text-align: start; white-space: nowrap; overflow: hidden; }
.nav-badge { font-size: var(--fs-micro); font-weight: 650; padding: 1px 6px; border-radius: var(--rf); background: var(--surface-4); color: var(--muted); }
.nav-item[aria-current="page"] .nav-badge { background: var(--accent-fill); color: var(--on-accent); }

.nav-foot { padding: var(--sp-3); border-block-start: 1px solid var(--border); flex-shrink: 0; }
.credit-line { display: flex; justify-content: space-between; align-items: baseline; font-size: var(--fs-xs); margin-block-end: 6px; }
.credit-line b { font-size: var(--fs-sm); font-weight: 650; font-variant-numeric: tabular-nums; }
/* THE `display` IS THE WHOLE RULE, and its absence made this invisible for its entire life.

   `.meter` is a <span>, so without a display it is INLINE — and block-size does not apply to an
   inline box. Measured on the Usage screen: `meter 0x0 display:inline`, with the inner <i> correctly
   sized to `92x0`, `31x0`, `29x0`. Every percentage was computed right, the markup was right, and
   the bar was zero pixels tall on both screens that use it — Account/Usage and Admin/Analytics.

   So the customer read a column headed "Share" with nothing in it.

   It survived because a missing 4px bar and a present one look identical on a dark background at a
   glance; it was found by measuring the element, not by looking at the screen. The inline-size on
   the <i> also needs a parent with a resolved width, which an inline box does not give it. */
.meter { display: block; inline-size: 100%; block-size: 4px; background: var(--surface-4); border-radius: var(--rf); overflow: hidden; }
.meter > i { display: block; block-size: 100%; background: var(--accent); border-radius: var(--rf); }
.nav-user { display: flex; align-items: center; gap: var(--sp-2); margin-block-start: var(--sp-3); padding-block-start: var(--sp-3); border-block-start: 1px solid var(--border); }
/* The account circle. It carries a 1px ACCENT ring, not a --border one: on
   accent-soft ground a neutral hairline is 1.05:1 and the client's report was
   exactly that — "profile button should have visible borders, maybe theme
   colour would be best". The ring is the one place the accent is allowed to
   draw an outline this fine, because the shape it outlines is a circle with two
   letters in it and has no other edge.

   A <button>, not a <span>. It opens the card below on hover, and a thing that
   reveals a menu has to be reachable by keyboard as well as by pointer — the
   card opens on :focus-within for exactly that reason. */
.avatar {
  inline-size: 26px; block-size: 26px; border-radius: var(--rf); flex-shrink: 0;
  background: var(--accent-soft); color: var(--accent);
  border: 1px solid var(--accent);
  display: grid; place-items: center; font-size: var(--fs-xs); font-weight: 700;
  padding: 0; line-height: 1; cursor: pointer;
  transition: box-shadow var(--dur-1), background var(--dur-1);
}
.acct:hover > .avatar,
.acct:focus-within > .avatar { background: var(--accent-fill); color: var(--on-accent); }
.avatar:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-line); }

/* --- the account card -------------------------------------------------------
   Gmail's pattern, and it is the right one here: the rail shows two letters, and
   two letters do not tell somebody which of their accounts they are signed in
   to. Hovering the circle is the cheapest possible way to ask.

   POSITION: FIXED, and that is load-bearing. .appnav is `overflow: hidden`, so
   anything absolutely positioned in .nav-foot is sliced at the rail's edge — the
   same wall the rail's tooltips hit, and the reason they had to be reparented to
   <body> by script. Fixed positioning is not clipped by an ancestor's overflow,
   and .nav-foot (unlike .nav-scroll) carries no mask or transform, so nothing
   between this and the viewport establishes a containing block. That buys the
   escape with no JavaScript at all — which matters, because a hover card driven
   by script is a listener on every page in the product.

   It follows the rail on its own: --nav-w is 228px or 58px and is declared on
   <html>, so the card sits beside whichever the rail currently is, and the
   collapsed state needs no separate rule.

   NOT `hidden` + `display: none`: visibility is what keeps the links inside out
   of the tab order while closed AND still allows a transition. */
.acct { position: relative; display: flex; }
.acct-card {
  position: fixed; z-index: 180;
  inset-inline-start: calc(var(--nav-w) + 10px);
  inset-block-end: var(--sp-3);
  inline-size: 268px; max-inline-size: calc(100vw - var(--nav-w) - 20px);
  padding: var(--sp-4);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r3); box-shadow: var(--sh-3);
  visibility: hidden; opacity: 0; transform: translateY(4px);
  transition: opacity var(--dur-1), transform var(--dur-1), visibility var(--dur-1);
}
/* The bridge. Without it the 10px of paper between the circle and the card is a
   dead zone, and the card closes while the pointer is on its way to the links
   inside it. */
.acct-card::before {
  content: ""; position: absolute; inset-block: 0;
  inset-inline-start: -12px; inline-size: 12px;
}
.acct:hover > .acct-card,
.acct:focus-within > .acct-card { visibility: visible; opacity: 1; transform: none; }

.acct-top { display: flex; align-items: center; gap: var(--sp-3); }
.acct-top .avatar {
  inline-size: 40px; block-size: 40px; font-size: var(--fs-lg);
  cursor: default; pointer-events: none;
}
.acct-id { min-inline-size: 0; }
/* The address wraps. An email is the one string here that can be longer than the
   card, and truncating the middle of it defeats the entire point of the card. */
.acct-id b { display: block; font-size: var(--fs-md); font-weight: 650; }
.acct-id span {
  display: block; font-size: var(--fs-xs); color: var(--muted);
  word-break: break-all; line-height: 1.4;
}
.acct-rows {
  margin: var(--sp-3) 0 0; padding-block-start: var(--sp-3);
  border-block-start: 1px solid var(--border);
  display: grid; grid-template-columns: auto 1fr; gap: 6px var(--sp-3);
  font-size: var(--fs-xs);
}
.acct-rows dt { color: var(--muted); }
.acct-rows dd { margin: 0; text-align: end; font-weight: 600; font-variant-numeric: tabular-nums; }
.acct-acts {
  display: flex; flex-wrap: wrap; gap: var(--sp-2);
  margin-block-start: var(--sp-3); padding-block-start: var(--sp-3);
  border-block-start: 1px solid var(--border);
}
.acct-acts a, .acct-acts button {
  font-size: var(--fs-xs); padding: 5px 10px; border-radius: var(--r2);
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  cursor: pointer;
}
.acct-acts a:hover, .acct-acts button:hover { background: var(--surface-3); border-color: var(--border-strong); }
.acct-acts form { display: contents; }

/* Below 901px the nav is an overlay drawer that CAN carry a transform, which
   would make it the containing block for the fixed card and put it straight back
   inside the clip. It is also a touch screen, where there is no hover to open it
   with. Both reasons point the same way. */
@media (max-width: 900px) {
  .acct-card { display: none; }
}
/* The signed-out foot. A column, not the signed-in row: there is no avatar and
   no name to sit beside, so the two things it does have get the full width. */
.nav-anon { flex-direction: column; align-items: stretch; gap: var(--sp-2); }
.nav-anon-why { font-size: var(--fs-xs); line-height: 1.45; color: var(--muted); }
.nav-signin {
  inline-size: 100%;
  background: var(--accent-soft); color: var(--accent);
  border: 1px solid var(--accent-line, var(--border));
  font-weight: 600;
}
.nav-signin:hover { background: var(--accent-fill); color: var(--on-accent); border-color: var(--accent); }
[data-nav="collapsed"] .nav-anon-why { display: none; }

.nav-user-meta { flex: 1; min-inline-size: 0; white-space: nowrap; overflow: hidden; }
.nav-user-meta div:first-child { font-weight: 600; font-size: var(--fs-sm); }

/* --- collapsed state: icon rail with hover tooltips --- */
[data-nav="collapsed"] .nav-word,
[data-nav="collapsed"] .nav-txt,
[data-nav="collapsed"] .nav-badge,
[data-nav="collapsed"] .nav-sect,
[data-nav="collapsed"] .nav-cta .btn .btn-lbl,
[data-nav="collapsed"] .nav-user .btn-lbl,
[data-nav="collapsed"] .credit-line,
[data-nav="collapsed"] .meter,
[data-nav="collapsed"] .nav-user-meta { display: none; }
[data-nav="collapsed"] .nav-brand { padding-inline: 0; justify-content: center; }
[data-nav="collapsed"] .nav-collapse { display: none; }
/* In the rail the CTA is a square icon button, not a stretched pill. Hiding
   only .lbl keeps the pencil — hiding every span left an empty green capsule.

   ONE size for every tile in the rail. The CTA used to be 38x38 while the items
   below it stretched to whatever the rail's inner width happened to be, so the
   button that starts a conversation read as a stray tile sitting above the list
   rather than the first thing in it. Both are 38x38 now and share a centre line.

   `display: flex` on the CTA is load-bearing, not tidying. `.btn` is inline-flex,
   and `margin-inline: auto` does nothing to an inline-level box — so the New chat
   tile sat hard against the rail's leading edge while every .nav-item below it
   (already block-level flex) centred correctly. The two were a couple of pixels
   apart, which reads as a wonky button rather than as a missing display value. */
[data-nav="collapsed"] .nav-cta { padding-inline: 0; }
[data-nav="collapsed"] .nav-scroll { padding-inline: 0; }
[data-nav="collapsed"] .nav-cta .btn,
[data-nav="collapsed"] .nav-item {
  display: flex;
  inline-size: 38px; block-size: 38px; padding: 0; margin-inline: auto;
  justify-content: center; border-radius: var(--r2); gap: 0; position: relative;
}
[data-nav="collapsed"] .nav-item { margin-block-end: 2px; }

/* The account row STACKS in the rail rather than centring as a pair.
   `justify-content: center` centred the avatar and the sign-out button
   together, which put the centre line between them and neither of them on it —
   so the two controls at the bottom of the rail were the only things in it not
   sharing the centre every tile above them uses. Two 58px-wide rows, each with
   its own centre, is the only arrangement that agrees with the icons above.
   `align-items: center` rather than `margin-inline: auto`: the sign-out button
   is wrapped in a <form>, and centring the form is what centres the button. */
[data-nav="collapsed"] .nav-foot { padding-inline: 0; }
[data-nav="collapsed"] .nav-user { flex-direction: column; align-items: center; gap: var(--sp-2); }
[data-nav="collapsed"] .nav-user .icon-btn { inline-size: 28px; block-size: 28px; }
[data-nav="collapsed"] .nav-user .icon-btn > .gi { inline-size: var(--ico); block-size: var(--ico); }
/* Sign in becomes a tile like every other control in the rail. Left as a
   full-width pill it printed "Sign in" into 58px and the word ran out of it. */
[data-nav="collapsed"] .nav-user .btn {
  inline-size: 38px; block-size: 38px; padding: 0;
  display: flex; justify-content: center; gap: 0;
}

/* --- the rail's labels ------------------------------------------------------
   These used to be a ::after on the tile, positioned at `100% + 8px`. Measured:
   the tooltip is 49px wide, it starts at x=56, and .appnav / .nav-scroll clip
   at x=57 — so TWO of its forty-nine pixels were ever on screen. A 58px rail
   whose labels are clipped by the rail is thirteen unlabelled icons, and no
   amount of hovering teaches you what any of them is.

   It cannot be fixed in place. `overflow-y: auto` forces the other axis to a
   clipping value, so .nav-scroll must clip horizontally; and .nav-scroll now
   carries a mask-image, which establishes a stacking context that would fade a
   fixed-position descendant along with everything else. The label has to leave
   the nav entirely, so the script parents one shared element to <body> — no
   ancestor between it and the viewport clips, masks or transforms.

   One element, not one per tile: thirteen absolutely positioned nodes that are
   invisible 99.9% of the time is thirteen chances to leak one. */
.railtip {
  position: fixed; z-index: 200; pointer-events: none;
  background: var(--tip-bg); color: var(--tip-fg);
  padding: 5px 9px; border-radius: var(--r2);
  font-size: var(--fs-xs); white-space: nowrap; box-shadow: var(--sh-2);
  opacity: 0; transition: opacity var(--dur-1);
}
.railtip[data-on] { opacity: 1; }

/* the floating re-open button, only visible when the nav is a rail */
.nav-expand { display: none; }
[data-nav="collapsed"] .nav-expand { display: grid; }

.appmain { display: flex; flex-direction: column; overflow: hidden; min-inline-size: 0; min-block-size: 0; }

/* every screen gets the same top strip inside the content area */
.topbar {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4); border-block-end: 1px solid var(--border);
  background: var(--surface); flex-wrap: wrap; block-size: 49px; flex-shrink: 0;
}
.topbar h2 { font-size: var(--fs-lg); }
.crumbs { display: flex; align-items: center; gap: 6px; font-size: var(--fs-sm); color: var(--faint); }
.crumbs b { color: var(--text); font-weight: 600; }

/* --- the appearance picker -------------------------------------------------
   Theme and accent are stored per device, not per account: they are read by the
   anti-flash script in <head> before the stylesheet paints, and a round trip to
   the server cannot happen that early. Signed-out visitors get it too — it costs
   nothing, and on a shared demo machine it is the first thing anyone touches. */
.appearance { position: relative; }
.appearance-menu {
  position: absolute; inset-block-start: calc(100% + 8px); inset-inline-end: 0; z-index: 60;
  inline-size: 236px; display: flex; flex-direction: column;
  padding: var(--sp-4); border: 1px solid var(--border); border-radius: var(--r3);
  background: var(--surface); box-shadow: var(--sh-3);
}
.appearance-menu .lbl { margin-block: var(--sp-4) var(--sp-2); }
.appearance-menu .lbl:first-child { margin-block-start: 0; }
.appearance-menu .seg { inline-size: 100%; }
.appearance-menu .seg button { flex: 1; padding-inline: 0; }

.swatches { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.sw {
  inline-size: 26px; block-size: 26px; border-radius: var(--rf);
  background: var(--sw); border: 2px solid var(--surface);
  box-shadow: 0 0 0 1px var(--border-strong);
  transition: box-shadow var(--dur-1), transform var(--dur-1);
}
.sw:hover { transform: scale(1.1); }
/* A ring rather than a tick inside the dot: a tick is unreadable against half of
   these colours, and the ring reads the same in either theme. */
.sw[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--accent); }
.sw[data-a="ink"]   { --sw: var(--a-ink); }
.sw[data-a="ocean"] { --sw: var(--a-ocean); }
.sw[data-a="pine"]  { --sw: var(--a-pine); }
.sw[data-a="clay"]  { --sw: var(--a-clay); }
.sw[data-a="plum"]  { --sw: var(--a-plum); }
.sw[data-a="slate"] { --sw: var(--a-slate); }
.sw[data-a="indigo"] { --sw: var(--a-indigo); }

.screen { display: none; flex: 1; min-block-size: 0; flex-direction: column; overflow: hidden; }
.screen.is-active { display: flex; }
.scrollarea { flex: 1; overflow-y: auto; }

/* An in-page jump is followed rather than cut to (BUG-0011). The Top up button moves the page 52px
   on a tall screen, and a 52px jump-cut is invisible while a 52px glide is not — which was the
   whole of the client's "nothing is happening". Only for people who have not asked for less. */
@media (prefers-reduced-motion: no-preference) {
  .scrollarea { scroll-behavior: smooth; }
}
.pad { max-inline-size: 1180px; margin-inline: auto; padding: var(--sp-6) var(--sp-6) var(--sp-9); }

/* --- one centre line across a scroll boundary --------------------------------
   A scrollbar is taken out of the CONTENT box, so `margin-inline: auto` inside a
   scroller centres on a narrower box than the identical rule outside it. Every
   layout here has a scrolling middle between a static header and a static footer
   or dock, so each one had TWO centre lines about 5.5px apart — a thin scrollbar
   halved. That is too small to read as a specific mistake and too large to look
   deliberate, which is exactly why it reads as "the UI is not aligned" without
   anyone being able to point at what is wrong.

   It was in six places at once: the icon rail (icons left of the brand and the
   New chat button), the conversation column against the prompt box below it, and
   every admin and account page's content column against the top bar.

   `scrollbar-gutter` fixes it at the source by reserving the gutter whether or
   not a scrollbar is showing. Which value depends on what the children do:

     both-edges  content is CENTRED in the scroller, so the gutter has to be
                 symmetrical or the box is still off-centre by the full gutter.
     stable      content is LEFT-ALIGNED; only the trailing edge needs to stop
                 moving, and a symmetrical gutter would just waste the width.

   The collapsed rail gets neither. It is 58px, and two thin gutters leave 36px
   for a 38px tile — so it hides the scrollbar instead, which costs nothing: a
   scrollbar in a 58px rail is not a control anyone can use.

   `scrollbar-width: thin` is repeated on every scroller here, and that is not
   redundant with the :root declaration at the top of the file. The comment there
   says "both properties inherit"; only `scrollbar-color` does. `scrollbar-width`
   is NOT an inherited property, so declaring it on :root styled exactly one
   scroller — the page itself — and every panel in the product kept a full system
   bar. The seam down the middle of the two-panel layout that the :root comment
   set out to remove was still there, in the one place it was written about.

   --- and then `stable` came off the panels again ------------------------------
   Client, on the sidebar once the two edges finally agreed: *"its left space is
   ok but right have a little bit extra space, even for below conversation list
   as well"*. Correct, and it is what `stable` is for: the gutter is reserved
   whether or not a bar is drawn, so a list with three conversations in it has
   12px on the left and 12px plus an empty gutter on the right. Making the
   siblings reserve the same emptiness kept the column internally consistent and
   left the column itself lopsided — which is what the client is looking at.

   A panel whose header and footer are visible beside the scroller gets `auto`.
   With no bar, every edge in the panel is 12px on both sides, with no measured
   variable involved at all. With a bar, the 10px belongs to a control that is
   actually drawn there, which is what that space is for and how every
   application on the machine behaves. The cost is a 10px reflow at the moment a
   panel crosses into overflow; that is one frame, during a content change, and
   the client has reported the permanent version of that gap twice.

   `stable` stays where nothing sits beside the scroller to disagree with and the
   content is filtered on every keystroke — a popover list that gains and loses
   its bar as you type would otherwise shift under the cursor. It also stays on
   the centred scrollers, where `both-edges` is what keeps the content centred.

   With the panels on `auto`, nothing needs to know the scrollbar's width, and
   --sbw and the probe that measured it are gone. Four rounds of one client
   complaint came out of that variable. Do not reintroduce it: if two edges have
   to agree, make them children of one box (D-229). */
.scrollarea, .thread { scrollbar-gutter: stable both-edges; scrollbar-width: thin; }
.mpick-list, .palette-list { scrollbar-gutter: stable; scrollbar-width: thin; }
.wslist-scroll, .insp-scroll, .pane-body,
.nav-scroll { scrollbar-gutter: auto; scrollbar-width: thin; }

[data-nav="collapsed"] .nav-scroll { scrollbar-gutter: auto; scrollbar-width: none; }
[data-nav="collapsed"] .nav-scroll::-webkit-scrollbar { inline-size: 0; block-size: 0; }

/* --- "there is more below" -------------------------------------------------
   THE FADE ON ITS OWN WAS NOT ENOUGH, and the measurement says why. On a 670px
   window an administrator's rail hides four of its thirteen items and leaves a
   fifth showing 23 of its 38 pixels — and the mask paints that fifth item's
   icon at an alpha of 0.13. A muted grey glyph at thirteen percent on
   --surface-2 is not a signal; it is an absence with a gradient on it. The
   affordance measured as present and was reported, again, as "I can not scroll
   it", which is the only verdict that counts.

   The answer to that was a pair of chevron buttons, and the client's verdict on
   those was "personally I didn't like it — maybe some spacing or some UI changes
   could make it good, I am fine if you not to use scrollbar." Which is the right
   instinct: a control for scrolling a list is an apology for the list being too
   long. **THE RAIL NO LONGER SCROLLS, BECAUSE IT NO LONGER OVERFLOWS** — see the
   group tile below. The chevrons are gone.

   The fade stays. It costs no width and needs no colour token — which matters,
   because the nav sits on --surface-2 in one theme, a drawer shadow in another
   and a scrolled tile underneath it at all times — and there are still windows
   short enough for six tiles to overflow, where it is the correct and only
   signal. It is no longer carrying that job alone in the case that mattered. */
.nav-scrollwrap[data-more="bottom"] .nav-scroll { mask-image: linear-gradient(to bottom, #000 calc(100% - 24px), transparent); }
.nav-scrollwrap[data-more="top"]    .nav-scroll { mask-image: linear-gradient(to bottom, transparent, #000 24px); }
.nav-scrollwrap[data-more="both"]   .nav-scroll { mask-image: linear-gradient(to bottom, transparent, #000 24px, #000 calc(100% - 24px), transparent); }

/* --- a section becomes ONE tile in the rail ---------------------------------
   THE ARITHMETIC, WHICH IS THE WHOLE ARGUMENT. A collapsed tile is 38px plus a
   2px margin. An administrator has thirteen items, so the list wants 13 x 40 +
   24px of padding = 544px. On a 670px window the rail has 351px to give it:
   193px of overflow, four items completely invisible, and no arrangement of
   spacing that closes a gap that size. Shrinking the tile to fit would need 27px
   tiles — below the size at which a 18px icon has any margin around it, and well
   below a usable pointer target.

   So the list gets shorter instead of the tiles getting smaller. Administration
   is eight of those thirteen items and it is a section already — it has a label
   in the expanded nav that the rail throws away with `display: none`. In the
   rail it collapses to a single tile that opens a flyout, which gives that label
   somewhere to be said out loud again.

   Six tiles: 6 x 40 + 24 = 264px, against 351px on the same 670px window. It
   fits, with room. A customer who is not staff has five items and never sees any
   of this.

   Desktop rail only. Below 901px "collapsed" means a hidden drawer rather than a
   rail, and the drawer is full width with room for all thirteen — the media
   query at the end of this block hands every item back. */
.nav-group { display: contents; }
.nav-grp { display: none; }
[data-nav="collapsed"] .nav-grp { display: flex; }
[data-nav="collapsed"] .nav-group > .nav-item:not(.nav-grp) { display: none; }
[data-nav="collapsed"] .nav-grp[aria-expanded="true"] { background: var(--accent-soft); color: var(--accent); }

/* The flyout is parented to <body> for exactly the reasons .railtip is: .appnav
   clips horizontally, .nav-scroll must clip horizontally because `overflow-y:
   auto` forces the other axis to a clipping value, and .nav-scroll carries a
   mask-image whose stacking context would fade a fixed descendant along with the
   content. Nothing between this and the viewport can touch it. */
.railpop {
  position: fixed; z-index: 210;
  inline-size: max-content; min-inline-size: 168px; max-inline-size: 240px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border-strong); border-radius: var(--r3);
  box-shadow: var(--sh-2); overflow: hidden auto;
  padding: var(--sp-1);
  opacity: 0; visibility: hidden; transition: opacity var(--dur-1);
}
.railpop[data-on] { opacity: 1; visibility: visible; }
.railpop-hd {
  padding: 6px 9px 4px; font-size: var(--fs-micro); font-weight: 650;
  letter-spacing: .06em; text-transform: uppercase; color: var(--faint);
}
/* .menu-item is the row menu's, reused verbatim — same product, same gesture,
   and a second set of near-identical menu rules is a second thing to drift. */
.railpop .menu-item { inline-size: 100%; }
.railpop .menu-item[aria-current="page"] { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.railpop .menu-item[aria-current="page"] > .gi { color: var(--accent); }

@media (max-width: 900px) {
  .app { grid-template-columns: 1fr; }
  /* absolute, not fixed — anchored to .app, so it can't be offset by a
     hard-coded top value that stops matching the bar above it */
  .appnav {
    position: absolute; inset-block: 0; inset-inline-start: 0; z-index: 120;
    inline-size: min(84vw, 268px); box-shadow: var(--sh-3);
    transform: translateX(-102%); transition: transform var(--dur-2) var(--ease-io);
  }
  [data-nav="open"] .appnav { transform: none; }
  [data-nav="collapsed"] .appnav { visibility: hidden; }
  [data-nav="collapsed"] { --nav-w: 0px; }
  .pad { padding: var(--sp-4) var(--sp-4) var(--sp-7); }
  /* the rail-with-tooltips treatment is desktop-only; on mobile it's a drawer */
  [data-nav="collapsed"] .nav-word, [data-nav="collapsed"] .nav-txt,
  [data-nav="collapsed"] .nav-badge, [data-nav="collapsed"] .nav-sect,
  [data-nav="collapsed"] .nav-cta .btn .btn-lbl, [data-nav="collapsed"] .credit-line,
  [data-nav="collapsed"] .meter, [data-nav="collapsed"] .nav-user-meta { display: revert; }
  [data-nav="collapsed"] .nav-collapse { display: grid; }
  [data-nav="collapsed"] .nav-brand { padding-inline: var(--sp-3); justify-content: flex-start; }
  [data-nav="collapsed"] .nav-cta { padding-inline: var(--sp-3); }
  [data-nav="collapsed"] .nav-scroll { padding-inline: var(--sp-2); }
  [data-nav="collapsed"] .nav-cta .btn {
    inline-size: 100%; block-size: auto; padding: 7px 13px; margin-inline: 0;
    justify-content: center; gap: 6px;
  }
  [data-nav="collapsed"] .nav-item {
    inline-size: 100%; block-size: auto; justify-content: flex-start;
    padding: 7px 9px; margin-inline: 0; margin-block-end: 1px; gap: 10px;
  }
  /* Below 900px "collapsed" means a hidden drawer, not a rail — so every rail
     rule above has to be handed back, the stacked account row included. */
  [data-nav="collapsed"] .nav-foot { padding-inline: var(--sp-3); }
  [data-nav="collapsed"] .nav-user {
    flex-direction: row; align-items: center; justify-content: flex-start; gap: var(--sp-2);
  }
  [data-nav="collapsed"] .nav-user .btn {
    inline-size: 100%; block-size: auto; padding: 7px 13px; gap: 6px;
  }
  [data-nav="collapsed"] .nav-item::after,
  [data-nav="collapsed"] .nav-cta .btn::after { content: none; }
  /* The drawer is up to 268px wide and as tall as the phone — it has room for
     every item, so the section is a section again and the rail's stand-in tile
     goes away. Handing these back is not optional: without it a phone would get
     the rail's one-tile Administration and a flyout positioned against a drawer
     that slides. */
  [data-nav="collapsed"] .nav-grp { display: none; }
  [data-nav="collapsed"] .nav-group > .nav-item:not(.nav-grp) { display: flex; }
}
@media (min-width: 901px) { .scrim { display: none !important; } }

/* ---------------------------------------------------------------------------
   1. HOME  (prompt-first)
   --------------------------------------------------------------------------- */
.home { max-inline-size: 780px; margin-inline: auto; padding: var(--sp-8) var(--sp-6) var(--sp-9); }
.home-head { margin-block-end: var(--sp-6); }
.home-head h1 { font-size: var(--fs-3xl); letter-spacing: -.028em; margin-block-end: 5px; }
.home-head p { color: var(--muted); font-size: var(--fs-md); }

.pbox {
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--r4); box-shadow: var(--sh-2); overflow: hidden;
  transition: border-color var(--dur-1), box-shadow var(--dur-1);
}
.pbox:focus-within { border-color: var(--accent); box-shadow: var(--sh-2), 0 0 0 3px var(--accent-soft); }
.pbox-head { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-2) var(--sp-3); border-block-end: 1px solid var(--border); background: var(--surface-2); flex-wrap: wrap; }
.pbox textarea { inline-size: 100%; border: 0; background: none; resize: none; padding: var(--sp-4); font-size: var(--fs-lg); line-height: 1.55; min-block-size: 92px; }
.pbox textarea:focus { outline: none; }
.pbox textarea::placeholder { color: var(--faint); }
.pbox-foot { display: flex; align-items: center; gap: var(--sp-1); padding: 0 var(--sp-3) var(--sp-3); flex-wrap: wrap; }
.pbox-count { margin-inline-start: auto; display: flex; align-items: center; gap: var(--sp-3); font-size: var(--fs-xs); color: var(--faint); }

.tool-toggle { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border-radius: var(--r2); font-size: var(--fs-sm); color: var(--muted); border: 1px solid transparent; transition: all var(--dur-1); }
.tool-toggle:hover { background: var(--surface-3); color: var(--text); }
.tool-toggle[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); border-color: var(--accent-line); }

.home-meta { display: flex; align-items: center; gap: var(--sp-2); margin-block-start: var(--sp-4); font-size: var(--fs-sm); color: var(--muted); flex-wrap: wrap; }
.pulse { inline-size: 6px; block-size: 6px; border-radius: var(--rf); background: var(--ok); animation: pulse 2.4s ease-in-out infinite; }
@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: .35 } }

.starters { display: grid; gap: var(--sp-2); grid-template-columns: repeat(auto-fit, minmax(172px, 1fr)); margin-block-start: var(--sp-6); }
.starter { padding: var(--sp-3) var(--sp-4); border: 1px solid var(--border); border-radius: var(--r3); background: var(--surface); text-align: start; transition: border-color var(--dur-1), box-shadow var(--dur-1); }
.starter:hover { border-color: var(--border-strong); box-shadow: var(--sh-1); }
.starter b { display: block; font-size: var(--fs-sm); font-weight: 600; margin-block-end: 2px; }
.starter span { font-size: var(--fs-xs); color: var(--faint); }

.trybar { display: flex; align-items: center; gap: var(--sp-3); margin-block-start: var(--sp-6); padding: var(--sp-3) var(--sp-4); border: 1px solid var(--accent-line); background: var(--accent-soft); border-radius: var(--r3); font-size: var(--fs-sm); color: var(--muted); flex-wrap: wrap; }
.trybar b { color: var(--accent); font-weight: 600; }

.home-recent { margin-block-start: var(--sp-8); }
.home-recent-head { display: flex; align-items: center; gap: var(--sp-3); margin-block-end: var(--sp-3); }
.rec-list { border: 1px solid var(--border); border-radius: var(--r3); overflow: hidden; background: var(--surface); }
.rec { display: flex; align-items: center; gap: var(--sp-3); padding: 10px var(--sp-4); border-block-start: 1px solid var(--border); font-size: var(--fs-sm); }
.rec:first-child { border-block-start: 0; }
.rec:hover { background: var(--surface-2); }
.rec-t { flex: 1; min-inline-size: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rec-when { color: var(--faint); font-size: var(--fs-xs); white-space: nowrap; }
.dotcol { inline-size: 6px; block-size: 6px; border-radius: var(--rf); flex-shrink: 0; }

/* ---------------------------------------------------------------------------
   2. WORKSPACE — conversation list + thread + inspector (all collapsible)
   --------------------------------------------------------------------------- */
/* The track widths animate, so a panel appears to slide rather than to teleport.

   `grid-template-columns` and not the custom property: a `var()` in a transition animates nothing,
   because a custom property has no type the browser can interpolate unless it is registered with
   @property — and registering one to animate a value that is only ever read here would be a lot of
   machinery for a shorthand. Transitioning the resolved track list is the same effect in one line.

   180ms, and only the columns. The thread inside re-flows as the track moves, which is real work on
   a long conversation, so this is deliberately shorter than the drawer's slide: long enough to show
   which panel moved, short enough that nobody waits for it. */
.ws { flex: 1; display: grid; grid-template-columns: var(--list-w) 1fr var(--insp-w); min-block-size: 0; position: relative; transition: grid-template-columns 180ms var(--ease-io); }
@media (prefers-reduced-motion: reduce) { .ws { transition: none; } }

.wslist { background: var(--surface-2); border-inline-end: 1px solid var(--border); display: flex; flex-direction: column; overflow: hidden; min-inline-size: 0; }
/* NEVER display:none a grid child here — removing it shifts every sibling one
   column to the left, so the thread lands in the 0px track and the screen
   goes blank. Keep it in flow at zero width instead. */
[data-list="collapsed"] .wslist { visibility: hidden; border-inline-end-width: 0; }
.fchip { padding: 3px 9px; border-radius: var(--rf); font-size: var(--fs-xs); color: var(--muted); border: 1px solid var(--border); background: var(--surface); }
.fchip[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); border-color: var(--accent-line); font-weight: 600; }

/* --- one leading edge AND one trailing edge down the whole sidebar -----------
   The column had FOUR leading edges, all within 5px of each other:

     New chat button, box edge          12px   (.wslist-head padding)
     search field, box edge             12px   (.wslist-search padding)
     search field, TEXT                 21px   (+ .input-sm padding + border)
     conversation row, band edge         8px   (.wslist-scroll padding)
     conversation row, TEXT             17px   (+ .conv padding)
     "Pinned" group label, TEXT         16px

   Nothing there is far enough out to look like a mistake, which is exactly why
   it reads as "the search box is not aligned" without anyone being able to say
   by how much. Two edges now, and every element is on one of them: boxes at
   12px, text at 21px. The 9px on .conv is not arbitrary — it is 21 minus 12,
   chosen so a row's label starts where the search field's own text starts.

   That fixed the LEADING edge and left the trailing one, which was worse:

     New chat button / search field, trailing   13px
     conversation row, trailing                 28px

   — a FIFTEEN pixel disagreement, three times the largest leading-edge error,
   running the full height of the list directly under the search box. The cause
   is `scrollbar-gutter: stable` on .wslist-scroll: it reserves the scrollbar's
   width inside the scroller whether or not a scrollbar is showing, so every row
   stops 15px short of a search field that does not reserve it. The client read
   that, correctly, as the search box not lining up with anything below it.

   The first answer was to have the siblings reserve the same width, from a --sbw
   the layout script measured. That went three more rounds — the probe not
   running, then measuring a bar nothing drew — and even once it was right the
   client saw what it had faithfully reserved: *"its left space is ok but right
   have a little bit extra space"*. An empty gutter is empty whether or not
   everything agrees about how wide it is.

   Both halves are structural now, and neither needs a number:

     .wslist-scroll uses `scrollbar-gutter: auto`, so nothing is reserved until
     a scrollbar is actually drawn. Every edge in the column is 12px on both
     sides, and --sbw is deleted.

     The search field is a CHILD of .wslist-head, not a sibling wrapper with its
     own copy of the padding. "The search box lines up with the New chat button"
     was true only while two rules thirty lines apart agreed on one expression,
     and every round of edits was another chance for them to stop. One container,
     one padding, one gap: they are the same width by construction. */
.wslist-head    { padding: var(--sp-3); display: flex; flex-direction: column; gap: var(--sp-2); border-block-end: 1px solid var(--border); }
/* Vertical only — the 8px inline padding is what puts the field's TEXT on the
   21px edge the conversation rows use. Height matched to the button above it:
   two stacked boxes of equal width and unequal height read as a mistake. */
.wslist-head .input { padding-block: 7px; }
.wslist-foot    { padding: var(--sp-2) var(--sp-3); border-block-start: 1px solid var(--border); display: flex; flex-direction: column; gap: 1px; }
.wslist-scroll  { flex: 1; overflow-y: auto; padding: var(--sp-2) var(--sp-3) var(--sp-4); }

/* Shown at EVERY width, since the list became collapsible on the desktop too (D-241).

   Both used to be handed out only inside the max-width:900px block, because above it the list was
   a column that was always there and had nothing to close. It can be hidden now, so the control
   that hides it and the control that brings it back have to exist wherever that can happen — and
   they are the same two controls at both breakpoints rather than a desktop pair and a mobile pair
   that would drift apart. */
.wslist-title { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); }
.ws-listbtn   { display: inline-flex; }
.wslist-title b { font-size: var(--fs-sm); }
/* A resting box, not a bare glyph. Every other icon-btn in the product sits on something the eye
   already reads as a control (the workspace bar, a message toolbar); this one sits alone against
   the list header, so with only :hover to announce itself it was invisible until pointed at —
   and on a touch screen there is no hover to point with. */
.wslist-close {
  color: var(--muted); border: 1px solid var(--border); background: var(--surface-2);
  transition: background var(--dur-1), color var(--dur-1), border-color var(--dur-1);
}
.wslist-close:hover { color: var(--text); background: var(--surface-3); border-color: var(--border-strong); }
/* The list's own switches, at the foot of it.

   A ROW, not a chip. Both of these change which conversations the list shows, so they are drawn as
   full-width rows with the icon on the leading edge - the same shape as the conversations above
   them and as the rail's items, which is what makes them read as part of the list rather than as
   loose buttons parked under it. As a chip under the search box the comparisons filter read as a
   suggestion belonging to the field.

   The pressed state is the accent, and the icon takes it too: at this size the label alone changing
   colour is a difference a reader has to look for. */
.wslist-opt {
  display: flex; align-items: center; gap: var(--sp-2);
  inline-size: 100%; padding: 6px 9px; border-radius: var(--r1);
  font-size: var(--fs-xs); color: var(--muted); text-align: start;
  transition: background var(--dur-1), color var(--dur-1);
}
.wslist-opt > .gi { inline-size: 14px; block-size: 14px; vertical-align: initial; }
.wslist-opt:hover { background: var(--surface-3); color: var(--text); }
.wslist-opt[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); font-weight: 600; }

/* THE HISTORY FILTERS (P-07), directly under the search box.

   The trigger is deliberately NOT a .wslist-opt. Those are switches: one press, one effect, and a
   pressed state that says what is currently true. This one opens a panel containing two controls
   that hold values, so it is drawn quieter and smaller - a disclosure, not a third switch. Making
   it look like its neighbours would promise that pressing it filters something, when pressing it
   only shows the controls that do.

   The count beside the label is the whole reason the panel is allowed to be collapsed: closed and
   silent, it would hide the fact that a list is being narrowed. */
.wslist-filters { display: flex; flex-direction: column; gap: var(--sp-2); }

.wslist-filterbtn {
  display: flex; align-items: center; gap: var(--sp-2);
  align-self: start; padding: 2px 4px; border-radius: var(--r1);
  font-size: var(--fs-xs); color: var(--muted);
  transition: color var(--dur-1);
}
.wslist-filterbtn > .gi { inline-size: 13px; block-size: 13px; vertical-align: initial; }
.wslist-filterbtn:hover { color: var(--text); }
.wslist-filterbtn[aria-expanded="true"] { color: var(--text); }

/* Stacked rather than side by side. The sidebar is narrow enough that two selects in a row would
   each be about eleven characters wide, which truncates every model name in the catalogue. */
.wslist-filterbody { display: flex; flex-direction: column; gap: var(--sp-2); }
.wslist-filterbody > .btn { align-self: start; }

/* `.pane-tab.on` used to live here, borrowed by the sidebar's comparisons chip from the compare
   card's swipe strip. The chip is gone and the borrowing went with it: the strip's own pressed
   state is [aria-current], written where the strip is. */
.conv-cmp { color: var(--accent); font-size: var(--fs-xs); flex-shrink: 0; }

.wsgroup { display: flex; align-items: center; gap: 6px; padding: var(--sp-4) 9px var(--sp-1); }
.conv { display: flex; align-items: center; gap: 7px; padding: 6px 9px; border-radius: var(--r1); font-size: var(--fs-sm); color: var(--muted); margin-block-end: 1px; transition: background var(--dur-1), color var(--dur-1); }

/* A column, because a row can carry a second line — the search snippet, or the date the
   retention window will remove it (D-191). The ellipsis moves down to .conv-label with it:
   a flex column cannot truncate its own children, only they can. */
.conv-title { flex: 1; min-inline-size: 0; display: flex; flex-direction: column; align-items: flex-start; gap: 2px; }
.conv-line { display: flex; align-items: center; gap: 5px; min-inline-size: 0; max-inline-size: 100%; }
.conv-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* A real pin in the accent, not a ▪. The square said "there is something about
   this row" and left you to work out what; the accent is the same colour the
   product uses for every other "this one is on" state. */
.conv-pin { color: var(--accent); flex-shrink: 0; display: inline-flex; }
.conv-pin > svg { inline-size: 11px; block-size: 11px; }
.conv-snippet, .conv-expiry { font-size: var(--fs-micro); color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-inline-size: 100%; }

/* Who shared it, in a team (backlog 13.2). Sits in the snippet line and is quieter than one: a
   colleague’s name is context for the title above it, not a second line to read. */
.conv-sharer { font-style: italic; opacity: .85; }
.conv-expiry { color: var(--muted); }
.conv-expiry.is-soon { color: var(--warning-fg); }
.conv-acts { display: flex; gap: 1px; opacity: 0; transition: opacity var(--dur-1); }
.conv:hover { background: var(--surface-3); color: var(--text); }
.conv:hover .conv-acts, .conv:focus-within .conv-acts { opacity: 1; }
.conv.is-active { background: var(--surface-4); color: var(--text); font-weight: 550; }
.conv.is-archived .conv-label { opacity: .6; }
/* Zero inline padding while renaming, so the edit field's border lands on the
   12px box edge and its text on the 21px text edge — i.e. exactly where the
   label it replaced was. Renaming should look like the title becoming editable,
   not like the row jumping sideways. */
.conv.is-renaming { padding: 2px 0; }
.conv.is-renaming .input { inline-size: 100%; }

/* Always visible on a touch screen: there is no hover to reveal them with, and a delete
   button you can only reach with a mouse is not a delete button on a phone. */
@media (hover: none) {
  .conv-acts { opacity: 1; }
}

/* An open menu keeps its trigger on screen. Without this the button faded out the
   moment the pointer moved onto the menu it had just opened, and the menu was left
   pointing at nothing. */
.conv.is-menu-open { background: var(--surface-3); color: var(--text); }
.conv.is-menu-open .conv-acts { opacity: 1; }

/* ---------------------------------------------------------------------------
   A ROW MENU — four hover icons became one ⋮
   ---------------------------------------------------------------------------
   The row is 250px wide and had pin, rename, archive and delete crammed into
   its trailing edge, each 22px, each meaningless without the tooltip that only
   appears after you have already hovered the one you guessed at. Four icons is
   also four things to miss by two pixels, and the one you land on by accident
   is delete.

   One trigger, and a menu that says the words. The pin is the exception and
   stays on the row itself, because a pinned conversation has to LOOK pinned
   without opening anything — state belongs on the row, actions belong in the
   menu. */
/* WIDTH, AND WHY THIS SELECTOR IS SO LONG.

   This was `.rowmenu { inline-size: min(214px, 78vw) }` and it never once applied.
   `.mpick-pop, .mpick-pop:popover-open` further down sets 340px, and in a selector
   list each selector carries its own specificity — so the moment the menu opened,
   `.mpick-pop:popover-open` (0,2,0) beat `.rowmenu` (0,1,0) and the row menu was
   340px wide. `.mpick-pop.rowmenu:popover-open` is (0,3,0) and wins.

   The knock-on was the position, not just the size. The menu is end-aligned to the
   ⋮, so it is placed at `button.right - width`; at 340px that went negative and the
   placement clamped it to the window edge — which is why a menu belonging to a
   button on the right of a 250px sidebar opened against the left of the screen,
   wider than the sidebar it came from.

   Sized to its content rather than to a number. Pin / Rename / Archive / Delete are
   short words, and a menu padded out to a fixed width reads as though something is
   missing from it. The floor stops a two-item menu becoming a stub; the ceiling is
   for a long conversation title if one is ever shown in here. */
.mpick-pop.rowmenu,
.mpick-pop.rowmenu:popover-open {
  inline-size: max-content;
  min-inline-size: 160px;
  max-inline-size: min(240px, 78vw);
}
.rowmenu .mpick-list { padding: var(--sp-1); }

/* Used by both the row menu, where the items are <button>s, and the rail's
   section flyout, where they are <a>s — hence the explicit colour and the
   text-decoration reset, neither of which a <button> would need. */
.menu-item {
  display: flex; align-items: center; gap: var(--sp-3);
  inline-size: 100%; text-align: start;
  padding: 7px 9px; border-radius: var(--r2);
  font-size: var(--fs-sm); color: var(--text); text-decoration: none;
  transition: background var(--dur-1), color var(--dur-1);
}
.menu-item:hover { background: var(--surface-3); }
.menu-item > .gi { inline-size: 15px; block-size: 15px; flex-shrink: 0; color: var(--muted); vertical-align: initial; }
.menu-item:hover > .gi { color: var(--text); }
.menu-sep { block-size: 1px; background: var(--border); margin: var(--sp-1) 3px; }

/* --- colour by meaning, and only by meaning --------------------------------
   Not a colourful icon set. Icons across the product stay in the text colour
   they inherit; colour is spent on the two things that are worth interrupting
   someone for, and spending it anywhere else is what stops it working here.

     destructive   --critical   the only irreversible item in any menu
     active state  --accent     pinned, selected, on

   A red trash icon in a list of grey ones is legible at a glance precisely
   because nothing else in that list is coloured. */
.menu-item.is-danger, .menu-item.is-danger > .gi { color: var(--critical); }
.menu-item.is-danger:hover { background: var(--critical-bg); }
.menu-item.is-danger:hover > .gi { color: var(--critical); }
.menu-item.is-on > .gi { color: var(--accent); }
.icon-btn.is-danger:hover { background: var(--critical-bg); color: var(--critical); }

.conv-group { padding: var(--sp-3) 9px var(--sp-1); font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: .06em; color: var(--faint); font-weight: 650; }

/* ---------------------------------------------------------------------------
   FOLDERS — the sidebar's grouping layer, above the loose conversations.

   Nothing here invents a new row shape. A conversation inside a folder is the
   same .conv it is outside one, indented; the only new things are the header
   that opens the group and the marks that say what kind of folder it is. A
   second row style for "a conversation, but in a folder" would have been two
   things to keep in step for no gain the reader can see.
   --------------------------------------------------------------------------- */
.folds { margin-block-end: var(--sp-2); }
.folds-head {
  display: flex; align-items: center; justify-content: space-between; gap: 6px;
  padding: var(--sp-3) 5px var(--sp-1) 9px;
  font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: .06em;
  color: var(--faint); font-weight: 650;
  border-radius: var(--r1);
}

/* The heading is also where a conversation is dropped to leave its folder, so it
   has to light up like one. Dashed, because it is a destination that is not a
   folder — the same affordance would say the wrong thing. */
.folds-head.is-drop { background: var(--accent-soft); color: var(--accent); outline: 1px dashed var(--accent); }

.fold { border-radius: var(--r1); }
.fold.is-drop { background: var(--accent-soft); outline: 1px solid var(--accent); }
.fold-head { display: flex; align-items: center; gap: 2px; }
.fold-btn {
  flex: 1; min-inline-size: 0;
  display: flex; align-items: center; gap: 6px;
  padding: 6px 4px 6px 6px; border-radius: var(--r1);
  font-size: var(--fs-sm); font-weight: 550; color: var(--text); text-align: start;
  transition: background var(--dur-1);
}
.fold-btn:hover { background: var(--surface-3); }

/* Rotated rather than swapped for a second glyph. One shape that turns is one
   thing moving; two glyphs is a redraw, and at 11px the eye reads the redraw as
   a flicker. */
.fold-caret { display: inline-flex; flex-shrink: 0; color: var(--faint); transition: transform var(--dur-1); }
.fold-caret > svg { inline-size: 11px; block-size: 11px; }
.fold.is-open .fold-caret { transform: rotate(90deg); }

.fold-ico { display: inline-flex; flex-shrink: 0; color: var(--muted); }
.fold-ico > .gi { inline-size: 15px; block-size: 15px; }

/* The six Nova accents, which are already contrast-verified against both
   surfaces — so a coloured folder icon cannot land on a colour that disappears
   in one theme. An unset colour keeps --muted and is not a gap in the column. */
.fold-ico[data-c="ink"]   { color: var(--a-ink); }
.fold-ico[data-c="ocean"] { color: var(--a-ocean); }
.fold-ico[data-c="pine"]  { color: var(--a-pine); }
.fold-ico[data-c="clay"]  { color: var(--a-clay); }
.fold-ico[data-c="plum"]  { color: var(--a-plum); }
.fold-ico[data-c="slate"] { color: var(--a-slate); }

.fold-name { flex: 1; min-inline-size: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* "This folder rewrites every answer inside it." Never the only signal — the
   mark carries a title and screen-reader text, and the edit menu item changes
   its wording to match. */
.fold-proj { display: inline-flex; flex-shrink: 0; color: var(--accent); }
.fold-proj > svg { inline-size: 12px; block-size: 12px; }

.fold-n { flex-shrink: 0; font-size: var(--fs-micro); color: var(--faint); font-variant-numeric: tabular-nums; padding-inline-end: 2px; }

/* Revealed on hover like the conversation row's own menu, and always present
   where there is no hover to reveal it with. */
.fold-acts { position: relative; opacity: 0; transition: opacity var(--dur-1); }
.fold-head:hover .fold-acts, .fold-acts:focus-within { opacity: 1; }
@media (hover: none) { .fold-acts { opacity: 1; } }

/* One indent, one rule. The line is what ties eight rows to the folder they
   belong to once the header has scrolled past the top of the list. */
.fold-kids { margin-inline-start: 13px; padding-inline-start: 7px; border-inline-start: 1px solid var(--border); }
.fold-empty { padding: 5px 9px; margin: 0; }

.menu-lab { padding: 5px 9px 3px; margin: 0; font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: .06em; color: var(--faint); font-weight: 650; }

/* Fifty folders is a legal state, so the section that lists them scrolls rather
   than growing a menu taller than the window. */
.menu-scroll { max-block-size: 210px; overflow-y: auto; }
.menu-scroll .fold-ico > .gi { inline-size: 15px; block-size: 15px; }

/* The dialog. .palette gives it the backdrop and the centring; this is the form
   inside it, which is the one thing the command palette does not already have. */
.palette-back { position: absolute; inset: 0; cursor: default; }
.fold-dlg { position: relative; padding: var(--sp-5); }
.fold-dlg h2 { font-size: var(--fs-lg); margin: 0 0 var(--sp-4); }
.fold-lab { display: block; font-size: var(--fs-sm); font-weight: 600; margin-block: var(--sp-4) 5px; }
.fold-dlg > .fold-lab:first-of-type { margin-block-start: 0; }
.fold-hint { margin: 0 0 6px; }
.fold-inst { resize: vertical; min-block-size: 74px; font-family: inherit; }
.fold-count { margin: 4px 0 0; text-align: end; font-variant-numeric: tabular-nums; }
.fold-count.is-over { color: var(--critical); font-weight: 600; }
.fold-err { color: var(--critical); margin: var(--sp-3) 0 0; font-weight: 550; }
.fold-say { font-size: var(--fs-sm); margin: 0 0 var(--sp-2); }

/* .fold-btns, not .fold-acts. The row's menu wrapper is already .fold-acts, and
   two unrelated things sharing a class means every later rule has to be written
   twice — once for the one it meant and once to undo it for the other. */
.fold-btns { display: flex; justify-content: flex-end; gap: var(--sp-3); margin-block-start: var(--sp-5); }

/* .wslist-foot padding lives with the rest of the sidebar's edges above. It used
   to re-declare it here, 30 lines away from the comment explaining what the
   numbers were for, which is how the trailing edge got fixed in one place and
   not the other. The search field's rule that lived here is gone entirely — the
   field is now a child of .wslist-head and inherits that block's one padding. */

/* Sticky rather than absolute: inside a scroller an absolutely positioned child scrolls away
   with the content, which is exactly when this button is needed. */
.jump-pill { position: sticky; inset-block-end: var(--sp-3); display: block; margin: var(--sp-4) auto 0; padding: 5px 14px; border-radius: var(--rf); border: 1px solid var(--border-strong); background: var(--surface-2); color: var(--text); font-size: var(--fs-xs); box-shadow: var(--sh-2); }
.jump-pill:hover { background: var(--surface-3); }

.palette { position: fixed; inset: 0; z-index: 90; background: rgb(0 0 0 / .38); display: flex; align-items: flex-start; justify-content: center; padding: 12vh var(--sp-4) var(--sp-4); }
.palette-box { inline-size: min(560px, 100%); background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--r3); box-shadow: var(--sh-3); overflow: hidden; }
.palette-input { inline-size: 100%; border: 0; border-block-end: 1px solid var(--border); background: transparent; color: var(--text); padding: var(--sp-4); font-size: var(--fs-md); font-family: inherit; }
.palette-input:focus { outline: none; }
.palette-list { max-block-size: 46vh; overflow-y: auto; padding: var(--sp-2); }
.palette-item { display: flex; align-items: center; gap: 7px; inline-size: 100%; text-align: start; padding: 8px 10px; border-radius: var(--r1); font-size: var(--fs-sm); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.palette-item.is-cursor { background: var(--accent-soft); color: var(--accent); }
.palette-foot { padding: var(--sp-2) var(--sp-4); border-block-start: 1px solid var(--border); margin: 0; }

/* The visible way out. Escape and the backdrop already close these panels and
   neither can be seen, so a panel opened out of curiosity had no exit anybody
   could point at. Positioned against the panel rather than placed in each
   view's header, so the editor and the confirmations get it for free — which
   means the header rows underneath have to give it room. */
.palette-x { position: absolute; inset-block-start: var(--sp-3); inset-inline-end: var(--sp-3); z-index: 1; }
.pe-top, .pr-top, .pr-box h2 { padding-inline-end: 26px; }

/* --- saved prompts ------------------------------------------------------------
   One panel, three views: the list, the editor, and the fill-in step. They swap
   inside .pr-box rather than opening a dialog on top of a dialog, so there is one
   backdrop, one Escape handler and one thing that can have focus.

   .palette-box is `overflow: hidden`, which is right for the command palette and
   wrong here — a prompt body is long. Everything that can grow gets its own
   scroller instead, and the panel itself is capped so it never runs off a laptop
   screen with the buttons below the fold. */
.pr-box { position: relative; inline-size: min(640px, 100%); max-block-size: 78vh; padding: var(--sp-5); display: flex; flex-direction: column; }
.pr-box h2 { font-size: var(--fs-lg); margin: 0 0 var(--sp-4); }

.pr-top { display: flex; align-items: center; gap: var(--sp-3); }
.pr-find { flex: 1; min-inline-size: 0; }

.pr-list { flex: 1; overflow-y: auto; list-style: none; margin: var(--sp-4) 0 0; padding: 0; }
.pr-row { display: flex; align-items: flex-start; gap: 2px; border-radius: var(--r2); }
.pr-row:hover { background: var(--surface-2); }

.pr-pick { flex: 1; min-inline-size: 0; display: grid; gap: 2px; text-align: start; padding: 8px 10px; border-radius: var(--r2); }
.pr-title { font-size: var(--fs-sm); font-weight: 600; color: var(--text); }

/* Two lines of the actual wording, wrapped as written. A prompt library listing
   only titles is a list of things the user has to open one at a time to identify,
   which is the same as not having titled them. */
.pr-body { font-size: var(--fs-xs); color: var(--muted); white-space: pre-wrap; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; }

.pr-vars { display: flex; flex-wrap: wrap; gap: 4px; margin-block-start: 3px; }
.pr-var { font-size: var(--fs-micro); font-weight: 600; padding: 1px 7px; border-radius: var(--rf); background: var(--accent-soft); color: var(--accent); }

/* Revealed on hover, like the conversation row's menu — and always visible where
   there is no hover, because on a phone a control that appears on hover is a
   control that does not exist. */
.pr-acts { display: flex; gap: 2px; padding-block-start: 7px; opacity: 0; transition: opacity .12s ease; }
.pr-row:hover .pr-acts, .pr-row:focus-within .pr-acts { opacity: 1; }

.pr-act { inline-size: 26px; block-size: 26px; display: grid; place-items: center; border-radius: var(--r1); color: var(--faint); }
.pr-act > svg { inline-size: 15px; block-size: 15px; }
.pr-act:hover { background: var(--surface-3); color: var(--text); }
.pr-act-bad:hover { color: var(--critical); }

/* A pinned star stays lit whether or not the row is hovered — it is a fact about
   the prompt, not an affordance. */
.pr-act.is-on { opacity: 1; color: var(--accent); }
.pr-row .pr-acts:has(.is-on) { opacity: 1; }

@media (hover: none) {
  .pr-acts { opacity: 1; }
}

.pr-empty { padding-block: var(--sp-5); }
.pr-starter-lab { margin: var(--sp-5) 0 6px; }
.pr-starters { display: flex; flex-wrap: wrap; gap: 6px; }
.pr-none { font-size: var(--fs-sm); padding-block: var(--sp-6); text-align: center; }
.pr-foot { margin: var(--sp-3) 0 0; }

.pr-edit, .pr-fill, .pr-confirm { display: flex; flex-direction: column; overflow-y: auto; }
.pr-edit > .fold-lab:first-of-type { margin-block-start: 0; }
.pr-text { resize: vertical; min-block-size: 150px; font-family: inherit; }
.pr-meta { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); margin-block-start: 6px; }
.pr-meta .fold-count { margin: 0; }
.pr-field { margin-block-end: var(--sp-3); }
.pr-field .input { inline-size: 100%; }

/* The result, exactly as it will arrive in the message box — and the wording
   being deleted, on the confirmation. A title is not enough to remember what a
   prompt said, and this is the one irreversible thing in the feature. */
.pr-preview { white-space: pre-wrap; word-break: break-word; max-block-size: 30vh; overflow-y: auto; margin: 0; padding: var(--sp-3); background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r2); font-family: inherit; font-size: var(--fs-xs); color: var(--text); }
.pr-confirm .pr-preview { margin-block-end: var(--sp-3); }

/* --- personas -----------------------------------------------------------------
   The picker in the dock, and the panel it opens. Same arrangement as the prompt
   library above — one box, several views — and the same reason for overriding
   .palette-box's overflow: instructions are 2,000 characters and have to scroll
   inside their own field rather than off the end of the panel. */
.pe-btn { display: inline-flex; align-items: center; gap: 6px; max-inline-size: 190px; }

/* Ellipsis rather than wrap: the dock is a single row, and a long persona name
   growing it would push the composer up as the user types. */
.pe-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.pe-box { position: relative; inline-size: min(620px, 100%); max-block-size: 78vh; padding: var(--sp-5); display: flex; flex-direction: column; }
.pe-top { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-3); }
.pe-top p { margin: 3px 0 0; }

.pe-list { flex: 1; overflow-y: auto; list-style: none; margin: var(--sp-4) 0 0; padding: 0; }
.pe-head { font-size: var(--fs-micro); font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--faint); padding: var(--sp-3) 10px 4px; }

.pe-item { display: flex; align-items: center; gap: 2px; border-radius: var(--r2); }
.pe-item:hover { background: var(--surface-2); }

.pe-row { flex: 1; min-inline-size: 0; display: flex; align-items: center; gap: 10px; text-align: start; padding: 8px 10px; border-radius: var(--r2); }
.pe-row.on { background: var(--accent-soft); }

/* A fixed square whatever is in it, so a row with an emoji and a row with a
   letter line their names up in the same column. */
.pe-icon { flex: none; inline-size: 30px; block-size: 30px; display: grid; place-items: center; border-radius: var(--r2); background: var(--surface-3); font-size: var(--fs-sm); color: var(--muted); }
.pe-row.on .pe-icon { background: var(--accent-fill); color: var(--on-accent); }

.pe-text { min-inline-size: 0; display: grid; gap: 1px; }
.pe-text b { font-size: var(--fs-sm); color: var(--text); }
.pe-text span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Revealed on hover like the prompt row's, and always visible where there is no
   hover — on a phone a control that appears on hover does not exist. */
.pe-acts-row { display: flex; gap: var(--sp-3); padding-inline-end: 10px; opacity: 0; transition: opacity .12s ease; }
.pe-act { font-size: var(--fs-xs); color: var(--muted); }
.pe-act:hover { color: var(--text); text-decoration: underline; }
.pe-act-bad:hover { color: var(--critical); }
.pe-item:hover .pe-acts-row, .pe-item:focus-within .pe-acts-row { opacity: 1; }

@media (hover: none) {
  .pe-acts-row { opacity: 1; }
}

.pe-foot { margin: var(--sp-3) 0 0; }

.pe-edit, .pe-confirm { display: flex; flex-direction: column; overflow-y: auto; }
.pe-fields { display: flex; gap: var(--sp-3); }
.pe-f { display: block; margin-block-start: var(--sp-3); }
.pe-f .input, .pe-f textarea { inline-size: 100%; }
.pe-f-icon { inline-size: 74px; flex: none; }
.pe-f-name { flex: 1; min-inline-size: 0; }
.pe-body { min-block-size: 190px; resize: vertical; }

.pe-acts { display: flex; justify-content: flex-end; gap: var(--sp-3); margin-block-start: var(--sp-4); }
.pe-preview { white-space: pre-wrap; word-break: break-word; max-block-size: 30vh; overflow-y: auto; margin: var(--sp-3) 0; padding: var(--sp-3); background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r2); font-size: var(--fs-xs); color: var(--text); }

/* --- web search: the toggle, and the citations ---------------------------------
   Two pieces, at opposite ends of one feature. The button in the dock says "look
   this one up"; the list under the answer says what it found.

   The list is DELIBERATELY quiet — smaller type, no card, no colour of its own.
   It is evidence, not content: the moment it competes with the answer for
   attention, every searched reply looks like a search results page with a
   paragraph on top, which is the thing the product exists not to be.              */

/* Reads as pressed rather than as selected. .btn.on elsewhere is a mode; this is
   a switch that spends money on the next send, so it is the accent itself. */
.sr-btn { display: inline-flex; align-items: center; gap: 6px; }
.sr-btn.on { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent); }
.sr-btn.on:hover { background: var(--accent-soft); }
.sr-btn:disabled { opacity: .5; }

.src { margin-block-start: var(--sp-3); }

/* A summary line, not a heading: "4 sources" is the whole reassurance, and it is
   readable without opening anything. */
.src-top {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 8px 3px 6px; border-radius: var(--rf);
  border: 1px solid var(--border); background: var(--surface-2);
  font-size: var(--fs-xs); font-weight: 550; color: var(--muted);
  transition: color var(--dur-1), border-color var(--dur-1);
}
.src-top:hover { color: var(--text); border-color: var(--border-strong); }
.src-caret { display: inline-block; transition: transform var(--dur-1); }
.src-caret.on { transform: rotate(90deg); }

/* The documents variant (S145b). Same chip, but it opens nothing — the file names ARE
   the detail — so it must not offer the hover affordance of something clickable. */
.src-static { cursor: default; }
.src-static:hover { color: var(--muted); border-color: var(--border); }

.src-files {
  list-style: none; margin: var(--sp-2) 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 2px;
  font-size: var(--fs-xs);
}
.src-files li { display: flex; gap: var(--sp-2); align-items: baseline; }

/* The name wins the space; a long file name truncates rather than pushing the count
   off the row, because the count is the part that says how much of the answer came
   from this document. */
.src-file {
  color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.src-passages { flex: none; color: var(--faint); font-variant-numeric: tabular-nums; }

/* An answer that finished with nothing in it (S146). Italic and muted so it never reads as
   the model's own words — it is the product explaining itself, not a reply. */
.msg-empty { font-style: italic; margin: 0; }

.src-list {
  list-style: none; margin: var(--sp-3) 0 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--sp-3);
  border-inline-start: 2px solid var(--border); padding-inline-start: var(--sp-4);
}

.src-item { display: flex; gap: var(--sp-3); align-items: flex-start; }

/* Tabular numerals and a fixed width so [1] and [10] start at the same place —
   these numbers are the ones the model cites inside its text, and a ragged column
   makes them read as decoration rather than as references. */
.src-rank {
  flex: none; min-inline-size: 18px; text-align: end;
  font-size: var(--fs-xs); font-weight: 700; color: var(--faint);
  font-variant-numeric: tabular-nums; padding-block-start: 1px;
}

.src-body { min-inline-size: 0; }
.src-title {
  display: block; font-size: var(--fs-sm); font-weight: 600; color: var(--accent);
  text-decoration: none; overflow-wrap: anywhere;
}
.src-title:hover { text-decoration: underline; }

.src-meta { display: flex; gap: var(--sp-2); font-size: var(--fs-micro); color: var(--faint); margin-block-start: 2px; }
.src-when::before { content: '\00b7'; margin-inline-end: var(--sp-2); }

/* Two lines. The snippet is what the model read, and showing all of it would make
   the citation list longer than most of the answers it sits under. */
.src-snip {
  margin: 4px 0 0; font-size: var(--fs-xs); color: var(--muted); line-height: 1.5;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* The legal line in the sidebar foot. Quiet by design and absent from the rail —
   58px cannot hold two words, and a reference nobody is looking for should not
   cost a tile. */
.nav-legal {
  display: flex; gap: 6px; justify-content: center; align-items: center;
  margin-block-start: var(--sp-3); font-size: var(--fs-xs); color: var(--muted-2, var(--muted));
}
.nav-legal a { color: inherit; opacity: .75; transition: opacity var(--dur-1); }
.nav-legal a:hover { opacity: 1; text-decoration: underline; }
[data-nav="collapsed"] .nav-legal { display: none; }

/* ===========================================================================
   LEGAL PAGES — terms, privacy
   ===========================================================================
   Long-form prose, which the product otherwise has none of. Everything else on
   the platform is a control, a list or a message; these are two thousand words
   somebody has to actually read, and the defaults that suit a dense admin table
   make them unreadable.

   Measure capped at 68ch. It is the one setting that decides whether a policy
   gets read: full-width text on a 1920px monitor is a line the eye loses its
   place on, and a customer who gives up on paragraph three has not agreed to
   anything in an informed way.                                                */
.legal { max-inline-size: 68ch; font-size: var(--fs-sm); line-height: 1.65; color: var(--text); }
.legal-lede { font-size: var(--fs-md); color: var(--muted); margin-block-end: var(--sp-6); }

.legal h2 {
  font-size: var(--fs-lg); letter-spacing: -.012em;
  margin-block: var(--sp-6) var(--sp-3); scroll-margin-block-start: var(--sp-4);
}
.legal p { margin-block-end: var(--sp-3); }
.legal ul { margin: 0 0 var(--sp-3); padding-inline-start: var(--sp-5); }
.legal li { margin-block-end: 6px; }
.legal a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }

/* The gaps only the client can fill: a legal entity, a jurisdiction, a refund
   window. Marked so they are findable by EYE — the alternative is re-reading the
   whole document twice looking for square brackets, which is how one gets
   shipped still saying [Company legal name]. */
.legal .fill {
  background: var(--warning-bg); color: var(--warning-fg);
  padding: 1px 5px; border-radius: var(--r1); font-weight: 500;
}

/* Three columns of prose. Scrolls in its own box rather than widening the page —
   the same rule the chat's markdown tables follow. */
.legal-tw { overflow-x: auto; margin-block: var(--sp-4) var(--sp-5); }
.legal-tw table { min-inline-size: 100%; font-size: var(--fs-xs); }
.legal-tw th, .legal-tw td {
  border: 1px solid var(--border); padding: 7px 10px; text-align: start; vertical-align: top;
}
.legal-tw th { background: var(--surface-3); font-weight: 600; }

.legal-foot {
  margin-block-start: var(--sp-6); padding-block-start: var(--sp-4);
  border-block-start: 1px solid var(--border); color: var(--muted);
}

/* --- C-01, how many answers: 1 / 2 / 4 ----------------------------------------
   The view selector in the composer dock, on the plain .seg above.

   Fixed-width digits in a fixed-width box, so pressing 1 then 4 does not shift the
   Send button beside it — a control that moves the button you are aiming at next
   is the reason people miss it.

   The word is dropped on a phone, not the digits. The dock is the tightest row in
   the product at that width, and three numbers next to a model picker survive
   losing their label better than the label survives losing them.               */
.vmode { display: inline-flex; align-items: center; gap: var(--sp-2); }
.vmode-lbl { font-size: var(--fs-xs); color: var(--muted); white-space: nowrap; }

.vmode .seg button, .vmode .seg a {
  min-inline-size: 26px; text-align: center; font-variant-numeric: tabular-nums;
}

/* Reachable, and visibly not one of the choices. It goes to the price list rather
   than arming anything, so it must not look pressable-then-pressed. */
.vmode .seg a { opacity: .55; }
.vmode .seg a:hover { opacity: 1; color: var(--accent); }

@media (max-width: 600px) { .vmode-lbl { display: none; } }

/* --- follow-up suggestions ----------------------------------------------------
   Chips under the newest finished answer. Pressing one sends it.

   Quiet on purpose, and for the opposite reason to the citations above. These are
   BUTTONS THAT SPEND CREDITS, sitting at the bottom of an answer somebody is
   still reading. Made prominent they become the obvious next thing to do, and a
   product that keeps offering a cheap-looking way to ask another question is one
   that bills you for a conversation you drifted into. So they read as an offer:
   outlined, muted text, no fill until the pointer is on one.

   Wrapping rather than scrolling. Three questions do not fit across a phone, and
   a horizontal scroller hides the third behind a gesture nobody performs.        */
.sug { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-block-start: var(--sp-3); }

.sug-chip {
  display: inline-flex; align-items: center; gap: 6px;
  max-inline-size: 100%; padding: 5px 12px 5px 9px;
  border: 1px solid var(--border); border-radius: var(--rf);
  background: transparent; color: var(--muted);
  font-size: var(--fs-xs); font-weight: 500; text-align: start;
  transition: background var(--dur-1), border-color var(--dur-1), color var(--dur-1);
}

.sug-chip:hover { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent); }
.sug-chip:disabled { opacity: .5; }

/* The arrow is the only thing that says "this sends"; without it a chip reads as
   a label. It never shrinks, so a long question eats the text and not the sign
   that the question is a button. */
.sug-chip > .gi { flex: none; inline-size: 14px; block-size: 14px; opacity: .7; }
.sug-chip:hover > .gi { opacity: 1; }

/* One line, cut with an ellipsis. The model is told to keep these under 90
   characters and mostly does; a chip that wrapped to three lines on a phone
   would be a paragraph with a border around it. The full text is in the title. */
.sug-chip > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* "Draw this with GPT Image 2" — offered when a chat model was asked for a picture
   and answered, correctly, with words. It sits in its own .sug row above the
   follow-ups and is drawn in the accent from the start rather than only on hover.

   That is the whole difference, and it is deliberate: the chips beside it continue
   a conversation, while this one starts a generation that costs roughly a hundred
   times as much. A control that spends real money should not be visually
   indistinguishable from one that does not, and a user pressing the wrong chip in a
   row of identical outlines finds out what it cost afterwards. */
.sug-chip.is-draw {
  border-color: var(--accent-line); background: var(--accent-soft); color: var(--accent);
}
.sug-chip.is-draw > .gi { opacity: 1; }

/* The same chip, for an account whose plan does not include drawing.

   Quieter, not disabled. A disabled control says no and offers nothing, and on a
   phone it cannot even be pressed to find out why — so this is a real link to the
   plans, it keeps its focus ring, and the padlock plus the muted colour is what
   stops it being mistaken for the live one.

   It stays in the neutral palette on purpose. The accent version above means "this
   will spend your credits", and wearing that colour for something that spends
   nothing would make the one signal that matters unreliable. */
.sug-chip.is-locked {
  border-color: var(--border); background: transparent; color: var(--muted);
  text-decoration: none;
}
.sug-chip.is-locked:hover { border-color: var(--accent-line); color: var(--accent); background: transparent; }
.sug-chip.is-locked > .gi { opacity: .55; }
.sug-chip.is-locked:hover > .gi { opacity: .8; }

/* "See plans", after the padlock. Smaller and underlined, so the chip reads as one
   sentence ending in a link rather than as two controls jammed together — there is
   only one place to press, and it is the whole thing. */
.sug-plan {
  padding-inline-start: 2px;
  font-size: var(--fs-micro); font-weight: 600;
  color: var(--accent); text-decoration: underline;
}

/* --- share this conversation -------------------------------------------------
   The dialog. It reuses .palette for the backdrop and .palette-box for the panel,
   like the prompt library, and overrides the same two properties for the same
   reason: the box is `overflow: hidden`, which is right for a command palette
   whose rows are one line each and wrong for anything with prose in it.

   One panel, two states — the link, or the confirmation of turning it off —
   swapped in place. See the prompt library's note above: a modal over a modal is
   two Escape handlers and two focus traps for a question with two answers.       */
.sh-box { inline-size: min(520px, 100%); overflow: visible; padding: var(--sp-4); }
.sh-top { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); margin-block-end: var(--sp-3); }
.sh-top b { font-size: var(--fs-lg); }
.sh-note { margin: 0; color: var(--muted); font-size: var(--fs-sm); }

/* What the link does, and — the half people get wrong — what it does not. */
.sh-rules { margin: 0 0 var(--sp-4); padding-inline-start: 18px; color: var(--muted); font-size: var(--fs-sm); display: grid; gap: 5px; }

.sh-span { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); flex-wrap: wrap; margin-block-end: var(--sp-4); }
.sh-span .lbl { font-size: var(--fs-sm); color: var(--muted); }

/* The field is readonly and selects itself on focus, so the Copy button is a
   convenience rather than the only way through — the clipboard API needs a secure
   context and a permission that can be refused. */
.sh-url { display: flex; gap: var(--sp-2); align-items: center; margin-block-end: var(--sp-3); }
.sh-in { flex: 1; min-inline-size: 0; padding: 8px 10px; border: 1px solid var(--border-strong); border-radius: var(--r2); background: var(--surface-2); color: var(--text); font-family: var(--mono); font-size: var(--fs-xs); }
.sh-in:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

.sh-facts { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 var(--sp-3); color: var(--faint); font-size: var(--fs-xs); }

/* The safety property of the whole feature, said out loud next to the button that
   changes it. Accent rather than warning: nothing has gone wrong — the link is
   doing exactly what it promised — and dressing it as a problem would train people
   to clear it without reading. */
.sh-new { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-3); padding: var(--sp-3); border: 1px solid var(--accent-line); background: var(--accent-soft); border-radius: var(--r2); margin-block-end: var(--sp-3); }
.sh-new b { display: block; font-size: var(--fs-sm); }
.sh-new p { margin: 3px 0 0; }
.sh-new .btn { flex: none; }

.sh-stop { padding: var(--sp-3); border: 1px solid var(--critical); background: var(--critical-bg); border-radius: var(--r2); }
.sh-stop p { margin: 0 0 var(--sp-3); font-size: var(--fs-sm); }

.sh-acts { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-2); }

/* A destructive action, so it is a quiet word rather than a red button — the red
   button belongs on the confirmation, where the decision is actually made. */
.sh-off { color: var(--critical); font-size: var(--fs-sm); text-decoration: underline; text-underline-offset: 2px; }

.sh-err { margin: var(--sp-3) 0 0; color: var(--critical); font-size: var(--fs-xs); }

/* --- the shared page ---------------------------------------------------------
   Served by Razor at /s/{token} with no React on it at all, so these classes are
   the whole of its layout. Deliberately NOT the workspace's thread styles: that
   thread is a live surface with hover actions, ratings and a composer under it,
   and inheriting it would leave a stranger hovering over controls that are not
   there.                                                                        */
.sc { max-inline-size: 760px; margin-inline: auto; }
.sc-head { margin-block-end: var(--sp-6); }
.sc-head h1 { font-size: var(--fs-3xl); line-height: 1.25; margin: 0 0 var(--sp-2); }
.sc-meta { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; margin: 0; font-size: var(--fs-xs); }

/* Said in words, not implied by the absence of a composer. Somebody who has used a
   chat product will look for the box to type in; being told is cheaper than
   discovering. */
.sc-ro { padding: 2px 8px; border-radius: var(--rf); background: var(--surface-3); color: var(--muted); font-size: var(--fs-micro); font-weight: 600; letter-spacing: .02em; text-transform: uppercase; }

.sc-turn { margin-block-end: var(--sp-6); }
.sc-who { font-size: var(--fs-micro); font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--faint); margin-block-end: var(--sp-2); }

/* The question, exactly as it was typed. No Markdown pass over it — an asterisk
   somebody meant literally is an asterisk. */
.sc-said { white-space: pre-wrap; word-break: break-word; padding: var(--sp-3) var(--sp-4); background: var(--surface-3); border-radius: var(--r3); font-size: var(--fs-md); }

.sc-answer { font-size: var(--fs-md); line-height: 1.65; }
.sc-answer > :first-child { margin-block-start: 0; }
.sc-answer > :last-child { margin-block-end: 0; }
.sc-answer p, .sc-answer ul, .sc-answer ol, .sc-answer blockquote { margin: 0 0 var(--sp-3); }
.sc-answer ul, .sc-answer ol { padding-inline-start: 22px; }
.sc-answer li { margin-block-end: 4px; }
.sc-answer h3, .sc-answer h4, .sc-answer h5, .sc-answer h6 { margin: var(--sp-4) 0 var(--sp-2); line-height: 1.3; }
.sc-answer h3 { font-size: var(--fs-xl); }
.sc-answer h4 { font-size: var(--fs-lg); }
.sc-answer h5, .sc-answer h6 { font-size: var(--fs-md); }
.sc-answer code { font-family: var(--mono); font-size: .92em; background: var(--surface-3); padding: 1px 5px; border-radius: var(--r1); }
.sc-answer pre { margin: 0 0 var(--sp-3); padding: var(--sp-3); background: var(--surface-3); border: 1px solid var(--border); border-radius: var(--r2); overflow-x: auto; }
.sc-answer pre code { background: none; padding: 0; font-size: var(--fs-xs); line-height: 1.55; }
.sc-answer blockquote { padding-inline-start: var(--sp-3); border-inline-start: 3px solid var(--border-strong); color: var(--muted); }
.sc-answer hr { border: 0; border-block-start: 1px solid var(--border); margin: var(--sp-5) 0; }
.sc-answer a { color: var(--accent); }

/* Scrolls inside its own box. A wide table that widens the page instead is how a
   shared transcript arrives on a phone scrolling sideways. */
.md-table { overflow-x: auto; margin: 0 0 var(--sp-3); }
.md-table table { border-collapse: collapse; font-size: var(--fs-sm); }
.md-table th, .md-table td { border: 1px solid var(--border); padding: 6px 10px; text-align: start; vertical-align: top; }
.md-table th { background: var(--surface-3); font-weight: 600; white-space: nowrap; }

/* A comparison stays a comparison. Drawn as columns where there is room and as
   stacked, labelled answers where there is not — the labels are what make the
   narrow version still legible, which is why every pane carries its model name
   rather than relying on the column it is in. */
.sc-panes { display: grid; gap: var(--sp-3); grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.sc-pane { padding: var(--sp-3); border: 1px solid var(--border); border-radius: var(--r3); background: var(--surface-2); min-inline-size: 0; }
.sc-model { font-size: var(--fs-xs); font-weight: 600; color: var(--accent); margin-block-end: var(--sp-2); }

/* Pictures on a shared page.

   Capped by HEIGHT rather than width, which is the whole trick. A generated image
   is usually square and an uploaded photograph is usually not, so a width cap
   makes a portrait shot taller than the screen and pushes the answer it belongs to
   off the bottom. Capping the height instead lets every shape settle to the same
   visual weight, and the width follows from it.

   No fixed aspect-ratio box: we do not store the dimensions, so reserving space
   would mean guessing, and a guess that is wrong leaves a grey band under every
   picture on the page. */
.sc-pics { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-block-end: var(--sp-3); }
.sc-pic { margin: 0; max-inline-size: 100%; }
.sc-pic img {
    display: block;
    max-inline-size: 100%;
    max-block-size: 420px;
    inline-size: auto;
    block-size: auto;
    border: 1px solid var(--border);
    border-radius: var(--r3);

    /* Under a transparent PNG - which is most logos, and a logo is the single most
       likely thing to be generated and then shared. Without it the picture is
       invisible against a light page in dark mode and vice versa. */
    background: var(--surface-3);
}

/* Inside a comparison column the ceiling comes down: four panes of 420px pictures
   is a page of images with the answers scrolled off it. */
.sc-pane .sc-pic img { max-block-size: 260px; }

.sc-pic figcaption { margin-block-start: 6px; font-size: var(--fs-micro); color: var(--faint); }

.sc-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4); flex-wrap: wrap; margin-block-start: var(--sp-8); padding-block-start: var(--sp-5); border-block-start: 1px solid var(--border); }
.sc-foot p { margin: 4px 0 0; font-size: var(--fs-sm); max-inline-size: 46ch; }

/* Three different sentences behind this, not one "not found" — see the page. */
.sc-gone { max-inline-size: 46ch; margin: var(--sp-9) auto; text-align: center; }
.sc-gone h1 { font-size: var(--fs-2xl); margin: 0 0 var(--sp-2); }
.sc-gone p { margin: 0 0 var(--sp-5); }

.wsmain { display: flex; flex-direction: column; overflow: hidden; background: var(--surface); min-inline-size: 0; }
/* min-block-size, not block-size: the bar still wraps on a very narrow window, and a fixed height
   would clip the second row. --wsbar-h is what the App Hub floats below, and it is right for every
   width at which the bar does not wrap — which measured identically at 1440, 1180, 900 and 700. */
.wsbar { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-2) var(--sp-4); border-block-end: 1px solid var(--border); flex-wrap: wrap; min-block-size: var(--wsbar-h); }

/* --- find in this conversation ----------------------------------------------
   A SECOND search box, and deliberately not the sidebar's. They answer different
   questions — "which conversation was that in" versus "where in this one did we
   say it" — and a single box that switched between them would answer the wrong
   one about half the time. The sidebar's reaches the server and searches every
   thread you own; this one never leaves the page.

   The highlight is painted with the CSS Custom Highlight API rather than by
   wrapping matches in <mark>. Answers are rendered markdown, so wrapping means
   splitting text nodes inside generated content: it fights React's ownership of
   the DOM, breaks a match that straddles an element boundary, and can put a tag
   in the middle of a code block. A Highlight is a set of Ranges the browser
   paints over the existing text — the DOM is never touched, so nothing can be
   corrupted by searching, and clearing it is one call. */
.tsearch { display: inline-flex; align-items: center; gap: 3px; }
.tsearch .input { inline-size: 172px; }
.tsearch-count {
  font-size: var(--fs-xs); color: var(--faint); font-variant-numeric: tabular-nums;
  min-inline-size: 52px; text-align: center; white-space: nowrap;
}
.tsearch-count.is-none { color: var(--critical); }

::highlight(bv-find)    { background: var(--find); color: var(--find-fg); }
/* The one you are ON is the accent, so "next" visibly moves rather than just
   scrolling a page of identical yellow. */
::highlight(bv-find-on) { background: var(--accent-fill); color: var(--on-accent); }

/* The fallback, for a browser without CSS.highlights: the MESSAGE containing the
   match is ringed. Coarser, and still answers "where is it" — which is the whole
   job. Never both: the ring is only added when the highlight could not be. */
.msg.is-hit .msg-body {
  box-shadow: 0 0 0 2px var(--accent-line); border-radius: var(--r2);
}

.thread { flex: 1; overflow-y: auto; padding: var(--sp-7) var(--sp-6); }
.thread-inner { max-inline-size: 700px; margin-inline: auto; display: flex; flex-direction: column; gap: var(--sp-7); }

.msg { display: flex; gap: var(--sp-3); }
.msg-av { inline-size: 24px; block-size: 24px; border-radius: var(--r1); flex-shrink: 0; display: grid; place-items: center; font-size: var(--fs-micro); font-weight: 700; background: var(--surface-3); color: var(--muted); }
.msg-av.ai { background: var(--accent-fill); color: var(--on-accent); }
.msg-body { flex: 1; min-inline-size: 0; }
.msg-head { display: flex; align-items: center; gap: var(--sp-2); margin-block-end: 7px; flex-wrap: wrap; font-size: var(--fs-xs); }
.msg-who { font-size: var(--fs-sm); font-weight: 650; }
.msg-text { font-size: var(--fs-md); line-height: 1.7; }
.msg-text p + p { margin-block-start: var(--sp-3); }
/* THE LINE BREAKS THEY TYPED (client, 5 September 2026).

   Shift+Enter puts a newline in the composer and the message carries it all the way to the
   database — but HTML collapses a newline in a text node to a space, so a question laid out on
   three lines arrived as one run-on sentence. Somebody who puts a question on separate lines
   meant the lines: they are the difference between a list of three things and one long thing.

   USER ONLY, deliberately. The assistant's text goes through Markdown, which has already turned
   blank lines into real paragraphs; pre-wrap on top of that would honour those newlines a second
   time and double every gap in every answer.

   overflow-wrap because pre-wrap alone will not break a long unbroken string - a pasted URL or
   token would otherwise push the bubble past the edge of the thread. */
.msg.user .msg-text { background: var(--surface-3); padding: var(--sp-3) var(--sp-4); border-radius: var(--r3); border-start-start-radius: var(--r1); white-space: pre-wrap; overflow-wrap: anywhere; }

.why { display: flex; gap: 8px; margin-block-end: var(--sp-3); padding: 8px 11px; border: 1px solid var(--accent-line); background: var(--accent-soft); border-radius: var(--r2); font-size: var(--fs-xs); color: var(--muted); line-height: 1.55; }
.why b { color: var(--accent); font-weight: 650; }
.why button { margin-inline-start: auto; color: var(--accent); font-size: var(--fs-xs); font-weight: 600; white-space: nowrap; }

.code { margin-block: var(--sp-4); border: 1px solid var(--border); border-radius: var(--r2); overflow: hidden; background: var(--surface-2); }
.code-bar { display: flex; align-items: center; gap: var(--sp-2); padding: 5px 10px; border-block-end: 1px solid var(--border); font-size: var(--fs-micro); color: var(--faint); font-family: var(--mono); }
.code pre { margin: 0; padding: var(--sp-3) var(--sp-4); overflow-x: auto; font-family: var(--mono); font-size: var(--fs-xs); line-height: 1.75; }
.k { color: var(--accent); } .cm { color: var(--faint); }

/* Text somebody typed or pasted, shown exactly as they left it.
   A bug report's steps are a numbered list with real line breaks, and an error message pasted out
   of a browser is one very long line with no spaces in it. `pre` keeps the first; the wrapping and
   the break-word keep the second from pushing the panel off the side of the screen. Body font, not
   mono: this is prose that happens to have line breaks, not code. */
.prewrap {
  margin: 0; white-space: pre-wrap; overflow-wrap: anywhere;
  font: inherit; font-size: var(--fs-sm); line-height: 1.65; color: var(--text);
}

.msg-acts { display: flex; gap: 1px; margin-block-start: var(--sp-3); flex-wrap: wrap; }
.act { padding: 4px 9px; border-radius: var(--r1); font-size: var(--fs-xs); color: var(--faint); transition: all var(--dur-1); }
.act:hover { background: var(--surface-3); color: var(--text); }

/* Rating. The pressed thumb takes the accent so "I already said this" is legible at a glance -
   without it a user rates the same answer twice and the client's quality signal counts it twice. */
.act-icon { display: inline-grid; place-items: center; padding: 5px 7px; }
.act-icon.is-on { color: var(--accent); background: var(--accent-soft); }
.act-sep { inline-size: 1px; align-self: stretch; margin-inline: 5px; background: var(--border); }

/* --- rendered model output -------------------------------------------------
   Every model on the platform answers in Markdown; these are the elements that
   arrive in practice. Spacing is deliberately tighter than a document's - a
   chat answer is read in a column beside a sidebar, not on a page. */
.msg-text > :first-child { margin-block-start: 0; }
.msg-text > :last-child { margin-block-end: 0; }
.msg-text h1, .msg-text h2, .msg-text h3, .msg-text h4 {
  margin-block: var(--sp-4) var(--sp-2); line-height: 1.35; font-weight: 650;
}
.msg-text h1 { font-size: var(--fs-lg); }
.msg-text h2 { font-size: var(--fs-md); }
.msg-text h3, .msg-text h4 { font-size: var(--fs-sm); color: var(--muted); }
.msg-text ul, .msg-text ol { margin-block: var(--sp-3); padding-inline-start: var(--sp-5); }
.msg-text li { margin-block: 3px; }
.msg-text li > p { margin: 0; }
.msg-text blockquote {
  margin-block: var(--sp-3); padding: 2px 0 2px var(--sp-3);
  border-inline-start: 2px solid var(--accent-line); color: var(--muted);
}
.msg-text hr { margin-block: var(--sp-4); border: 0; border-block-start: 1px solid var(--border); }
.msg-text a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
.code-inline {
  font-family: var(--mono); font-size: .9em; padding: 1px 5px;
  background: var(--surface-3); border-radius: var(--r1); word-break: break-word;
}

/* A wide table scrolls inside the message. Letting it widen the column pushes the composer off
   screen, which makes the whole page unusable rather than just the table. */
.md-tablewrap { overflow-x: auto; margin-block: var(--sp-3); }
.msg-text table { border-collapse: collapse; font-size: var(--fs-sm); min-inline-size: 100%; }
.msg-text th, .msg-text td {
  padding: 6px 11px; border: 1px solid var(--border); text-align: start; vertical-align: top;
}
.msg-text th { background: var(--surface-2); font-weight: 650; white-space: nowrap; }

/* --- syntax colours --------------------------------------------------------
   Drawn from the theme's own tokens rather than importing a highlight.js theme:
   a stylesheet built for a white page puts near-black on our dark surface, and
   the contrast failure only shows up after someone switches theme. */
.hljs-comment, .hljs-quote { color: var(--faint); font-style: italic; }
.hljs-keyword, .hljs-selector-tag, .hljs-literal, .hljs-section, .hljs-doctag { color: var(--accent); }
.hljs-string, .hljs-regexp, .hljs-addition { color: var(--ok-fg); }
.hljs-number, .hljs-symbol, .hljs-bullet { color: var(--warning-fg); }
.hljs-title, .hljs-title.function_, .hljs-name { color: var(--text); font-weight: 600; }
.hljs-attr, .hljs-attribute, .hljs-variable, .hljs-template-variable { color: var(--muted); }
.hljs-built_in, .hljs-class .hljs-title, .hljs-type { color: var(--accent); opacity: .85; }
.hljs-meta, .hljs-deletion { color: var(--faint); }
.hljs-emphasis { font-style: italic; }
.hljs-strong { font-weight: 700; }

.caret { display: inline-block; inline-size: 6px; block-size: 13px; background: var(--accent); border-radius: 1px; vertical-align: -1px; margin-inline-start: 2px; animation: blink 1.05s step-end infinite; }
@keyframes blink { 50% { opacity: 0 } }

/* --- waiting for the first token -------------------------------------------
   The dots carry the "still working" signal and the words carry what it is
   working ON. Both are needed: motion alone does not distinguish "picking a
   model" from "the model is answering", and a static label does not
   distinguish either of them from a stalled request.

   aria-hidden on the dots - a screen reader gets the sentence, which is the
   part that means something. Three animated bullets are noise. */
.thinking { font-size: var(--fs-sm); min-block-size: 22px; }
/* The row is the reading order the phrase is written for: what it is doing, which model is
   doing it, then the dots that say it has not stopped. */
.thinking-run { display: flex; align-items: center; gap: 9px; min-block-size: 22px; }

/* Sized to the cap height of the word beside it so the pair reads as one line rather than as
   an icon with a caption. Fades in with the word it belongs to. */
.thinking-ico { display: inline-flex; color: var(--accent); animation: phase-in .28s ease-out both; }
.thinking-ico svg { inline-size: 16px; block-size: 16px; display: block; }

/* The model name is context, not the message - it sits back a step so the rotating phrase stays
   the thing the eye lands on. */
.thinking-model {
  font-size: var(--fs-micro); color: var(--faint); padding-inline: 6px;
  border-inline-start: 1px solid var(--line);
}

@keyframes phase-in { from { opacity: 0; transform: translateY(2px) } to { opacity: 1; transform: none } }

.thinking-dots { display: inline-flex; gap: 4px; align-items: center; }
.thinking-dots i {
  inline-size: 6px; block-size: 6px; border-radius: var(--rf);
  background: var(--accent); animation: think 1.15s ease-in-out infinite;
}
.thinking-dots i:nth-child(2) { animation-delay: .16s; }
.thinking-dots i:nth-child(3) { animation-delay: .32s; }
@keyframes think {
  0%, 75%, 100% { transform: translateY(0); opacity: .3; }
  35%           { transform: translateY(-4px); opacity: 1; }
}

/* A light sweeping across the words. Falls back to a legible static gradient
   when the animation is off, which is why the mid-stop is the body text colour
   rather than something only readable while it moves. */
.thinking-word {
  background: linear-gradient(90deg, var(--faint) 20%, var(--text) 45%, var(--faint) 70%);
  background-size: 220% 100%;
  -webkit-background-clip: text; background-clip: text;
  color: transparent; animation: shimmer 2.1s linear infinite, phase-in .28s ease-out both;
  white-space: nowrap;
}
@keyframes shimmer { from { background-position: 220% 0 } to { background-position: -20% 0 } }

/* The elapsed count, next to a word that is now standing still.

   No shimmer and no entrance, on purpose: this is the only part of the line that changes on a
   timer, and animating something that redraws every second is how the label ends up flashing
   instead of ticking. Tabular figures so 9s becoming 10s does not shift the phrase beside it, and
   a fixed minimum width so the same thing is true of 99s becoming 100s. */
.thinking-secs {
  font-size: var(--fs-micro); color: var(--faint);
  font-variant-numeric: tabular-nums; min-inline-size: 3ch;
}

/* The same line inside a compare pane, which is a column and not a page.

   Two changes only. The gap closes up, because a pane at four-across on a laptop is about 300px
   and the phrase has to share it. And the run is allowed to wrap, so on the narrowest pane the
   dots drop below the words instead of pushing them out of the column - a phrase clipped at
   "Composing the ans" is worse than a phrase on two lines. The words themselves stay nowrap so
   they never break mid-phrase. */
.thinking.compact .thinking-run { gap: 7px; flex-wrap: wrap; }

/* --- a message arriving -----------------------------------------------------
   Short and small on purpose. This fires on every turn, and an entrance anyone
   notices twice is an entrance that is in the way by the tenth. Keyed on
   Message.key, so it plays once per row and not again when the server's ids
   replace the invented ones mid-answer. */
/*
   ---- `backwards`, NOT `both`, AND THE DIFFERENCE IS A REAL BUG (client, 26 Aug) --------------

   `both` holds the animation's fill AFTER it has finished, so every message kept a transform on it
   for the rest of the session. A transform creates a STACKING CONTEXT — so each message became its
   own, the Download menu's `z-index: 30` was trapped inside the message it belonged to, and every
   LATER message painted straight over it. The client found it by opening Download on his own first
   message: the answer below drew through the menu.

   Nothing about the entrance changes. There is no animation-delay here, so `backwards` and `both`
   are identical while it runs; they differ only in what is left behind, and what was being left
   behind was a stacking context nobody wanted.

   Anything positioned inside a message would have hit this — this menu was simply the first. */
.msg { animation: msg-in .24s cubic-bezier(.2,.8,.3,1) backwards; }
@keyframes msg-in { from { opacity: 0; transform: translateY(7px); } }

/* AND A BELT FOR THE BRACES: while a menu is open, lift its message above the ones after it.
   The rule above is the fix; this covers the 240ms in which a NEW message is mid-entrance and is
   legitimately a stacking context — a menu left open while an answer streams in would otherwise
   spend a quarter of a second underneath it. */
.msg:has(details[open]) { position: relative; z-index: 3; }

/* --- the credit balance changing --------------------------------------------
   The one number on screen that moves without the user touching it. Unmarked,
   a balance that silently drops looks like a billing error. */
[data-bv-credits].bv-tick { animation: tick .5s ease-out; }
@keyframes tick {
  0%   { transform: scale(1); color: var(--accent); }
  35%  { transform: scale(1.16); color: var(--accent); }
  100% { transform: scale(1); }
}

/* --- the conversation list, before it has loaded ----------------------------
   Rows the right shape and the right number, rather than a spinner. The list
   does not jump when the real titles land, which is the actual complaint people
   have about spinners. */
.sk { display: block; block-size: 11px; border-radius: var(--rf); background: var(--surface-3); overflow: hidden; position: relative; }
.sk::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, var(--surface-4), transparent);
  transform: translateX(-100%); animation: sweep 1.35s ease-in-out infinite;
}
@keyframes sweep { to { transform: translateX(100%) } }
.sk-row { padding: 8px 9px; }

.dock { padding: var(--sp-3) var(--sp-6) var(--sp-5); border-block-start: 1px solid var(--border); }
.dock-inner { max-inline-size: 700px; margin-inline: auto; }
.dock-box { border: 1px solid var(--border-strong); border-radius: var(--r3); background: var(--surface); overflow: hidden; }
.dock-box:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.dock-box textarea { inline-size: 100%; border: 0; background: none; resize: none; padding: var(--sp-3) var(--sp-4) var(--sp-2); font-size: var(--fs-md); min-block-size: 48px; }
.dock-box textarea:focus { outline: none; }
.dock-foot { display: flex; align-items: center; gap: var(--sp-1); padding: 0 var(--sp-2) var(--sp-2); flex-wrap: wrap; }

/* --- attachments ------------------------------------------------------------
   Pictures waiting to go with the message being written, inside the composer's
   own border so they read as part of it rather than as a separate tray.

   The strip is only in the DOM when there is something in it, so there is no
   empty band above the box on the 99% of messages that carry nothing. */
.attach-strip {
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-3) 0;
}
.attach-chip { position: relative; flex-shrink: 0; }
.attach-chip img {
  inline-size: 56px; block-size: 56px; object-fit: cover; display: block;
  border-radius: var(--r2); border: 1px solid var(--border-strong);
  background: var(--surface-2);
}

/* Always visible, never hover-only. The chip is 56px on a touch screen where
   there is no hover at all, and a remove button you have to discover is a
   picture the user cannot take back. */
.attach-x {
  position: absolute; inset-block-start: -6px; inset-inline-end: -6px;
  inline-size: 18px; block-size: 18px; border-radius: var(--rf);
  display: grid; place-items: center; line-height: 1; font-size: 13px;
  border: 1px solid var(--border-strong); background: var(--surface);
  color: var(--muted); cursor: pointer; padding: 0;
}
/* --critical and --critical-bg, not --danger: --danger has never been declared
   anywhere in this file, and the one rule that reached for it was silently
   painting a hard-coded fallback that answered to neither theme. */
.attach-x:hover {
  background: var(--critical-bg); border-color: var(--critical); color: var(--critical);
}

.attach-error {
  display: flex; align-items: center; gap: var(--sp-2); inline-size: 100%;
  font-size: var(--fs-xs); color: var(--critical);
}

/* The same pictures once the message has been sent. Larger, because now they
   are content being read rather than chips being managed, and capped so a
   portrait photograph cannot push the answer below the fold. */
.msg-attach { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin-block-end: var(--sp-3); }
.msg-attach img {
  max-inline-size: 220px; max-block-size: 220px; display: block;
  border-radius: var(--r2); border: 1px solid var(--border);
  background: var(--surface-2);
}
.msg-attach a:hover img { border-color: var(--accent); }

/* A picture the model DREW, not one the user attached.
   It is the answer rather than a note beside a question, so it is drawn at the width an answer
   gets rather than at thumbnail size - a 220px square under "draw me a poster" reads as a
   preview of something else, and the whole point of the feature is the picture.
   Capped by height as well as width so a portrait shape cannot push the composer off a laptop
   screen, and the aspect ratio is left to the image itself. */
.msg-attach.is-generated img {
  max-inline-size: min(100%, 520px); max-block-size: 520px;
  inline-size: auto; block-size: auto;
}
.msg-attach figure { margin: 0; display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
.msg-attach figcaption { font-size: var(--fs-xs); color: var(--faint); }

/* A generated video or piece of audio (S117).

   Its own block rather than a variant of .msg-attach, because the two disagree about the one
   thing that block is for. Pictures sit in a wrapping flex row and each get a fixed cap: several
   thumbnails side by side is exactly right for them. A clip is the answer, there is only ever one
   of it, and a <video> element beside another <video> at 220px would be two things nobody can
   watch. So this stacks, and fills the width an answer gets. */
.msg-media { display: flex; flex-direction: column; gap: var(--sp-3); margin-block-end: var(--sp-3); }
.msg-media figure { margin: 0; display: flex; flex-direction: column; gap: 6px; align-items: flex-start; inline-size: 100%; }

.msg-media video {
  inline-size: min(100%, 520px);
  max-block-size: 520px;
  block-size: auto;
  display: block;
  border-radius: var(--r2);
  border: 1px solid var(--border);

  /* Black, not the surface colour, and not theme-aware. A video's own letterboxing is black on
     every platform, so a grey backing shows as two mismatched bars down the sides of any clip
     whose aspect ratio is not exactly the box's. */
  background: #000;
}

/* Full width up to the same cap. An audio player is a control strip - it has no aspect ratio to
   preserve and looks broken when it is narrower than its own buttons. */
.msg-media audio { inline-size: min(100%, 520px); display: block; }

.msg-media figcaption {
  font-size: var(--fs-xs); color: var(--faint);
  display: flex; align-items: center; gap: var(--sp-2);
  inline-size: min(100%, 520px);
}
.msg-media-name { flex: 1; min-inline-size: 0; }

/* THE NEWS PORTAL'S MOUNT (18.19, finished 18.30).

   One rule, and it is a container rather than a style: the portal is a vendored bundle that brings
   its own entire stylesheet, scoped under .rndp. Anything this product said about its insides would
   be a second opinion fighting the first, and would have to be re-checked at every version of it.

   All this does is give it the height of the screen to grow into — the same job the rooms mount
   does for the rooms island, and for the same reason. */
.news-mount { flex: 1; min-block-size: 0; display: flex; }
.news-mount > .rndp { flex: 1; min-inline-size: 0; overflow: auto; }

/* Dictation (L-06).

   The listening state has to be unmistakable, because the microphone is open and the user is the
   only one who can close it. Colour alone would not do it - the button is small and sits in a row
   of other small quiet buttons - so it also pulses. */
.btn.is-listening { color: var(--critical); background: var(--critical-bg); }

.btn.is-listening svg circle { animation: mic-pulse 1.3s ease-in-out infinite; }

@keyframes mic-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: .25; }
}

/* Somebody who has asked for less motion still needs to know the microphone is live, so the dot
   stays on rather than the animation simply being removed. */
@media (prefers-reduced-motion: reduce) {
  .btn.is-listening svg circle { animation: none; }
}

.dictate-problem { font-size: var(--fs-xs); color: var(--critical); align-self: center; }

/* RECORDING, WHICH IS THE SAME BUTTON WITH A WAIT IN IT (18.27).

   The transcribing state is deliberately NOT the listening one. Listening is urgent and red because
   the microphone is open and only the user can close it; transcribing is the opposite - the
   microphone is already shut and there is nothing to do but wait - so it goes quiet and faded
   instead. Reusing the red would have kept a "you are being recorded" signal on screen after the
   recording had stopped. */
.btn.is-transcribing { color: var(--muted); opacity: .7; }

.btn.is-transcribing svg { animation: mic-thinking 1.6s ease-in-out infinite; }

@keyframes mic-thinking {
  0%, 100% { opacity: 1; }
  50%      { opacity: .4; }
}

@media (prefers-reduced-motion: reduce) {
  .btn.is-transcribing svg { animation: none; }
}

/* The seconds, climbing towards the cap. Tabular so the row does not shuffle once per second - the
   button beside it would otherwise step sideways when the width of a digit changed. */
/* HOW LONG IS LEFT, in the last seconds before the cap. Not a stopwatch - it appears only when it
   has something to warn about, so the toolbar does not reflow on every recording. */
.dictate-elapsed {
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
  color: var(--critical);
  align-self: center;
  white-space: nowrap;
}

/* A LINE OF TEXT WHERE A CONTROL WOULD HAVE BEEN.

   Two places use it, and for the same reason: the Attach button on an image model, and the
   microphone in a browser that cannot listen. Both were ABSENT before, which is correct as far as
   it goes — a greyed-out control invites the press that does nothing — but absence explains
   nothing, and the client filed both as missing features rather than as unsupported ones.

   Quiet, not a warning: nothing is wrong, the reader is simply somewhere this does not apply.
   Wrapped so a long sentence does not push the send button off the row. */
.composer-note {
  font-size: var(--fs-xs); color: var(--faint); line-height: 1.4;
  align-self: center; max-inline-size: 46ch; text-wrap: pretty;
}

/* A document in the composer. Deliberately NOT a 56px square like a picture chip:
   there is nothing to look at, and the two facts that matter - how much of it there
   is, and whether all of it is being sent - are sentences. A card that is wide and
   short reads as a file; a square with an icon in it reads as a thumbnail that
   failed to load. */
.attach-doc {
  position: relative; flex-shrink: 0;
  display: flex; align-items: flex-start; gap: var(--sp-2);
  max-inline-size: 260px; padding: 8px 10px;
  border-radius: var(--r2); border: 1px solid var(--border-strong);
  background: var(--surface-2);
}
.attach-doc-icon { color: var(--muted); flex-shrink: 0; margin-block-start: 1px; }
.attach-doc-body { display: flex; flex-direction: column; gap: 1px; min-inline-size: 0; }

/* Ellipsis rather than wrap. A file name is often long and rarely interesting past
   its start, and a chip that grows to three lines pushes the message box down. */
.attach-doc-name {
  font-size: var(--fs-xs); font-weight: 550; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.attach-doc-meta { font-size: 11px; color: var(--faint); }

/* Warning colour, not faint. This is the line that stops somebody trusting an answer
   about a document the model only half received. */
.attach-doc-warn { font-size: 11px; color: var(--warning-fg); font-weight: 550; }

/* Documents on a message that has been sent. A row of quiet links above the words,
   so the reader can see what the question was asked ABOUT before reading the answer. */
.msg-docs { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin-block-end: var(--sp-3); }
.msg-doc {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px; border-radius: var(--rf);
  border: 1px solid var(--border); background: var(--surface-2);
  font-size: var(--fs-xs); color: var(--muted); text-decoration: none;
  max-inline-size: 260px;
}
.msg-doc:hover { border-color: var(--accent); color: var(--text); }
.msg-doc-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.msg-doc-warn {
  font-size: 10px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--warning-fg); flex-shrink: 0;
}
.dock-meta { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); margin-block-start: 7px; font-size: var(--fs-xs); color: var(--faint); flex-wrap: wrap; }
.kbd { font-family: var(--mono); font-size: 10px; padding: 1px 5px; border: 1px solid var(--border-strong); border-radius: 4px; background: var(--surface-2); }

/* NOT TOLD TO A DEVICE WITH NO KEYBOARD.

   At 390px this row read "Enter to send · Shift + Enter for a new line · Ctrl + K to jump · / for
   saved prompts" — two lines of a small screen spent on keys a phone does not have.

   `pointer: coarse` and not a width, deliberately. The question is not "is the window narrow" — a
   half-width desktop window still has a Ctrl key, and a 900px tablet still has none. What decides it
   is whether the primary input is a finger, which is exactly what this query asks. */
@media (pointer: coarse) { .dock-keys { display: none; } }

/* ============================================================================================
   THE APP HUB (client, 26 Aug; direction D, chosen from four mockups).

   His own screenshot: the connected apps open from a side panel, you browse one, and what you find
   goes into the message you are writing.

   ---- IT FLOATS. IT IS NOT A COLUMN. ---------------------------------------------------------

   The first build made it a fourth track in the .ws grid, which was cheap — the grid already
   animated — and wrong. A column IS the window; this is summoned over it and goes away again, and
   the client picked the floating shape over the flush one for exactly that reason. So it is
   absolutely positioned inside .ws, takes no track, and resizes nothing when it opens.

   ---- WHY THE TOP INSET CLEARS THE BAR -------------------------------------------------------

   At 14px the panel's own top edge cut horizontally through the "Your apps" button it had been
   opened from — half a button showing above a floating card, which reads as a rendering fault.
   A full-height panel never has this problem because the bar is simply behind it. A floating one
   has a top edge, and a top edge has to land somewhere deliberate: below the bar.

   ---- ONE PANEL FOR THE WHOLE JOURNEY --------------------------------------------------------

   It was built as two cards first — opening an app pushed a second card over the first, leaving
   the first peeking down the leading edge as the way back. That is what iOS does, and it was
   wrong here. The client's words on seeing it: "modal over modal is not looking good, it should be
   in same modal for this whole process." He is right, and the reason is that a floating panel is
   ALREADY a modal: stacking a second one inside it means two dialogs on screen to answer one
   question, and the peeking strip that was supposed to read as "where you came from" reads as a
   panel that failed to close.

   So the box never moves. Its contents do — sliding in from the trailing side going deeper and
   from the leading side coming back, which is the same forward-and-back cue without a second
   surface. The header carries the way out.
   ========================================================================================== */

/* Dims the conversation. Its own element rather than the layout's #bv-scrim, which belongs to the
   phone navigation and is driven by a different script. */
.hub-scrim {
  position: absolute; inset: 0; z-index: 118;
  background: rgba(8, 10, 20, .42);
  opacity: 0; pointer-events: none;
  transition: opacity 240ms var(--ease-io);
}
[data-hub="open"] .hub-scrim { opacity: 1; pointer-events: auto; }

.hub {
  position: absolute;
  inset-block: calc(var(--wsbar-h) + 14px) 14px;
  inset-inline-end: 16px;
  z-index: 119;

  /* Never wider than the window can hold. Below about 360px the min() takes over and the panel
     becomes a near-full-width sheet without needing a breakpoint of its own. */
  inline-size: min(332px, calc(100vw - 32px));

  display: flex; flex-direction: column; overflow: hidden;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 20px; box-shadow: var(--sh-3);

  transform: translateX(calc(100% + 18px));
  transition: transform 380ms var(--ease-io);
  will-change: transform;
  pointer-events: none;
}

[data-hub="open"] .hub { transform: none; pointer-events: auto; }
[data-hub="closed"] .hub { visibility: hidden; }

@media (prefers-reduced-motion: reduce) {
  .hub, .hub-scrim { transition-duration: 1ms; }
}

/* WHAT MOVES INSTEAD OF THE PANEL.

   Going deeper slides in from the trailing side, coming back from the leading side — the same
   direction a second card would have travelled, carried by the contents rather than by a surface.
   It is keyed on the level in the island, so React rebuilds the element and the animation replays
   on every step. 14px, because this is a cue about direction and not a journey: any further and
   the list appears to arrive from somewhere off screen, which it did not. */
@media (prefers-reduced-motion: no-preference) {
  .hub-view { animation: hub-fwd 220ms var(--ease-io); }
  .hub-view.is-back { animation-name: hub-bwd; }
}

@keyframes hub-fwd {
  from { opacity: 0; transform: translateX(14px); }
  to   { opacity: 1; transform: none; }
}

@keyframes hub-bwd {
  from { opacity: 0; transform: translateX(-14px); }
  to   { opacity: 1; transform: none; }
}

.hub-bar {
  flex: none; block-size: 50px; display: flex; align-items: center; gap: var(--sp-2);
  padding: 0 var(--sp-4); border-block-end: 1px solid var(--border);
}
.hub-bar h3 { font-size: var(--fs-sm); font-weight: 650; }

.hub-scroll { flex: 1; overflow-y: auto; padding: var(--sp-2); scrollbar-width: thin; }

/* One row per connected app. Pressing it pushes that app's card over this one. */
.hub-app {
  inline-size: 100%; display: flex; align-items: center; gap: var(--sp-3);
  padding: 10px; border-radius: 12px; text-align: start;
  color: var(--text); background: none; border: 0; font: inherit;
}
.hub-app:hover { background: var(--surface-3); }
.hub-app:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.hub-app-txt { display: grid; gap: 1px; min-inline-size: 0; flex: 1; }
.hub-app-txt b { font-size: var(--fs-sm); font-weight: 600; }
.hub-app-txt small,
.hub-app-txt b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hub-app-txt small { font-size: var(--fs-xs); color: var(--faint); }

/* The vendor's real mark, in a tinted tile. The first pass drew geometric glyphs to avoid shipping
   other people's brand guidelines; on screen they read as placeholders, and the marks were already
   in the product (_AppIcon.cshtml) for the Apps screen. */
.hub-ico {
  flex: none; inline-size: 34px; block-size: 34px; padding: 7px;
  border-radius: 10px; background: var(--surface-3);
  display: grid; place-items: center;
}
.hub-ico svg { inline-size: 100%; block-size: 100%; display: block; }

/* Notion's and GitHub's marks are monochrome and must follow the page's ink — the two whose own
   guidelines both allow and require that. */
.hub-ico.is-ink { color: var(--ink); }

.hub-chev { flex: none; color: var(--faint); font-size: var(--fs-sm); }

.hub-back {
  display: inline-flex; align-items: center; gap: 5px; border: 0; background: none; padding: 0;
  color: var(--accent); font: inherit; font-size: var(--fs-xs); font-weight: 600;
}
.hub-back:hover { text-decoration: underline; }
.hub-back:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

.hub-items { display: flex; flex-direction: column; gap: 1px; }

.hub-item {
  inline-size: 100%; display: flex; align-items: center; gap: var(--sp-2);
  padding: 9px 10px; border-radius: 11px; text-align: start;
  color: var(--text); background: none; border: 0; font: inherit; font-size: var(--fs-xs);
}
.hub-item:hover { background: var(--surface-3); }
.hub-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.hub-item-name { flex: 1; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hub-item-meta { flex: none; color: var(--faint); font-variant-numeric: tabular-nums; }

.hub-fi { flex: none; inline-size: 14px; color: var(--faint); font-size: var(--fs-xs); }

/* A folder is a place and a file is a thing, and pressing them does two different things — one
   descends, the other writes into your message and closes the panel. Two rows that look identical
   and behave differently is the kind of surprise that makes people stop trusting a panel. */
.hub-item.is-folder .hub-item-name { font-weight: 600; }
.hub-item.is-folder .hub-fi { color: var(--accent); }

/* There was a per-row stagger here — rows arriving in sequence rather than all at once. It went
   with the second card. The view itself now slides on every step, and a staggered list riding a
   sliding container is two animations doing one job: the rows appear to swim inside a box that is
   also moving. One motion per action. */

.hub-note { padding: var(--sp-3) 10px; font-size: var(--fs-xs); color: var(--faint); line-height: 1.55; }
.hub-note.is-bad { color: var(--critical); }

/* Nothing connected. The client asked for this state by name: the button that opens this panel is
   present whether or not anything is connected, because it is the way IN — so the panel has to be
   able to invite somebody rather than apologise to them. */
.hub-zero { display: grid; gap: var(--sp-3); justify-items: start; padding: var(--sp-4) 10px; }
.hub-zero p { font-size: var(--fs-xs); color: var(--muted); line-height: 1.6; margin: 0; }

.insp { border-inline-start: 1px solid var(--border); background: var(--surface-2); display: flex; flex-direction: column; overflow: hidden; min-inline-size: 0; }
[data-insp="closed"] .insp { visibility: hidden; border-inline-start-width: 0; }
.insp-bar { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-3) var(--sp-4); border-block-end: 1px solid var(--border); }
.insp-bar h3 { font-size: var(--fs-sm); }
.insp-scroll { flex: 1; overflow-y: auto; padding: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-6); }
.insp-grp { display: flex; flex-direction: column; gap: var(--sp-3); }
.ctl { display: flex; align-items: center; gap: var(--sp-3); font-size: var(--fs-sm); }
.ctl-name { flex: 1; min-inline-size: 0; }
.ctl-name small { display: block; font-size: var(--fs-xs); color: var(--faint); line-height: 1.4; }
.range { inline-size: 100%; accent-color: var(--accent); block-size: 3px; }
.range-row { display: flex; align-items: center; justify-content: space-between; font-size: var(--fs-xs); color: var(--faint); }
.sys-box { inline-size: 100%; border: 1px solid var(--border-strong); border-radius: var(--r2); background: var(--surface); padding: 8px 10px; font-size: var(--fs-xs); resize: vertical; min-block-size: 62px; line-height: 1.55; color: var(--muted); }
.sys-box:focus { outline: none; border-color: var(--accent); }
.est { border: 1px solid var(--border); border-radius: var(--r2); background: var(--surface); padding: var(--sp-3); display: flex; flex-direction: column; gap: 7px; }
.est-row { display: flex; justify-content: space-between; font-size: var(--fs-xs); }
.est-row.total { border-block-start: 1px solid var(--border); padding-block-start: 7px; font-weight: 650; font-size: var(--fs-sm); }

/* Below 1240px the inspector stops being a column and becomes a right-hand
   drawer — it does NOT disappear. Run settings are one of the things that make
   this product worth using, and 1240 CSS px is an ordinary 13" laptop. */
@media (max-width: 1240px) {
  :root { --insp-w: 0px; }
  /* the inspector has left the flow, so the grid must lose its track too —
     otherwise the thread inherits a 0px column and renders blank */
  .ws { grid-template-columns: var(--list-w) 1fr; }
  .insp {
    position: absolute; inset-block: 0; inset-inline-end: 0; z-index: 116;
    inline-size: min(88vw, 300px); box-shadow: var(--sh-3);
    visibility: visible; border-inline-start-width: 1px;
    transform: translateX(102%); transition: transform var(--dur-2) var(--ease-io);
  }
  [data-insp="open"] .insp { transform: none; }
  [data-insp="closed"] .insp { visibility: hidden; }

  /*
     NOTHING HERE FOR THE APP HUB, AND THAT IS THE POINT OF THE SHAPE HE CHOSE.

     Every other panel needs a rule in this block because each is a grid COLUMN that has to become
     an overlay when the window gets tight. The Hub is already an overlay at every width — it never
     took a track — so it needs no breakpoint, and its own min() keeps it inside a narrow window.

     It does still cover the run-settings drawer when both are open, which is correct: it is above
     it in the stacking order and it has a scrim, so there is no sliver of the wrong panel showing.
  */
}
@media (max-width: 900px) {
  /* The conversation list becomes a drawer rather than disappearing — on a
     phone it is the only way to switch chats, so it must stay reachable. */
  :root { --list-w: 0px; }
  .ws { grid-template-columns: 1fr; }   /* both side panels are overlays now */
  .wslist {
    position: absolute; inset-block: 0; inset-inline-start: 0; z-index: 115;
    inline-size: min(80vw, 264px); box-shadow: var(--sh-3);
    visibility: visible; border-inline-end-width: 1px;
    transform: translateX(-102%); transition: transform var(--dur-2) var(--ease-io);
  }
  [data-list="open"] .wslist { transform: none; }
  [data-list="collapsed"] .wslist { visibility: hidden; }
  /* The dismiss and the way back to it are declared once, above, and apply at every
     width — the list is collapsible on the desktop too now (D-241). The warning that
     used to sit here still holds and is worth keeping: a panel with a close button
     and no way back is a trap, and the two controls have to move together. */
  .wsbar { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .wsbar::-webkit-scrollbar { display: none; }
  .wsbar > * { flex-shrink: 0; }
  .thread { padding: var(--sp-5) var(--sp-4); }
  .dock { padding: var(--sp-3) var(--sp-4) var(--sp-4); }
}

/* ---------------------------------------------------------------------------
   3. COMPARE
   --------------------------------------------------------------------------- */
/* A comparison is a CARD INSIDE THE THREAD, not a page.
   The template shipped these classes for a standalone /compare screen - `flex: 1` filling a
   viewport, panes scrolling independently, a summary pinned to the bottom. Compare is a mode of
   the workspace instead (D-235), so the same names now describe a block that sits in the
   conversation between two ordinary messages: it grows with its content, the page scrolls, and
   nothing inside it has a scrollbar of its own. Two answers of different lengths make an uneven
   bottom edge, and that is the honest picture - equalising them means clipping one. */
.cmp { border: 1px solid var(--border); border-radius: var(--r2); background: var(--surface); overflow: hidden; margin-block: var(--sp-4); }
.cmp-q { padding: var(--sp-3) var(--sp-4); border-block-end: 1px solid var(--border); background: var(--surface-2); display: flex; gap: var(--sp-3); align-items: flex-start; flex-wrap: wrap; }
.cmp-q p { flex: 1; min-inline-size: 0; font-size: var(--fs-md); }

/* The comparison's own controls, at the end of the prompt row. Not in the pane bar: this acts on
   every pane at once, and a control repeated in four columns reads as four separate switches. */
.cmp-tools { display: flex; align-items: center; gap: var(--sp-2); flex: 0 0 auto; }
/* Painted in the highlight's own colours — a legend that names a colour without wearing it is a
   sentence the reader has to take on trust. */
.cmp-legend { font-size: var(--fs-xs); background: var(--diff); color: var(--diff-fg); padding: 1px 6px; border-radius: var(--rf); white-space: nowrap; }

/* The band the browser paints over a passage no other pane covers. Background and colour only —
   ::highlight takes nothing else, which is the constraint that keeps this from becoming decoration. */
::highlight(bv-diff) { background: var(--diff); color: var(--diff-fg); }

/* The count in the footer, when there is one to make. */
.cmp-only { color: var(--text); }

/* The column count is a data attribute rather than a class per number, so a plan that allows six
   needs no new rule. Six across is unreadable at any laptop width - it wraps to two rows of three,
   which the grid does by itself once the count is set. */
.panes { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.panes[data-cols="3"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* Borders BETWEEN panes only, drawn from each pane's leading edge, so the count can change without
   a rule that knows what the last column is. The first of every row is exempt, which is what
   `nth-child` cannot express for a variable column count - hence the outline-style trick below. */
.pane { display: flex; flex-direction: column; min-inline-size: 0; box-shadow: -1px 0 0 var(--border); }
.pane:first-child { box-shadow: none; }
.pane-bar { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-2) var(--sp-3); border-block-end: 1px solid var(--border); background: var(--surface-2); font-size: var(--fs-sm); }
.pane-bar b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pane-body { flex: 1; padding: var(--sp-3) var(--sp-4); font-size: var(--fs-sm); line-height: 1.68; overflow-wrap: anywhere; }
.pane-body p + p { margin-block-start: var(--sp-3); }
.pane-body pre { overflow-x: auto; }
.pane-error { color: var(--critical); }
.pane-foot { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-2) var(--sp-3); border-block-start: 1px solid var(--border); font-size: var(--fs-xs); color: var(--faint); flex-wrap: wrap; }

/* The answer the conversation carried on from. An inset ring rather than a border so that turning
   it on does not move anything - a 2px border here would shift every pane beside it. */
.pane.best { box-shadow: inset 0 0 0 2px var(--accent); }

/* The merged answer (§142). A tinted head rather than a ring, so it reads as a different KIND of
   column at a glance without competing with .best — which the merge can also be, and often is. */
.pane.merged .pane-bar { background: var(--accent-soft); border-block-end-color: var(--accent-line); }
.stars { display: flex; gap: 1px; color: var(--faint); font-size: var(--fs-sm); }
.stars .on { color: var(--warning); }
.hl { background: var(--accent-soft); border-block-end: 1px solid var(--accent-line); padding: 0 2px; }
.cmp-sum { padding: var(--sp-2) var(--sp-4); border-block-start: 1px solid var(--border); background: var(--surface-2); display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; font-size: var(--fs-xs); }

/* YOUR MODEL RECORD - what this account's own comparisons showed.

   A TABLE, and deliberately not a chart. Six rows of five figures is a table; drawing it as bars
   would encode two of the columns in length and colour and leave the other three as text, which
   makes the two look like the answer and the rest like footnotes. The reader is comparing models
   they chose on numbers they can check - the figures themselves are the point. It also means this
   panel costs nothing to render and reads identically in both themes and in print.

   Scrolls inside its own box on a narrow screen rather than widening the thread. A table that
   pushes the composer off the edge makes the page unusable to fix. */
.record { border: 1px solid var(--border); border-radius: var(--r2); background: var(--surface); overflow: hidden; margin-block: var(--sp-4); }
.record-head { padding: var(--sp-3) var(--sp-4); border-block-end: 1px solid var(--border); background: var(--surface-2); }
.record-head h2 { font-size: var(--fs-md); letter-spacing: -.012em; }
.record-wrap { overflow-x: auto; }
.record-tbl { inline-size: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.record-tbl th, .record-tbl td { padding: var(--sp-2) var(--sp-4); text-align: start; white-space: nowrap; }
.record-tbl thead th { font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: .06em; color: var(--faint); font-weight: 650; }
.record-tbl tbody tr { border-block-start: 1px solid var(--border); }
.record-tbl tbody th { font-weight: 400; display: flex; align-items: baseline; gap: var(--sp-2); }

/* The provider, quietly. Two models with the same short name from different vendors is a real
   possibility on this platform, and the row has to survive it. */
.record-prov { font-size: var(--fs-micro); color: var(--faint); }

/* The rate, beside the two numbers it was computed from and never instead of them. */
.record-rate { margin-inline-start: 6px; color: var(--faint); font-variant-numeric: tabular-nums; }
.record-bad { color: var(--critical); font-size: var(--fs-xs); }
.record-up { color: var(--ok-fg); }
.record-down { color: var(--faint); margin-inline-start: 6px; }
.record-note { padding: var(--sp-2) var(--sp-4) var(--sp-3); border-block-start: 1px solid var(--border); background: var(--surface-2); white-space: normal; }

/* Arming a comparison, in the dock.

   A full row of its own (`flex-basis: 100%`), so the Send button never ends up wrapped underneath
   four model chips looking like it belongs to them. With one model selected the row is short and
   the arrangement still reads deliberately; with four it is the only thing that fits. */
.cmpbar { flex: 1 1 100%; display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; min-inline-size: 0; }
.cmpbar-chips { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.chip { display: inline-flex; align-items: center; gap: 4px; padding: 2px 4px 2px 8px; border: 1px solid var(--border); border-radius: var(--rf); background: var(--surface-2); font-size: var(--fs-xs); }
.chip-x { border: 0; background: none; color: var(--muted); cursor: pointer; font-size: var(--fs-sm); line-height: 1; padding: 0 2px; }
.chip-x:hover { color: var(--text); }
.cmpbar-est { font-size: var(--fs-xs); color: var(--faint); }

/* Council mode (§142): whether to pay for a merged answer, and which model writes it. Sits between
   the chips and the price, because that is the order the decision is made in. */
.cmpbar-council { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-xs); }
.cmpbar-council .input-sm { max-inline-size: 16ch; }
/* The composer's line when it is waiting for something rather than describing shortcuts. */
.dock-meta .warn { color: var(--warning-fg); }
/* The one case where the estimate is a warning rather than a note: the comparison would cost more
   than the balance, so some panes will be refused. Said before Send, not after. */
.cmpbar-est.short { color: var(--warning-fg); }

/* The swipe strip (C-04). Desktop hides it: up there the panes are side by side and there is
   nothing to navigate BETWEEN, so a control that scrolled a grid with no overflow would be a
   button that does nothing. */
.pane-tabs { display: none; gap: 4px; padding: var(--sp-2) var(--sp-3); border-block-end: 1px solid var(--border); background: var(--surface-2); overflow-x: auto; scrollbar-width: none; }
.pane-tabs::-webkit-scrollbar { display: none; }
.pane-tab { flex: 0 0 auto; padding: 3px 10px; border: 1px solid var(--border); border-radius: var(--rf); background: var(--surface); font-size: var(--fs-xs); color: var(--muted); white-space: nowrap; transition: background var(--dur-1), color var(--dur-1), border-color var(--dur-1); }
.pane-tab[aria-current="true"] { background: var(--accent-soft); border-color: var(--accent-line); color: var(--text); }

@media (max-width: 900px) {
  /*
     C-04 - swipe between model results.

     Four columns do not fit a phone and shrinking them produces one word per line, so this stops
     being a grid and becomes a scroll-snapping row: one whole answer per screen, swiped between.

     Stacked in a column - which is what this did before - was navigable but anonymous: three long
     answers one after another, with no sense of how many there were or which you were in, and
     comparing them meant scrolling a screen and a half to get back to the first one. Side by side
     is the entire point of the feature, and a phone can only show that one at a time.

     The gesture is the browser's own. No touch handler, so momentum, rubber-banding, a trackpad
     and a scrollbar drag all behave the way the platform does everywhere else.
  */
  .panes, .panes[data-cols="3"] {
    display: flex; grid-template-columns: none;
    overflow-x: auto; scroll-snap-type: x mandatory; overscroll-behavior-x: contain;
    scrollbar-width: none;
  }
  .panes::-webkit-scrollbar { display: none; }

  /* flex-basis 100%, so a snap point is always exactly one answer. */
  .pane { flex: 0 0 100%; scroll-snap-align: start; box-shadow: none; }

  .pane-tabs { display: flex; }
}

/* ---------------------------------------------------------------------------
   4. PRICING
   --------------------------------------------------------------------------- */
.ph { margin-block-end: var(--sp-6); }
.ph h1 { font-size: var(--fs-2xl); letter-spacing: -.026em; margin-block-end: 5px; }
.ph > p { font-size: var(--fs-md); color: var(--muted); }
.ph-ctls { display: flex; align-items: center; gap: var(--sp-4); margin-block-start: var(--sp-5); flex-wrap: wrap; }

.plans { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fit, minmax(214px, 1fr)); }
.plan { border: 1px solid var(--border); border-radius: var(--r3); padding: var(--sp-5); background: var(--surface); display: flex; flex-direction: column; position: relative; }
.plan.pick { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.plan-tag { position: absolute; inset-block-start: -9px; inset-inline-start: var(--sp-5); background: var(--accent-fill); color: var(--on-accent); font-size: var(--fs-micro); font-weight: 650; letter-spacing: .07em; text-transform: uppercase; padding: 2px 8px; border-radius: var(--r1); }
.plan h3 { font-size: var(--fs-md); margin-block-end: 3px; }
.plan-desc { font-size: var(--fs-xs); color: var(--muted); margin-block-end: var(--sp-4); min-block-size: 32px; }
.plan-cost { display: flex; align-items: baseline; gap: 3px; }
.plan-cost .amt { font-size: 30px; font-weight: 650; letter-spacing: -.035em; font-variant-numeric: tabular-nums; }
.plan-cost .per { font-size: var(--fs-xs); color: var(--muted); }
/* Credits are the headline unit — USD is only the price tag. CONTEXT §18.1 */
.plan-cr { padding: var(--sp-3) 0; margin-block: var(--sp-4); border-block: 1px solid var(--border); }
.plan-cr .n { font-size: var(--fs-xl); font-weight: 650; font-variant-numeric: tabular-nums; letter-spacing: -.022em; color: var(--accent); }
.plan-cr .u { font-size: var(--fs-xs); color: var(--muted); display: block; margin-block-start: 1px; }
.plan-list { flex: 1; display: flex; flex-direction: column; gap: 8px; margin-block-end: var(--sp-5); font-size: var(--fs-sm); }
.plan-list li { display: flex; gap: 8px; color: var(--muted); }
.plan-list .y { color: var(--ok-fg); flex-shrink: 0; font-weight: 700; }
.plan-list .n { color: var(--faint); flex-shrink: 0; }
.plan .btn { inline-size: 100%; }

.matrix-wrap { margin-block-start: var(--sp-8); border: 1px solid var(--border); border-radius: var(--r3); overflow: hidden; background: var(--surface); }
.matrix-head { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-4) var(--sp-5); border-block-end: 1px solid var(--border); flex-wrap: wrap; }
.matrix-head h3 { font-size: var(--fs-md); }
/* position: relative so the visually hidden "Included" labels inside (.sr-only is absolutely positioned) are clipped
   by this scroller rather than stretching the page's own; /pricing scrolled sideways on a phone (17 September audit). */
.matrix-scroll { overflow-x: auto; position: relative; }
.matrix { inline-size: 100%; font-size: var(--fs-sm); min-inline-size: 620px; }
.matrix th, .matrix td { padding: 10px var(--sp-4); text-align: start; border-block-start: 1px solid var(--border); vertical-align: top; }
.matrix thead th { border-block-start: 0; font-size: var(--fs-micro); font-weight: 650; letter-spacing: .07em; text-transform: uppercase; color: var(--faint); padding-block: var(--sp-3); }
.matrix thead th:not(:first-child) { text-align: center; }
/* The feature column carries a name and a sentence explaining it, so it is the only
   column allowed to wrap - and it gets enough width that the sentence is not a column
   of single words. */
.matrix tbody th { font-weight: 550; color: var(--text); inline-size: 38%; min-inline-size: 220px; }
.matrix tbody th .hint { font-weight: 400; margin-block-start: 2px; }
.matrix td { text-align: center; font-variant-numeric: tabular-nums; color: var(--muted); }
.matrix .grp th { background: var(--surface-2); inline-size: auto; min-inline-size: 0; font-size: var(--fs-micro); font-weight: 650; letter-spacing: .07em; text-transform: uppercase; color: var(--faint); padding-block: 6px; }
.matrix .yes { color: var(--ok-fg); font-weight: 650; }
.matrix .no  { color: var(--faint); }
/* Sized above 1em so the tick reads as a mark rather than as a glyph in the sentence, and
   given the row's own colour so forced-colors mode still draws it. */
.matrix .tick { inline-size: 18px; block-size: 18px; vertical-align: -.28em; }
.matrix tbody tr:not(.grp):hover th, .matrix tbody tr:not(.grp):hover td { background: var(--surface-2); }

.bundles { margin-block-start: var(--sp-8); }
.bundles h3 { font-size: var(--fs-xl); margin-block-end: 4px; }
.bundles > p { color: var(--muted); font-size: var(--fs-sm); margin-block-end: var(--sp-5); }
.bgrid { display: grid; gap: var(--sp-3); grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); }
.bundle { border: 1px solid var(--border); border-radius: var(--r3); padding: var(--sp-4); background: var(--surface); text-align: center; transition: border-color var(--dur-1); }
.bundle:hover { border-color: var(--accent); }
.bundle .n { font-size: var(--fs-2xl); font-weight: 650; font-variant-numeric: tabular-nums; letter-spacing: -.024em; }
.bundle .u { font-size: var(--fs-xs); color: var(--muted); margin-block-end: var(--sp-3); }
.bundle .c { font-size: var(--fs-md); font-weight: 650; }
.bundle .s { font-size: var(--fs-xs); color: var(--ok-fg); margin-block-start: 2px; }

.faq { margin-block-start: var(--sp-8); }
.faq h3 { font-size: var(--fs-xl); margin-block-end: var(--sp-5); }
.faq-grid { display: grid; gap: var(--sp-5) var(--sp-7); grid-template-columns: repeat(auto-fit, minmax(262px, 1fr)); }
.faq-item h4 { font-size: var(--fs-sm); margin-block-end: 4px; }
.faq-item p { font-size: var(--fs-sm); color: var(--muted); }

/* ---------------------------------------------------------------------------
   5. ANALYTICS
   --------------------------------------------------------------------------- */
.filters { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; padding: var(--sp-3); margin-block-end: var(--sp-4); border: 1px solid var(--border); border-radius: var(--r3); background: var(--surface); }
.subtabs { display: flex; gap: var(--sp-4); border-block-end: 1px solid var(--border); margin-block-end: var(--sp-5); overflow-x: auto; }
.subtab { padding: 8px 1px; font-size: var(--fs-sm); color: var(--muted); font-weight: 500; border-block-end: 2px solid transparent; margin-block-end: -1px; white-space: nowrap; }
.subtab:hover { color: var(--text); }
.subtab[aria-selected="true"] { color: var(--text); font-weight: 600; border-block-end-color: var(--accent); }

.tiles { display: grid; gap: var(--sp-3); grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); margin-block-end: var(--sp-4); }
.tile { border: 1px solid var(--border); border-radius: var(--r3); padding: var(--sp-4); background: var(--surface); }
.tile-l { font-size: var(--fs-xs); color: var(--muted); margin-block-end: 6px; }
.tile-v { font-size: var(--fs-3xl); font-weight: 650; letter-spacing: -.03em; line-height: 1.05; }
.tile-d { font-size: var(--fs-xs); margin-block-start: 5px; }
.good { color: var(--ok-fg); } .bad { color: var(--critical); }

.pgrid { display: grid; gap: var(--sp-4); grid-template-columns: 1.7fr 1fr; margin-block-end: var(--sp-4); }
.panel { border: 1px solid var(--border); border-radius: var(--r3); background: var(--surface); }
.panel-bar { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-4) var(--sp-5) var(--sp-3); flex-wrap: wrap; }
/* h2 AS WELL AS h3. The admin panels are sections of a page with its own h1, so their headings
   are h2 — and they were reaching for a `.panel-head` that never existed, which left them at the
   browser default: a heading noticeably larger than the panel it sits in. */
.panel-bar h3, .panel-bar h2 { font-size: var(--fs-md); }

/* A SUBTITLE IN THE BAR GOES UNDER THE HEADING, NOT BESIDE IT.

   The bar is a flex ROW, which is right for what it usually holds — a heading on the left and a
   button or a tag pushed to the right. A sentence explaining the panel is not that: put one in and
   it lands on the same line as the heading, reading as a caption glued to the title. That is what
   "labels alignment is not correct, it is touching with card layout" was on the app edit screen.

   Fixed here rather than on that one screen, and by ELEMENT rather than by a modifier class, so the
   next panel that needs a subtitle gets it right without anybody remembering a class. A <p> is the
   only prose in a bar; tags, counts and buttons are spans and buttons and still sit on the heading's
   own line, which is what they are for.

   `flex-basis: 100%` rather than a wrap on the container: the bar already wraps, but a heading and
   a short sentence fit on one line together, so wrapping never fires. This forces the break. */
.panel-bar > p {
  flex-basis: 100%;
  margin: 0;
  font-size: var(--fs-sm); color: var(--muted); line-height: 1.5;
}
.panel-bar .sub { font-size: var(--fs-xs); color: var(--faint); }
.panel-body { padding: 0 var(--sp-5) var(--sp-5); }

/* Two stacked panels are two things and have to look like two things. Until now every page
   supplied its own gap — `.stack` on the admin screens, an `.mt` on the second panel
   elsewhere — which means a page that forgets gets its cards welded into one slab, and the
   Profile page had five of them fused together. Same lesson as the `.form-ok` spacing above:
   a rule that has to be re-added per page is a missing rule, not a missing patch.

   Switched off inside containers that already space their own children, because a margin on a
   flex or grid item ADDS to the gap rather than replacing it. */
.panel + .panel { margin-block-start: var(--sp-5); }
.stack > .panel + .panel, .statgrid > .panel + .panel { margin-block-start: 0; }

/* Same rule, taught to see through a wrapper — and the third time of asking, so this time it is
   written as a set rather than as the one pair that was broken that day.

   A `<form>` draws nothing. Neither does a `.stack` or a `.statgrid`. So a panel inside one of them
   and a panel beside it are still two cards to the eye, but `.panel + .panel` cannot match across
   the wrapper and they weld into one slab. The first version of this listed `form + .panel` and its
   mirror; then Plans put a `.panel` next to a `.stack` and the gap vanished again, because the list
   named the wrapper of the week instead of the shape of the problem.

   The shape is: at the top level of a page, these are all SLABS, and two slabs never touch. Written
   symmetrically so every pair is covered in both directions, including pairs that do not exist yet.

   Over-inclusion is safe here and that is deliberate. `.pad` is a plain block, so a slab that
   already carries its own `margin-block-end` (`.tiles`, `.pgrid`) COLLAPSES with the margin below
   rather than adding to it — the result is one gap of `max(a, b)`, never two stacked. That is the
   same collapsing the `.form-ok` spacing further down relies on. What is NOT safe is a margin on a
   flex or grid ITEM, which does add; hence this stays scoped to `.pad >` and the `.stack`/`.statgrid`
   resets above stay where they are.

   FOURTH time of asking, and this one is the reason the rule now names a PARENT it does not know.
   The email designer put its tab panes in `<div id="pane-design">`, and every slab the React island
   rendered became a grandchild of `.pad` — so the rule stopped matching and the whole screen welded
   into one slab again. Adding `#pane-design` to the list would have been the wrapper of the week for
   the fourth time.

   `.flow` is the fix: it means "my children are top-level slabs, space them as such". Any wrapper
   that exists for structure rather than for looks wears it, and the rule below sees through it
   without ever being told what that wrapper is. */
:is(.pad, .flow)
  > :is(form, .panel, .stack, .statgrid, .pgrid, .tiles, .form-warn, .form-error, .form-ok, .seg, .flow)
  + :is(form, .panel, .stack, .statgrid, .pgrid, .tiles, .form-warn, .form-error, .form-ok, .seg, .flow) {
  margin-block-start: var(--sp-5);
}

/* A `.flow` draws nothing at all. It exists so the rule above can reach the slabs inside it — and it
   is itself in that list, because a `.flow` sitting under a `.seg` is still two things that need a
   gap between them. `display: contents` would be the tidier way to do the first half and is wrong:
   it would take the element out of the box tree, and `hidden` with it. */
.flow { display: block; }

/* A panel that opens and closes. `<details>` rather than a button and a class, so it works
   before any JavaScript runs and Ctrl+F still finds text inside a closed section — the browser
   opens it to show the match, which no hand-built accordion does. */
details.panel > summary.panel-bar { cursor: pointer; list-style: none; user-select: none; }
details.panel > summary.panel-bar::-webkit-details-marker { display: none; }
details.panel > summary.panel-bar:hover { background: var(--surface-2); border-radius: var(--r3) var(--r3) 0 0; }
summary.panel-bar .chev { transition: transform var(--dur-1) var(--ease); color: var(--faint); }
details[open] > summary.panel-bar .chev { transform: rotate(90deg); }
/* Closed, the bar IS the panel, so its own bottom padding is what keeps it from looking clipped. */
details.panel:not([open]) > summary.panel-bar { padding-block-end: var(--sp-4); }

/* The other half of the same idea: a card whose STATUS must always be readable but whose editing
   controls are wanted about twice a year. Used on Providers, where folding the whole card away
   would hide which vendor is broken — the one question that screen exists to answer. The summary
   is a quiet line rather than a bar, because it sits inside a card that already has a heading. */
/* No margin of its own: `.card-body > * + *` already separates it from the status above, and a
   margin here would be added to that one rather than replacing it. */
details.editbox { border-block-start: 1px solid var(--border); padding-block-start: var(--sp-3); }
details.editbox > summary { display: flex; align-items: center; gap: 6px;
  cursor: pointer; list-style: none; user-select: none;
  font-size: var(--fs-sm); font-weight: 600; color: var(--muted); }
details.editbox > summary::-webkit-details-marker { display: none; }
details.editbox > summary:hover { color: var(--text); }
details.editbox[open] > summary { margin-block-end: var(--sp-4); }
/* The forms inside want the rhythm they had when they were loose in the card, which they lost by
   ceasing to be direct children of `.card-body`. */
details.editbox > form + form { margin-block-start: var(--sp-4); }

.chart { position: relative; padding-block-start: var(--sp-4); }
.chart-plot { display: flex; align-items: flex-end; gap: 4px; block-size: 158px; position: relative; }
.gridlines { position: absolute; inset: 0 0 18px 0; pointer-events: none; }
.gridlines i { position: absolute; inline-size: 100%; block-size: 1px; background: var(--grid); }
.col { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; gap: 6px; block-size: 100%; position: relative; }
.bar { inline-size: 100%; max-inline-size: 26px; border-radius: 4px 4px 0 0; background: var(--s1); min-block-size: 3px; }
.bar.muted-bar { background: var(--surface-4); }
.pair { inline-size: 100%; max-inline-size: 30px; display: flex; gap: 2px; align-items: flex-end; block-size: 100%; }
.pair > i { flex: 1; border-radius: 4px 4px 0 0; min-block-size: 3px; }
.tick { font-size: var(--fs-micro); color: var(--faint); font-variant-numeric: tabular-nums; }
.col:hover .bar, .col:hover .pair > i { filter: brightness(1.08); }
.tip { position: absolute; inset-block-end: calc(100% + 6px); inset-inline-start: 50%; transform: translateX(-50%); opacity: 0; pointer-events: none; background: var(--tip-bg); color: var(--tip-fg); padding: 6px 9px; border-radius: var(--r2); font-size: var(--fs-xs); white-space: nowrap; z-index: 20; box-shadow: var(--sh-2); transition: opacity var(--dur-1); }
.tip b { font-variant-numeric: tabular-nums; }
.col:hover .tip { opacity: 1; }

.legend { display: flex; gap: var(--sp-4); flex-wrap: wrap; font-size: var(--fs-xs); color: var(--muted); margin-block-start: var(--sp-4); padding-block-start: var(--sp-3); border-block-start: 1px solid var(--border); }
.legend button { display: flex; align-items: center; gap: 6px; color: inherit; }
.sw { inline-size: 9px; block-size: 9px; border-radius: 2px; display: inline-block; flex-shrink: 0; }

.brk { display: flex; flex-direction: column; gap: var(--sp-4); }
.brk-r { display: grid; grid-template-columns: 1fr auto; gap: 5px 10px; font-size: var(--fs-sm); }
.brk-n { display: flex; align-items: center; gap: 7px; font-weight: 500; }
.brk-v { color: var(--muted); font-variant-numeric: tabular-nums; font-size: var(--fs-xs); }
.brk-t { grid-column: 1 / -1; block-size: 5px; background: var(--surface-3); border-radius: 3px; overflow: hidden; }
.brk-t > i { display: block; block-size: 100%; border-radius: 3px; }

.hero-stat { border: 1px solid var(--accent-line); background: var(--accent-soft); border-radius: var(--r3); padding: var(--sp-5) var(--sp-6); margin-block-end: var(--sp-4); display: flex; align-items: center; gap: var(--sp-6); flex-wrap: wrap; }
.hero-stat .n { font-size: 34px; font-weight: 650; letter-spacing: -.035em; color: var(--accent); line-height: 1; }
.hero-stat .t { flex: 1; min-inline-size: 220px; }
.hero-stat .t b { font-size: var(--fs-md); display: block; margin-block-end: 2px; }
.hero-stat .t p { font-size: var(--fs-sm); color: var(--muted); }

.dt { inline-size: 100%; font-size: var(--fs-sm); }
/* thead th, NOT th. A <th scope="row"> is the correct markup for the first cell of a data
   row - it names that row - and it was inheriting the COLUMN-header treatment: micro,
   uppercase, faint, nowrap. User emails, model names and pricing features were all being
   shouted in 10px grey capitals. Column headers and row headers look nothing alike, so
   they are styled apart. */
/* Padded on all four sides, top included. It used to open at 0, so inside a .panel the
   column headings sat directly on the card's own top border - the label and the line
   touching, with no air between them, which reads as a rendering fault rather than a
   design. The row beneath draws the divider (every body cell carries border-block-start),
   so the header needs no border of its own. */
.dt thead th { text-align: start; font-size: var(--fs-micro); font-weight: 650; letter-spacing: .07em; text-transform: uppercase; color: var(--faint); padding: var(--sp-3) var(--sp-4); white-space: nowrap; }
.dt tbody th { text-align: start; font-size: var(--fs-sm); font-weight: 500; letter-spacing: normal; text-transform: none; color: var(--text); padding: 9px var(--sp-4); border-block-start: 1px solid var(--border); vertical-align: top; }
.dt th button { display: inline-flex; align-items: center; gap: 4px; color: inherit; font: inherit; }
.dt th .sort { color: var(--accent); }
.dt td { padding: 9px var(--sp-4); border-block-start: 1px solid var(--border); }
.dt tbody tr:hover { background: var(--surface-2); }
.dt .n { text-align: end; font-variant-numeric: tabular-nums; }
.dt-scroll { overflow-x: auto; padding: 0 var(--sp-1) var(--sp-3); }

.capbar .track { block-size: 6px; background: var(--surface-3); border-radius: 3px; overflow: hidden; margin-block-start: var(--sp-4); }
.capbar .track > i { display: block; block-size: 100%; background: var(--warning); border-radius: 3px; }
.capbar .meta { display: flex; justify-content: space-between; font-size: var(--fs-xs); color: var(--muted); margin-block-start: 6px; }

@media (max-width: 1024px) { .pgrid { grid-template-columns: 1fr; } }

/* ---------------------------------------------------------------------------
   SMALL SCREENS
   Two rules do most of the work: nothing horizontal is allowed to wrap into a
   taller bar (it scrolls sideways instead), and every wide table gets its own
   scroller so the page itself never scrolls horizontally.
   --------------------------------------------------------------------------- */
@media (max-width: 900px) {
  .topbar { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; padding-inline: var(--sp-3); }
  .topbar::-webkit-scrollbar { display: none; }
  .topbar > * { flex-shrink: 0; }
  /* The one thing that may give way: a long page title, so the bar's own button is not pushed off the screen
     ("Terms of Service" hid most of Get started at 390 px, 17 September audit). */
  .topbar > h2 { flex-shrink: 1; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .crumbs { display: none; }
  .pad { padding: var(--sp-4) var(--sp-3) var(--sp-7); }
  .subtabs { overflow-x: auto; scrollbar-width: none; }
  .subtabs::-webkit-scrollbar { display: none; }
  .filters { overflow-x: auto; flex-wrap: nowrap; scrollbar-width: none; padding-block-end: var(--sp-1); }
  .filters::-webkit-scrollbar { display: none; }
  .filters > * { flex-shrink: 0; }
  .dt, .matrix { min-inline-size: 580px; }
  .dt-scroll, .matrix-scroll { -webkit-overflow-scrolling: touch; }
}

/* ---------------------------------------------------------------------------
   PHONES — A TABLE STOPS BEING A TABLE
   Below this width a seven-column grid cannot be squeezed and cannot honestly
   be scrolled either: at 580px minimum on a 380px screen, "Vendor key" was an
   input one character wide next to a Save button, and the operator had to
   scroll sideways to find out which column they were even looking at.
   So each row becomes a card, and each cell carries its own column heading -
   read from data-label, which is why every <td> in an admin table has one.
   The <thead> is not deleted, only hidden: it is what a screen reader uses to
   associate a cell with its column, and removing it would fix the phone by
   breaking the screen reader.
   .matrix is deliberately excluded. It is a plan-by-feature grid whose whole
   meaning is the intersection of two axes; stacked into a list it says nothing,
   so it keeps its sideways scroll.
   --------------------------------------------------------------------------- */
@media (max-width: 720px) {
  /* Even padding now that the content inside is a stack of cards rather than a
     table bleeding to the panel's edges. */
  .dt-scroll { overflow-x: visible; padding: var(--sp-3); }
  .dt { display: block; min-inline-size: 0; }

  .dt thead {
    position: absolute; inline-size: 1px; block-size: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  /* tfoot is in here with tbody deliberately. Left as a table-footer-group while the body
     above it is a stack of blocks, a totals row lays itself out on a grid of columns that
     no longer exists - it renders as one squashed line across the bottom. */
  .dt tbody, .dt tfoot,
  .dt tbody tr, .dt tfoot tr,
  .dt tbody th, .dt tfoot th,
  .dt tbody td, .dt tfoot td { display: block; }

  .dt tbody tr, .dt tfoot tr {
    border: 1px solid var(--border); border-radius: var(--r2);
    padding: var(--sp-3); background: var(--surface);
  }
  .dt tbody tr + tr, .dt tfoot tr { margin-block-start: var(--sp-3); }

  /* The row header is the card's title - the model name, the customer's email -
     so it gets size and weight rather than the cell treatment. */
  .dt tbody th, .dt tfoot th {
    border: 0; padding: 0 0 var(--sp-2);
    font-size: var(--fs-md); font-weight: 600;
  }

  .dt tbody td, .dt tfoot td {
    border: 0; padding: var(--sp-2) 0 0;
    display: flex; align-items: center; gap: var(--sp-3);
  }
  .dt tbody td::before, .dt tfoot td::before {
    content: attr(data-label); flex: 0 0 38%;
    font-size: var(--fs-micro); font-weight: 650; letter-spacing: .07em;
    text-transform: uppercase; color: var(--faint);
  }

  /* Cells with nothing to label - an actions column, or the "nothing matched"
     row that spans every column - get no label rail and the full width. */
  .dt tbody td:not([data-label])::before,
  .dt tfoot td:not([data-label])::before { content: none; }
  .dt tbody td[colspan] { display: block; }

  /* Right alignment is what makes a column of figures comparable. Stacked, there
     is no column, so it only pushes the number away from its own label. */
  .dt .n { text-align: start; }

  /* nowrap exists to stop a date breaking across two lines in a narrow column.
     There are no narrow columns here, and it forces a card wider than the screen -
     which on the models table is a row header carrying a name and three tags. */
  .dt tbody td.nowrap, .dt tbody th.nowrap { white-space: normal; }

  /* An editable cell gets the room the card has, instead of the sliver a
     squeezed column left it. */
  .dt tbody td .inline-form { inline-size: 100%; }
  .dt tbody td .inline-form .input { flex: 1; min-inline-size: 0; }

  .pager { padding-inline: var(--sp-3); }
}
@media (max-width: 560px) {
  .home { padding: var(--sp-5) var(--sp-3) var(--sp-7); }
  .hero-stat { padding: var(--sp-4); gap: var(--sp-3); }
  .pbox { border-radius: var(--r2); }
  .dock { padding: var(--sp-2) var(--sp-3) var(--sp-3); }
  .thread { padding: var(--sp-4) var(--sp-3); }
  .msg.user .msg-text { padding: var(--sp-2) var(--sp-3); }
  .plans, .tiles, .bgrid, .starters { grid-template-columns: 1fr; }
  /* comfortable tap targets — 24px icon buttons are too small for a thumb */
  .icon-btn { min-inline-size: 34px; min-block-size: 34px; }
  .btn { padding-block: 9px; }
}
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important; } }

/* One inline-SVG icon set replaces the font glyphs and colour emoji that were
   here before. A glyph renders at a different weight, size and baseline in
   every font on every OS, and colour emoji ignore the UI palette entirely.
   1em keeps each icon exactly the size the glyph it replaced would have been. */
.gi { inline-size: 1em; block-size: 1em; display: inline-block; vertical-align: -.13em; flex-shrink: 0; }

/* ============================================================================
   BLUE VISION ADDITIONS

   The template ships home, workspace, compare, pricing and analytics. It has no
   auth and no admin screens, so those are built here — from the same tokens and
   the same primitives, never a parallel style.

   Rule for anything added below: if Nova already has a class that does the job
   (.btn, .tag, .dt, .panel, .field, .lbl), use it. Only genuinely new structure
   belongs here.
   ============================================================================ */

/* ---------------------------------------------------------------------------
   AUTH — showcase on the left, form on the right. No app shell around it.

   Sign-in deliberately does NOT get the sidebar. A nav full of links that all
   bounce back to this page is noise at the exact moment the person wants one
   thing. But a lone card on an empty background is the other failure: this is
   the only page a visitor sees before they know what the product is, and it
   should not look like an internal tool.

   The showcase is a fixed deep blue in BOTH themes rather than following
   --bg. Two reasons, and the second is the real one: the panel is white text
   on a photographic surface, which needs a known contrast; and a visitor has
   no account, so there is no accent of theirs to honour — the only colour the
   page can be is the brand's own.
   --------------------------------------------------------------------------- */
/* HALF AND HALF (NETFIVE-Auth-Handoff, 21 September 2026: brandPanelWidth 720 on a 1440 reference).
   The form side was 512px fixed, which made the panel grow and the form side stay still on a wide
   screen; the handoff gives them the same width and centres a 400px form inside its half. */
.auth {
  flex: 1 1 auto; min-block-size: 0; overflow-y: auto;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  background: var(--surface);
}

/* No showcase (a signed-in visitor on /account/access-denied): one centred
   column, which is what this page was before. The padding stays on .auth-pane
   so the card is spaced by one rule in both shells rather than by two that have
   to agree. */
.auth-solo { display: flex; flex-direction: column; }

.auth-pane {
  position: relative;
  display: flex; flex-direction: column; align-items: center;

  /* ROOM RESERVED FOR THE CONTROLS AT THE CORNER, which are out of flow and therefore invisible to
     everything below them. Their own inset, their height, and air — one variable, because the
     padding and the toolbar's position have to agree and two numbers eventually stop agreeing. */
  --auth-band: calc(var(--sp-4) + 2.1rem + var(--sp-6));
  /* The foot reserves the legal row the same way the head reserves the language picker: both are out
     of flow, so both have to be paid for in padding or the form overlaps them. */
  padding: var(--auth-band) 32px calc(24px + 1.2rem + var(--sp-5)); min-inline-size: 0;
  background: var(--surface);

  /* SAFE centring, and this is the second half of the same fix.

     Plain "center" on a box shorter than its content overflows at BOTH ends, ignoring the padding
     entirely — so on a laptop window 600px tall the sign-in card rose straight through the reserved
     band and under the language picker. Measured: minus one pixel. That is the "still touching"
     report, and no amount of padding would have fixed it, because padding is what centring overflows
     past. "safe center" centres while there is room and falls back to the start edge when there is
     not, which also means the overflow goes downward where it can be scrolled to. */
  justify-content: safe center;
}

/* Top-right of the form side. Absolute rather than a flex child: .auth-pane
   centres its children vertically, and a toolbar in that flow would drag the
   card off centre by half its own height. */
.auth-appear {
  position: absolute; inset-block-start: var(--sp-4); inset-inline-end: var(--sp-4);
  display: flex; align-items: center; gap: 2px; z-index: 5;
}

/* NEAR-BLACK, FLAT, AND THE MARK IN THE CORNER (NETFIVE-Auth-Handoff, 21 September 2026).

   The panel was a blue gradient with a ray texture over it and the logo floating in the middle beside
   the headline. The handoff draws it as one flat #14161A with the mark at the top-left, the claim and
   the illustration centred, and a single quiet line at the foot. Three bands, so it is a column now
   rather than one centred block.

   Still fixed in both themes, for the reason written above: white text on a known surface, and a
   visitor has no accent of their own to honour. What changed is which colour, not the principle. */
.auth-show {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column;
  padding: 40px 64px;
  color: #FFFFFF;
  background: #14161A;
}
.auth-show-in {
  position: relative; flex: 1 1 auto;
  display: flex; flex-direction: column; justify-content: center; gap: 32px;
  min-block-size: 0;
}

.auth-show-brand { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
.auth-show-brand img { inline-size: 32px; block-size: 32px; display: block; }
.auth-show-brand span { font-size: 18px; font-weight: 800; letter-spacing: .02em; }

/* The one line at the foot of the panel. Quiet on purpose — it is the only place the rest of the
   product is named, and it is not a claim. */
/* The top margin only bites on a narrow screen, where the panel is a short band and the middle band
   has no spare height to push this away with. On a full screen the middle is flex:1 and already
   holds it against the bottom edge. */
.auth-show-foot { flex: 0 0 auto; margin: var(--sp-4) 0 0; font-size: 13px; color: #9AA1AD; }

/* ONE CLAIM, NOT THREE ON A TIMER (NETFIVE-Auth-Handoff, 21 September 2026).

   The panel used to rotate three claims on an 18-second CSS animation. The handoff draws a single
   static headline — "Ask once. Compare the answers." — above the illustration that shows the same
   idea, and a claim that moves while somebody is typing a password competes with the thing they came
   to do. The other two claims are kept in the layout's markup as a comment, so restoring the rotator
   is reading one file rather than rewriting it.

   44/1.1/800 and the measure capped at 520px, both the handoff's: the cap is what keeps it to two
   lines at 1440, which is the shape it was drawn in. */
.auth-claim h2 {
  margin: 0 0 14px; max-inline-size: 520px;
  font-size: clamp(2rem, 3.2vw, 44px); line-height: 1.1; font-weight: 800; letter-spacing: -.025em;
}
.auth-claim p {
  margin: 0; max-inline-size: 480px;
  font-size: 16px; line-height: 1.55; color: #B6BCC6;
}


/* ---- THE COMPARE ILLUSTRATION (NETFIVE-Auth-Handoff §4.1, 21 September 2026) --------------------
 *
 * Replaces the row of vendor names in the signed-out panel. The handoff's own values, and fixed
 * rather than themed: this panel is always the dark one, on every theme and every accent, so it does
 * not take --surface or --accent. The send square is the one place red appears here besides the logo,
 * which is the handoff's rule.
 */
.auth-demo {
  /* No margin of its own: the panel's middle band is a flex column with the handoff's 32px gap, and
     a margin here was stacking on top of that and pushing the illustration away from its claim. */
  padding: 18px; border-radius: 18px;
  background: #1c1f25; border: 1px solid #2c3038;
}

.auth-demo-ask {
  display: flex; align-items: center; gap: 12px;
  block-size: 44px; padding-inline: 14px 7px;
  border-radius: 12px; background: #24282f; border: 1px solid #343944;
  font-size: 14px; color: #e4e7ec;
}
.auth-demo-ask > span:first-child { flex: 1 1 auto; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.auth-demo-send {
  flex: 0 0 auto; display: grid; place-items: center;
  inline-size: 30px; block-size: 30px; border-radius: 9px;
  background: #dc1f2e; color: #fff;
}

.auth-demo-cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin-block-start: 12px; }

.auth-demo-card {
  display: grid; gap: 7px; align-content: start;
  padding: 14px; border-radius: 12px; background: #24282f; border: 1px solid #343944;
}

.auth-demo-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }

.auth-demo-chip {
  justify-self: start;
  padding: 3px 7px; border-radius: 6px; background: #343944;
  font-size: 12px; font-weight: 700; color: #e4e7ec; white-space: nowrap;
}

/* The point of the picture: one of the three does not agree. Amber, and it carries the word. */
.auth-demo-differs { font-size: 11px; font-weight: 700; color: #fcd34d; white-space: nowrap; }

.auth-demo-bar { block-size: 7px; border-radius: 999px; background: #454b57; }

/* Below the handoff's 1200px split the panel narrows; three cards side by side stop being readable
   before the panel itself is hidden at 900. */
@media (max-width: 1199px) {
  .auth-demo-cards { grid-template-columns: minmax(0, 1fr); }
  .auth-demo-card:nth-child(n+3) { display: none; }
}

/* Motion is decoration here, and this is the page somebody signs in on. With
   reduced motion the first claim simply stays put — not a faster loop. */
@media (prefers-reduced-motion: reduce) {
  .auth-rot-i { animation: none; }
  .auth-rot-i:first-child { opacity: 1; }
  .auth-rot-i:not(:first-child) { display: none; }
}

/* Under 1000px the showcase becomes a band above the form: the brand and the
   claim, no vendor list, no big type. It is not hidden outright — on a phone it
   is still the only thing telling a first-time visitor what they are signing in
   to.

   A COLUMN, not a one-column grid. A grid item with `overflow: hidden` has an
   automatic minimum size of ZERO, so when the band and the card together came
   to more than the viewport the browser did not scroll — it crushed the band to
   whatever was left, and the logo was sliced through the middle. A flex column
   whose band cannot shrink has no way to do that; the page scrolls instead,
   which is what a page that is too long for the screen is supposed to do. */
@media (max-width: 1000px) {
  .auth { display: flex; flex-direction: column; }
  .auth-show { flex: 0 0 auto; padding: var(--sp-5) var(--sp-5) var(--sp-4); align-items: flex-start; }
  .auth-pane { flex: 1 0 auto; padding: var(--sp-6) var(--sp-4) var(--sp-8); justify-content: flex-start; }

  .auth-show-in { max-inline-size: none; }
  .auth-show-brand { gap: 9px; margin-block-end: var(--sp-4); }
  .auth-show-brand img { inline-size: 32px; block-size: 32px; }
  .auth-show-brand span { font-size: var(--fs-xl); }

  /* One line of claim on a phone. The supporting sentence is the first thing to
     go: it is the part somebody scrolls past to reach the password box, and
     every pixel it takes is a pixel of form below the fold. */
  .auth-rot { min-block-size: 3.4rem; }
  .auth-rot h2 { font-size: var(--fs-2xl); margin-block-end: 0; }
  .auth-rot p { display: none; }
  .auth-demo { display: none; }

  /* In the flow above the card, not floating over its corner. The pane's inline
     padding and the card's edge are the same 16px here, so an absolute button
     inset by 16px lands exactly on top of the card — which is what "the theme
     button is out" was: a control overlapping the thing it sits beside.

     THE GAP IS sp-6, NOT sp-3, and the difference is the whole fix. Moving the
     controls into the flow stopped them OVERLAPPING the card, and the report
     came back anyway: "still touching language with signin card". Measured, the
     gap was ten pixels — no overlap, and near enough that the eye reads one
     block, which is what somebody means by touching. Twenty-two pixels reads as
     two things. The lesson is that "they no longer overlap" and "they look
     separate" are different tests, and only the second one is the complaint. */
  .auth-appear { position: static; align-self: flex-end; margin-block-end: var(--sp-6); }
}

/* Below 380px — a small phone in portrait — the band is brand only. Two lines
   of claim on top of a keyboard is a form nobody can see. */
@media (max-width: 380px) {
  .auth-rot { display: none; }
  .auth-show { padding-block: var(--sp-4); }
  .auth-show-brand { margin-block-end: 0; }
}

/* NO CARD (NETFIVE-Auth-Handoff, 21 September 2026).

   The form sat in a white card with a border and a shadow, on a tinted page. The handoff puts it
   directly on a white half at 400px wide, with the panel beside it doing the work the card's edge
   used to do — there is already a hard line down the middle of this page, and a second frame inside
   it read as a dialogue floating over a website.

   It keeps the card chrome on /account/access-denied, where there is no panel and a lone form on an
   empty page IS the thing the card was for. That is .auth-solo, below. */
.auth-card {
  inline-size: 100%; max-inline-size: 400px;
  background: transparent; border: 0; border-radius: 0; box-shadow: none;
  padding: 0;
}
.auth-solo .auth-card {
  max-inline-size: 408px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r4); box-shadow: var(--sh-2);
  padding: var(--sp-6);
}
.auth-brand {
  display: flex; align-items: center; gap: 9px;
  margin-block-end: var(--sp-6); justify-content: center;
}
/* Exactly one mark on the page. Wherever the showcase is rendered it carries
   the brand — at 40px beside the form, at 32px in the phone band — so the
   card's own brand block is the same logo and the same three words a second
   time, twenty pixels below the first.

   It stays in the markup rather than being deleted, because it is the only
   brand on /account/access-denied, where there is no showcase. */
.auth:not(.auth-solo) .auth-brand { display: none; }
/* The handoff's type scale for the form side: a 30/800 title over a 15px line, and 13/700 labels.
   It reads a step larger than the rest of the product on purpose — this is the only page a visitor
   meets before they know what any of it is. */
.auth-head { margin-block-end: 22px; }
.auth-head h1 { font-size: 30px; font-weight: 800; letter-spacing: -.02em; margin-block-end: 6px; }
.auth-head p { font-size: 15px; color: var(--muted); }

.auth-card .form-row > label,
.auth-card .form-row .row > label { font-size: 13px; font-weight: 700; color: var(--text); }

/* 46 for a field, 48 for the one button that submits, 12px corners on the buttons and 10 on the
   fields — the handoff's `size` and `radius` blocks. The 2px difference between field and primary
   button is deliberate there and is what makes the button read as the end of the form. */
.auth-card .input { min-block-size: 46px; border-radius: 10px; font-size: 15px; }
.auth-card .btn-lg { min-block-size: 48px; border-radius: 12px; font-size: 15px; font-weight: 700; }
.auth-card .auth-alt .btn { min-block-size: 46px; border-radius: 12px; font-size: 14px; font-weight: 600; }

/* New here / Locked out: stacked and centred, not a split row. The handoff draws them as two short
   sentences under the form rather than as a footer bar, and no rule above them — the "or" divider is
   already the only line this column needs. */
.auth-foot {
  margin-block-start: 6px; padding-block-start: 0; border-block-start: 0;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  font-size: 14px; color: var(--text-2, var(--muted)); text-align: center;
}
.auth-foot a { font-weight: 700; }

/* The page's own footer, at the bottom of the form side rather than under the form: support on one
   end, the legal links on the other. Out of the centred block, so it stays at the foot of the
   column however tall the form is. */
/* Absolute at the foot, for the same reason the language picker is absolute at the head: .auth-pane
   centres its children, and a footer in that flow would drag the form off centre by half its own
   height. The pane reserves the room in its padding. */
.auth-legal {
  position: absolute; inset-inline: 32px; inset-block-end: 24px;
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4);
  font-size: 12px; color: var(--muted);
}
.auth-legal a { color: var(--link); font-weight: 600; }
.auth-legal a:hover { color: var(--link-hover); text-decoration: underline; }
.auth-legal-links { display: flex; gap: 14px; }
.auth-legal-links a { color: var(--muted); font-weight: 400; }
.auth-foot a { color: var(--link); font-weight: 600; }
.auth-foot a:hover { color: var(--link-hover); text-decoration: underline; }

/* "Forgot password?" and the like, on the label row: indigo, 600, like every other link here
   (NETFIVE-Auth-Handoff §4.5). It was inheriting the body colour and reading as static text. */
.auth-card .form .row a.sm { color: var(--link); font-weight: 600; }
.auth-card .form .row a.sm:hover { color: var(--link-hover); text-decoration: underline; }

/* The second way in, under the sign-in form (S115).

   A rule with the word sitting ON it, rather than a heading — this is a fork in one task, not the
   start of a new section, and a bold "OR" heading makes the page read as two forms. The line is
   drawn with a gradient so it stops either side of the word without needing three elements. */
/* Files pinned to a project (S115).

   A list rather than the thumbnail strip a message uses: these are documents, there is nothing to
   look at, and the two facts that matter — what it is called and how much of every message it is
   going to occupy — are both text. */
/*
 * DROPPING DOCUMENTS ONTO A PROJECT (client, 27 Aug — "our screen should provide drag and drop").
 *
 * Same visual language as the sidebar's folder targets above: a dashed accent outline while a file
 * is over it. Offset outwards so the outline sits around the whole area rather than clipping the
 * first row of the list.
 */
.pinned-drop { border-radius: var(--r2); }
.pinned-drop.is-drop { background: var(--accent-soft); outline: 1px dashed var(--accent); outline-offset: 4px; }

.pinned-list { display: flex; flex-direction: column; gap: 2px; margin-block: var(--sp-2); }
.pinned-list li {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 5px var(--sp-2); border-radius: var(--r1); background: var(--surface-2);
}
.pinned-name {
  flex: 1; min-inline-size: 0; font-size: var(--fs-xs); font-weight: 550;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pinned-meta { font-size: var(--fs-micro); color: var(--faint); flex-shrink: 0; }

/* "shortened" is never quiet. Only part of this document is being sent, and a user who does not
   know that will read a wrong answer as the model being wrong. */
.pinned-warn { color: var(--warning-fg); }

/* The folder drag handle (S115).

   Hidden until the row is hovered, and then held visible for the whole drag: a column of grips beside
   a list nobody is reordering is clutter, but one that vanishes when the pointer leaves its own row
   makes the next target impossible to aim at. `visibility` rather than `display`, so the row does not
   change width as the handle appears — a list that reflows under the pointer is a list you cannot
   drop on accurately. */
.fold-grip {
  visibility: hidden; cursor: grab; user-select: none;
  padding-inline: 2px; color: var(--faint); line-height: 1; flex-shrink: 0;
}
.fold-head:hover .fold-grip,
.fold.is-reordering .fold-grip { visibility: visible; }
.fold-grip:active { cursor: grabbing; }

/* Where it will land. A line on the leading edge rather than a filled row, because the folder is
   going BETWEEN two things and a filled row says "into this one" — which is the other gesture this
   same header accepts. */
.fold.is-reorder-over > .fold-head { box-shadow: inset 0 3px 0 0 var(--accent); }

/* The picture being drawn (S115).

   A box in the shape the picture will be, so the thread does not jump when a megapixel image lands
   in it. `max-inline-size` because a "tall" picture in a wide column would otherwise reserve most of
   a screen height for something that arrives at a readable size. */
.drawing-canvas {
  position: relative; overflow: hidden;
  inline-size: 100%; max-inline-size: 420px;
  border: 1px solid var(--border); border-radius: var(--r3);
  background: var(--surface-2);
  display: grid; place-items: center;
}

/* The sweep. A gradient moving across the surface, which reads as something developing without
   claiming to measure anything — there is no progress to report, and a bar that sat at 90% for
   eleven seconds would be worse than this. */
.drawing-sweep {
  position: absolute; inset: 0;
  background: linear-gradient(
    115deg,
    transparent 20%,
    color-mix(in oklab, var(--accent) 12%, transparent) 45%,
    color-mix(in oklab, var(--accent) 20%, transparent) 50%,
    color-mix(in oklab, var(--accent) 12%, transparent) 55%,
    transparent 80%);
  background-size: 260% 100%;
  animation: drawing-sweep 2.4s var(--ease) infinite;
}

@keyframes drawing-sweep {
  from { background-position: 130% 0; }
  to   { background-position: -30% 0; }
}

.drawing-status { position: relative; z-index: 1; padding: var(--sp-4); text-align: center; }

/* A wait that pulses for thirty seconds is a wait that cannot be looked away from. The box still
   reserves the space, which is the half of this that is not decoration. */
@media (prefers-reduced-motion: reduce) {
  .drawing-sweep { animation: none; opacity: .35; }
}

/* "Changing this picture", above the message box (S115).

   A thumbnail rather than a line of text, because the whole point is that the next message acts on
   THAT picture — and a thread can easily hold six. Sitting inside the composer, above the words,
   for the same reason the attachment strip does: it is part of the message being written. */
.edit-strip {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3); margin-block-end: var(--sp-2);
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--r2); font-size: var(--fs-xs); color: var(--muted);
}
.edit-strip img {
  inline-size: 38px; block-size: 38px; object-fit: cover;
  border-radius: var(--r1); border: 1px solid var(--line); flex-shrink: 0;
}
.edit-strip span { flex: 1; min-inline-size: 0; line-height: 1.45; }
.edit-strip b { color: var(--text); }

.auth-alt { margin-block-start: var(--sp-5); text-align: center; }
.auth-alt .auth-or {
  display: block; position: relative;
  font-size: var(--fs-xs); color: var(--faint); text-transform: uppercase; letter-spacing: .08em;
  margin-block-end: var(--sp-4);
}
.auth-alt .auth-or::before, .auth-alt .auth-or::after {
  content: ""; position: absolute; inset-block-start: 50%;
  inline-size: calc(50% - 22px); block-size: 1px; background: var(--border);
}
.auth-alt .auth-or::before { inset-inline-start: 0; }
.auth-alt .auth-or::after  { inset-inline-end: 0; }
.auth-alt .btn { inline-size: 100%; justify-content: center; }

/* The provider button is wrapped in its own form (a sign-in is a POST), so the gap between it and
   whatever follows has to come from the form, not from the button. */
.auth-alt > form { margin-block-end: var(--sp-3); }

/* Google's mark and the label as one row. gap rather than a margin on the svg, so the pair stays
   centred as a unit whatever the label says. */
.btn-provider { display: inline-flex; align-items: center; gap: var(--sp-2); }
.btn-provider svg { flex-shrink: 0; }

/* The sign-up variant: provider first, then the divider introducing the email form below it.
   The two hairlines are dropped here because they are sized for the word "or" and a longer label
   would run straight through them. */
.auth-alt-top { margin-block-start: 0; margin-block-end: var(--sp-5); }
.auth-alt-top .auth-or { margin-block: var(--sp-3) 0; text-transform: none; letter-spacing: 0; }
.auth-alt-top .auth-or::before, .auth-alt-top .auth-or::after { content: none; }

.auth-aside { max-inline-size: 408px; margin-block-start: var(--sp-4); }

/* ---------------------------------------------------------------------------
   FORMS — stacked label + control, which is what every form outside the
   workspace actually is. Nova's .field is a single-line inline control and does
   not cover this.
   --------------------------------------------------------------------------- */
.form { display: flex; flex-direction: column; gap: var(--sp-4); }
.form-row { display: flex; flex-direction: column; gap: 5px; }
.form-row > label { font-size: var(--fs-sm); font-weight: 600; }
/* NOT scoped to `.form-row`, and `display: block` is the point of the rule.

   A `.hint` is nearly always a `<span>`, and an inline span ignores vertical margin entirely — so a
   hint used anywhere other than inside a `.form-row` (which is a flex column and supplies its own
   gap) came out full-size, full-contrast, and welded to whatever was above it. That is what the
   sentence under the block palette on the email designer looked like. */
.hint { display: block; font-size: var(--fs-xs); color: var(--faint); line-height: 1.45; }

.input {
  inline-size: 100%; padding: 8px 11px;
  border: 1px solid var(--border-strong); border-radius: var(--r2);
  background: var(--surface); font-size: var(--fs-md);
  transition: border-color var(--dur-1), box-shadow var(--dur-1);
}
.input:hover { border-color: var(--faint); }
/* FOCUS IS INDIGO, NOT THE ACCENT (NETFIVE-Auth-Handoff §4.4, 21 September 2026). The accent is each
   person's own colour and on ruby it is the logo's red - so a focused field was drawing a red ring
   that reads as an error on the one screen where being wrong about which field is wrong matters. */
.input:focus { outline: none; border-color: var(--link); box-shadow: 0 0 0 3px var(--link-ring); }

/* AND THE FIELD THAT ACTUALLY HAS THE ERROR IS MARKED. This class has been emitted by the validation
   tag helper all along with nothing styling it, so the errored box looked exactly like a good one -
   while the focused box next to it wore a red ring. The pair of those said "the email is wrong",
   which on a sign-in form is both untrue and the one thing it must never say. */
.input-validation-error,
.input[aria-invalid="true"] { border-color: var(--critical); }
.input-validation-error:focus,
.input[aria-invalid="true"]:focus { border-color: var(--critical); box-shadow: 0 0 0 3px var(--critical-bg); }
.input::placeholder { color: var(--faint); }
/* background-COLOR, not the shorthand: the shorthand resets background-image,
   and a disabled <select> would lose the chevron painted on it below. */
.input:disabled { background-color: var(--surface-3); color: var(--muted); cursor: not-allowed; }
.input-sm { padding: 5px 8px; font-size: var(--fs-sm); }

/* A PASSWORD WITH A "SHOW" BUTTON INSIDE ITS RIGHT EDGE (sign-up, Syed 21 September 2026).
   The field keeps room for the button, so a long password never runs underneath it. */
.pw-field { position: relative; }
.pw-field > .input { padding-inline-end: 72px; }
.pw-reveal {
  position: absolute; inset-block: 4px; inset-inline-end: 4px;
  padding: 0 11px; border: 0; border-radius: calc(var(--r2) - 2px);
  background: transparent; color: var(--muted);
  font: inherit; font-size: var(--fs-sm); font-weight: 600; cursor: pointer;
  transition: background var(--dur-1), color var(--dur-1);
}
.pw-reveal:hover { color: var(--text); background: var(--surface-3); }
.pw-reveal:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.pw-reveal[hidden] { display: none; }

/* CAPS LOCK (NETFIVE-Auth-Handoff §4.5, 21 September 2026). A hint about the keyboard, not an error:
   muted rather than red, and the field is never marked invalid for it - otherwise somebody typing a
   correct password in capitals is told their password is wrong, which it is not. */
.pw-caps {
  display: flex; align-items: center; gap: 6px;
  margin-block-start: 6px;
  font-size: var(--fs-xs); font-weight: 600; color: var(--muted);
}
.pw-caps[hidden] { display: none; }
.pw-caps-key {
  display: inline-grid; place-items: center;
  min-inline-size: 18px; block-size: 18px; padding-inline: 4px;
  border: 1px solid var(--border); border-radius: 5px;
  background: var(--surface-3); font-size: 11px; line-height: 1;
}
/* RIGHT-TO-LEFT: THE REVEALED PASSWORD MUST NOT FLIP. The exception below keeps a password left-to-right
   by matching input[type="password"] - and the moment "Show" makes it type="text", that rule stops
   matching and an Urdu or Arabic reader watches their password reverse. So the whole field is held
   left-to-right as a unit, which also keeps the button and the room made for it on the same side. */
[dir="rtl"] .pw-field { direction: ltr; }
[dir="rtl"] .pw-field > .input { text-align: right; }
/* Every <select> in the product, fixed in one place.

   Chromium draws the native arrow hard against the inline-end border and
   IGNORES padding-inline-end — rendered side by side, a select with 11px of
   end padding and one with 32px are identical pixel for pixel. So the gap
   cannot be bought with padding: the arrow has to be ours, which means
   resetting `appearance` and painting a chevron as a background layer.

   LONGHANDS, never the `background` shorthand. `.input` above sets the surface
   colour with the shorthand, so repeating it here would wipe whichever of the
   two lost — the colour or the chevron — depending on rule order.

   The chevron's grey is a literal, not a token: a colour inside a data URI
   cannot read a custom property. It is the one colour in this file that is not
   themed, picked to sit correctly on both surfaces — which is exactly what the
   native arrow it replaces was doing. */
select.input {
  cursor: pointer;
  appearance: none; -webkit-appearance: none;
  padding-inline-end: 32px;
  background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b91a0' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 15px;
}
select.input-sm { padding-inline-end: 26px; background-position: right 8px center; background-size: 13px; }

/* Physical positions, because there is no logical `background-position`. Urdu
   turns the whole interface round and an arrow left behind on the right would
   be the one control that did not follow. */
[dir="rtl"] select.input { background-position: left 11px center; }
[dir="rtl"] select.input-sm { background-position: left 8px center; }

textarea.input { resize: vertical; min-block-size: 76px; line-height: 1.55; }

.check { display: flex; align-items: flex-start; gap: 9px; font-size: var(--fs-sm); }
.check input { margin-block-start: 3px; accent-color: var(--accent); flex-shrink: 0; }

/* Model access picker. Columns rather than one tall list: the tiers are what an admin
   reasons in ("Free gets Fast, Pro gets everything"), and a single column pushed the
   Publish button below the fold — which is where an irreversible action should never be
   hiding, because the way you find it is by scrolling past what you just ticked. */
.modelgrid {
  display: grid; gap: var(--sp-5);
  grid-template-columns: repeat(auto-fit, minmax(232px, 1fr));
  margin-block-end: var(--sp-4);
}
.modelgrid-tier { display: flex; flex-direction: column; gap: 9px; min-inline-size: 0; }
.modelgrid-tier h4 {
  font-size: var(--fs-micro); font-weight: 650; letter-spacing: .07em;
  text-transform: uppercase; color: var(--faint);
  padding-block-end: var(--sp-2); border-block-end: 1px solid var(--border);
}
/* The row is the hit target, not the 13px box. Padding is on the label so the whole
   strip highlights, which also makes the price legible as part of the choice. */
.modelgrid .check {
  padding: 6px 8px; border-radius: var(--r2); cursor: pointer;
  transition: background var(--dur-1);
}
.modelgrid .check:hover { background: var(--surface-3); }
.modelgrid .check:has(input:checked) { background: var(--accent-soft); }
.modelgrid .check:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 1px; }

/* The cap that belongs to the switch above it. Indented to the switch's label rather than
   the column edge, so the pairing is visible without a box drawn round it — twenty features
   in twenty boxes reads as twenty problems. */
.limitrow {
  display: flex; align-items: center; gap: 7px;
  margin-block: -3px var(--sp-2); padding-inline-start: 30px;
}
.limitrow .input { inline-size: 88px; }

/* Wraps. A plan granting ten models produced one line that scrolled the panel sideways. */
.chiprow { display: flex; flex-wrap: wrap; gap: 5px; }

/* The free-trial strip, directly above the composer. It sits with the thing it is about — a
   banner at the top of the page is read once and scrolled past, and this has to stay true as
   the count goes down. */
.trialbar {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  padding: 7px 10px; margin-block-end: var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--r2);
  background: var(--surface-2); color: var(--muted);
  font-size: var(--fs-sm); line-height: 1.4;
}
.trialbar b { color: var(--text); font-weight: 600; }

/* Out of messages. Warning colours rather than error: nothing went wrong, the trial simply
   ended, and red would read as a fault the visitor caused. */
.trialbar-done { background: var(--warning-bg); border-color: var(--warning); }
.trialbar-done b { color: var(--warning-fg); }

/* The cost ceiling, when it is not a warning. Same shape as .form-warn so the panel does not
   jump as a plan crosses into loss — only the colour changes, which is the one thing that
   actually changed. */
.estimate {
  display: flex; gap: 8px; align-items: flex-start;
  padding: 9px 12px; border-radius: var(--r2);
  font-size: var(--fs-sm); line-height: 1.5;
  background: var(--surface-2); color: var(--muted);
  border: 1px solid var(--border);
  margin-block-end: var(--sp-4);
}
.form-warn + .form, .estimate + .form { margin-block-start: var(--sp-4); }

/* Validation. Colour is never the only signal — the message itself carries the
   meaning, which is what a screen reader and a colourblind user both get. */
.form-error, .form-ok, .form-warn {
  display: flex; gap: 8px; align-items: flex-start;
  padding: 9px 12px; border-radius: var(--r2);
  font-size: var(--fs-sm); line-height: 1.5;
}
.form-error { background: var(--critical-bg); color: var(--critical); border: 1px solid var(--critical); }
.form-ok    { background: var(--ok-bg); color: var(--ok-fg); border: 1px solid var(--ok); }
.form-warn  { background: var(--warning-bg); color: var(--warning-fg); border: 1px solid var(--warning); }
.form-error ul, .form-ok ul { display: flex; flex-direction: column; gap: 3px; }

/* An EMPTY validation summary must not paint.
   asp-validation-summary renders its div on every request, valid or not — when there is nothing
   wrong it is an empty box carrying `validation-summary-valid`. With `.form-error` on it that is a
   red bar with no text in it, sitting above a form nobody has submitted yet, which reads as "this
   page is broken" before the user has done anything. Hiding it here rather than on each page fixes
   every form in the product at once, including the ones that had it before the issue tracker did. */
.form-error.validation-summary-valid,
.form-ok.validation-summary-valid,
.form-warn.validation-summary-valid { display: none; }

/* --- a message that belongs to the PAGE rather than to a form ----------------
   These three carry no margin of their own, and that is right everywhere they
   were designed to be used: `.form` has `gap`, `.card-body` has the owl, and a
   block that spaced itself as well would space itself twice.

   `.pad` is the one container in the product with NO spacing model — it supplies
   the reading width and the gutter and nothing else. So a message dropped
   straight into a page had nothing at all between it and whatever came next, and
   its border sat on the first panel's border. Two components touching read as one
   broken component, which is exactly how the client reported it.

   It was on EVERY admin screen, not the new one: the read-only `.form-warn` and
   the error block above `.stack` on Settings, Pricing, Plans, Providers, Users
   and Billing all did it. It went unnoticed because those two only appear to a
   Support user or after a failed save, and the screen where it finally showed was
   the one whose happy path ends in a green message.

   It had already been patched twice as an adjacency rule — `.form-warn + .form`
   and `.plans + .form-ok` — and came back on the next screen both times. A rule
   that has to be re-added per page is a missing rule, not a missing patch, so
   this states the whole thing once: at page level these do the same job as
   `.flash` and take the same step.

   Margin collapsing is what keeps it safe. `.pad` is a plain block, so this
   collapses with the next sibling's own `margin-block-start` instead of adding to
   it — the two adjacency patches above still resolve to one gap, not two. The
   `:last-child` reset is for the same reason in the other direction: a margin
   does not collapse through `.pad`'s padding, so a trailing message would
   otherwise push the page's bottom gutter open by an extra step. */
.pad > .form-error, .pad > .form-ok, .pad > .form-warn,
.auth-card > .form-error, .auth-card > .form-ok, .auth-card > .form-warn {
  margin-block-end: var(--sp-5);
}
.pad > .form-error:last-child, .pad > .form-ok:last-child, .pad > .form-warn:last-child,
.auth-card > .form-error:last-child, .auth-card > .form-ok:last-child,
.auth-card > .form-warn:last-child { margin-block-end: 0; }

/* Field-level message, sits directly under the control it belongs to. */
.field-error { font-size: var(--fs-xs); color: var(--critical); font-weight: 550; }

/* A value that cannot be changed, sitting where its input would be.

   Deliberately NOT a disabled <input>: a disabled control posts nothing, so the handler would read
   an empty value and refuse a save that changed nothing about that field. It also has to LOOK
   settled rather than broken - a greyed-out box invites clicking and reads as a fault, whereas
   plain text on the page reads as a fact. The hint underneath says why it is fixed. */
.field-static {
  padding: 7px 10px; border: 1px dashed var(--border-strong); border-radius: var(--r2);
  background: var(--surface-2); color: var(--muted); font-size: var(--fs-sm);
}

/* ---------------------------------------------------------------------------
   FLASH — the result of an action the user just took, at the top of the page.

   Distinct from .form-* on purpose. A validation block belongs to a form and is
   sized by it; a flash belongs to the page. Rendered as .form-ok it stretched
   the full content width, so "OpenAI base URL updated." arrived as five words
   floating in a 1000px empty green bar, which reads as a broken component
   rather than as confirmation. This shrinks to its own text.

   It also has to be dismissible. It is stateless server-side - a refresh brings
   it back - so nothing is lost by closing it, and it stops covering the row the
   operator is about to edit next.
   --------------------------------------------------------------------------- */
.flash {
  display: inline-flex; align-items: flex-start; gap: 8px;
  max-inline-size: 100%; margin-block-end: var(--sp-5);
  padding: 8px 10px 8px 12px; border-radius: var(--r2);
  font-size: var(--fs-sm); line-height: 1.5;
  animation: flash-in .22s cubic-bezier(.2,.8,.3,1) both;
}
.flash-ok   { background: var(--ok-bg); color: var(--ok-fg); border: 1px solid var(--ok); }
.flash-warn { background: var(--warning-bg); color: var(--warning-fg); border: 1px solid var(--warning); }
.flash .gi { inline-size: 16px; block-size: 16px; flex-shrink: 0; margin-block-start: 2px; }
.flash-x {
  margin-inline-start: 6px; color: inherit; opacity: .6; line-height: 1;
  font-size: var(--fs-md); padding: 0 2px; transition: opacity var(--dur-1);
}
.flash-x:hover { opacity: 1; }
@keyframes flash-in { from { opacity: 0; transform: translateY(-7px); } }

/* The plan grid and whatever follows it need air between them. Flush, the notice
   border sat directly on the card borders and on the featured card's 3px ring,
   which looks like two components colliding rather than two stacked. */
.plans + .flash, .plans + .form-warn, .plans + .form-ok { margin-block-start: var(--sp-6); }

/* ---------------------------------------------------------------------------
   ADMIN — Nova's .dt is the data table and .panel is the card. What is missing
   is the row-level editing an admin screen needs: a form living inside a table
   cell, and a two-state toggle button that posts on click.
   --------------------------------------------------------------------------- */
.admin-tabs {
  display: flex; gap: var(--sp-4); overflow-x: auto; scrollbar-width: none;
  border-block-end: 1px solid var(--border); margin-block-end: var(--sp-5);
}
.admin-tabs::-webkit-scrollbar { display: none; }
.admin-tab {
  padding: 8px 1px; font-size: var(--fs-sm); font-weight: 500; color: var(--muted);
  border-block-end: 2px solid transparent; margin-block-end: -1px; white-space: nowrap;
}
.admin-tab:hover { color: var(--text); }
.admin-tab[aria-current="page"] { color: var(--text); font-weight: 600; border-block-end-color: var(--accent); }

.inline-form { display: flex; align-items: center; gap: 5px; }
.inline-form .input { min-inline-size: 0; }

/* The footer of a table that does not fit on one screen.
   It sits INSIDE the .panel, under .dt-scroll, so the count and the controls belong
   visibly to the table above them rather than floating in the page.
   The row count is always shown, even on a single page - "13 of 13" is the answer to
   "is this everything?", which is the question a truncated list leaves an operator
   holding with no way to answer it. */
.pager {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-4); border-block-start: 1px solid var(--border);
  font-size: var(--fs-xs); color: var(--muted);
}
/* A pager standing alone in its own panel - the card list on the pricing screen - has no
   table above it to be divided from, and the rule would sit directly on the panel border. */
.panel > .pager:only-child { border-block-start: 0; }
.pager-count { font-variant-numeric: tabular-nums; }
.pager-nav { display: flex; align-items: center; gap: var(--sp-2); }
.pager-at { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Disabled ends are rendered as spans, not disabled buttons: there is nothing to press
   on the first page, and a dead control that still looks pressable is how a user decides
   the screen is broken. */
.pager-end { opacity: .45; padding: 0 var(--sp-2); }

/* ROWS PER PAGE (backlog 10.1). Sits beside the count, because it is part of the same
   sentence - "showing 1-25 of 340, 25 per page" - and a control at the top of the screen
   would be separated from the thing it changes.

   Deliberately the same pill as .lang-picker rather than a second look for the same kind
   of control: both are a display preference the reader sets for themselves, and every
   comment on that select about resetting appearance, painting a real background and
   stating colour-scheme applies here for exactly the same reasons. */
.pager-size {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  block-size: 26px; padding-inline: var(--sp-1);
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--r2);
}
/* THE ARROW HAD TO BE DRAWN BACK ON.

   `appearance: none` is what lets a <select> take the product's own surface and border in both
   themes — without it, the browser paints its native control and the dark theme gets a light grey
   box. But it also removes the chevron, and a dropdown with no chevron does not read as a dropdown:
   people click it once, see nothing, and stop. That is precisely what the client reported.

   Drawn as an inline SVG data URI on the select itself rather than as a ::after on the wrapper, so
   it stays glued to the control when the row wraps, and it takes `currentColor` so it follows the
   theme without a second rule. */
.pager-size select {
  appearance: none; -webkit-appearance: none;
  background: var(--surface-2); color: var(--text);
  color-scheme: inherit;
  border: 0; outline-offset: 2px;
  font: inherit; font-size: var(--fs-xs);
  padding-inline: var(--sp-1) 20px; padding-block: 0;
  cursor: pointer;

  /* THE SAME CHEVRON AS select.input, down to the grey. There was already a house arrow in this
     file; drawing a second one with different geometry would be exactly the inconsistency the
     client asked to be rid of. Only the size and inset differ, because this control is 26px tall
     rather than a full-height field. */
  background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b91a0' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
  background-repeat: no-repeat;
  background-position: right 4px center;
  background-size: 12px;
}

/* Physical position, because there is no logical `background-position` — same reason as the field
   chevron above. Urdu turns the interface round and an arrow left on the right would be the one
   control that did not follow. */
[dir="rtl"] .pager-size select {
  padding-inline: 20px var(--sp-1);
  background-position: left 4px center;
}
.pager-size select:focus-visible { outline: 2px solid var(--accent); border-radius: var(--r1); }
.pager-size select option { background: var(--surface-2); color: var(--text); }
.pager-size select option:checked { background: var(--accent-soft); color: var(--text); }

/* A posted toggle, not a checkbox: each one is its own form, so a mis-click
   changes one thing and is one click to undo. */
.toggle {
  min-inline-size: 44px; padding: 3px 10px; border-radius: var(--rf);
  font-size: var(--fs-xs); font-weight: 600; border: 1px solid transparent;
  transition: background var(--dur-1), color var(--dur-1), border-color var(--dur-1);
}
.toggle-on  { background: var(--ok-bg); color: var(--ok-fg); border-color: var(--ok); }
.toggle-off { background: var(--surface-3); color: var(--faint); border-color: var(--border-strong); }
.toggle:hover { filter: brightness(.97); }

/* The settings filter: one row, two controls, and nothing else competing with
   the settings themselves. It was a row of fourteen category chips first —
   which wrapped onto two lines and read as a slab of navigation sitting on top
   of the thing the operator came here to edit. A select holds the same fourteen
   in one control's worth of space, and holds twenty just as well. */
.settings-filter { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); margin-block-end: var(--sp-4); }
#setting-search { flex: 1 1 300px; max-inline-size: 460px; }
#setting-cat { flex: 0 0 auto; inline-size: auto; min-inline-size: 190px; }

/* The Providers screen reuses the same row. Its own ids rather than a shared
   class, to match how the settings controls above are already sized. */
#provider-search { flex: 1 1 300px; max-inline-size: 460px; }
#provider-state { flex: 0 0 auto; inline-size: auto; min-inline-size: 190px; }

/* A setting is a label, a description, a control and a reset — four things that
   have to line up down the page or the screen reads as a jumble. */
.setting {
  display: grid; gap: var(--sp-2) var(--sp-4);
  grid-template-columns: minmax(0, 1fr) minmax(220px, 320px);
  padding: var(--sp-4) 0; border-block-start: 1px solid var(--border);
  align-items: start;
}
.setting:first-child { border-block-start: 0; }

/* Set by the filter on the first row still showing. `:first-child` is true of a
   hidden row too, so without this a filtered group opens with a divider above
   nothing. */
.setting.setting-first { border-block-start: 0; }
.setting-name { font-size: var(--fs-sm); font-weight: 600; }
.setting-desc { font-size: var(--fs-xs); color: var(--muted); line-height: 1.5; margin-block-start: 2px; }
.setting-ctl { display: flex; flex-direction: column; gap: 6px; }
.setting-meta { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; font-size: var(--fs-xs); color: var(--faint); }
@media (max-width: 1024px) { .setting { grid-template-columns: 1fr; } }

/* The bar under a panel that submits as a whole, rather than row by row.
   A rule above it, not a gap: the button belongs to the fields it saves, and a
   floating button below a card reads as belonging to the page. */
.panel-foot {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-5) var(--sp-4);
  border-block-start: 1px solid var(--border);
}

/* One step of separation, for the odd control that follows prose inside a
   panel. Deliberately the only spacing utility in this stylesheet - layout is
   done with gap on the container everywhere else, and a set of margin helpers
   is how that stops being true. */
.mt { margin-block-start: var(--sp-3); }

/* ---------------------------------------------------------------------------
   SHARED ODDS AND ENDS
   --------------------------------------------------------------------------- */
.page-head { display: flex; align-items: flex-end; gap: var(--sp-3); flex-wrap: wrap; margin-block-end: var(--sp-5); }
.page-head h1 { font-size: var(--fs-2xl); letter-spacing: -.024em; }
/* Capped at a readable measure. Left alone these run the full 1180px of the content column,
   and the admin pages carry the longest explanations in the product — the Plans warning about
   frozen versions is three sentences the operator has to actually read, and at ~150 characters
   a line the eye loses its place returning to the left edge. */
.page-head p { font-size: var(--fs-sm); color: var(--muted); margin-block-start: 3px; max-inline-size: 78ch; }

.stack { display: flex; flex-direction: column; gap: var(--sp-4); }
.row   { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.xs    { font-size: var(--fs-xs); }
.sm    { font-size: var(--fs-sm); }
.faint { color: var(--faint); }
.nowrap { white-space: nowrap; }
.block { display: block; }

/* A list inside a message block rather than in a document. A browser's default list margin was
   written for prose and pushes the first item away from the sentence introducing it, which reads as
   two unrelated things rather than one point and its examples. */
.tight { margin: 4px 0 0; padding-inline-start: 1.2em; }
.tight > li + li { margin-block-start: 2px; }
.tabular { font-variant-numeric: tabular-nums; }

/* Every wide table gets its own scroller so the PAGE never scrolls sideways. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.empty {
  padding: var(--sp-8) var(--sp-5); text-align: center;
  border: 1px dashed var(--border-strong); border-radius: var(--r3);
  color: var(--muted); font-size: var(--fs-sm);
}
.empty b { display: block; color: var(--text); font-size: var(--fs-md); margin-block-end: 4px; }

/* --- the chat welcome, which is NOT an empty state ------------------------------------------
   A NEW CONVERSATION IS NOT A LIST WITH NOTHING IN IT.

   This screen used .empty, and .empty means "there is nothing here" — hence the dashed border,
   which is the right way to draw an absent thing. On a welcome screen it reads as a placeholder
   somebody forgot to finish.

   IT ALSO MADE THE OVERFLOW LOOK LIKE A FAULT. The starter prompts are admin data: seven
   categories and eighteen prompts today, and an administrator may add more. At the client's
   window height the block is 441px inside a 355px pane, so it overruns by 182px. The thread
   scrolls, so nothing is unreachable — but a BORDERED box sliced through the middle reads as
   broken, while the same content flowing off the fold reads as ordinary. Removing the border is
   most of the fix, and it is the half that holds however many prompts get configured.

   The padding comes down too. sp-8 top and bottom is generous for a two-line empty list and
   costs about seventy pixels here, which is the difference between one more row fitting and not.

   WHAT IS DELIBERATELY NOT DONE: capping the categories in CSS or in the markup. How many to
   show is the administrator's decision, made on /admin/starter-prompts (D-18), and hard-coding a
   limit here would quietly overrule a screen the client already controls. */
.welcome {
  padding: var(--sp-5) var(--sp-5) var(--sp-6); text-align: center;
  color: var(--muted); font-size: var(--fs-sm);
}
.welcome b { display: block; color: var(--text); font-size: var(--fs-md); margin-block-end: 4px; }

/* A label/value list. Two columns that stay aligned down the card, because an
   operator reads these by scanning the values, not the labels. */
.kv { display: grid; grid-template-columns: minmax(96px, auto) 1fr; gap: 6px var(--sp-4); font-size: var(--fs-sm); }
.kv dt { color: var(--muted); }
.kv dd { margin: 0; min-inline-size: 0; overflow-wrap: anywhere; }

/* Master list beside a detail card. Collapses to one column before the two get
   too narrow to read rather than at an arbitrary device width. */
.admin-split { display: grid; gap: var(--sp-4); grid-template-columns: minmax(0, 1fr) minmax(320px, 420px); align-items: start; }
@media (max-width: 1024px) { .admin-split { grid-template-columns: 1fr; } }

/* ---------------------------------------------------------------------------
   DRAWER

   A master list with the detail over the top of it, rather than beside it.

   The users screen was a two-column split, and the split was costing both halves: the detail column
   held a fixed 320-420px of the grid whether or not anything was in it, so the table ran at two
   thirds width permanently, and the detail it made room for was too narrow for the forms that
   belong in it. Every account action added made the squeeze worse in both directions.

   Driven entirely by the URL — the drawer is rendered when `?id=` is present, and closing it is a
   link back to the list. That means it survives a reload, it can be sent to a colleague, the back
   button closes it, and the POST-redirect-GET after every action lands with the drawer still open
   on the account just changed. A drawer held open by a JavaScript flag does none of those.
   --------------------------------------------------------------------------- */
.drawer-scrim {
  /* Above the sidebar (180), the topbar (200) and the bell popover (210). A scrim that loses to
     the navigation dims the page while leaving the nav bright and clickable, which tells the user
     the opposite of what a scrim is for. */
  position: fixed; inset: 0; z-index: 220;
  /* Same wash the command palette uses, so the two overlays in this product do not read as two
     different mechanisms. */
  background: rgb(0 0 0 / .38);
  animation: scrim-in var(--dur-2) var(--ease) both;
}
@keyframes scrim-in { from { opacity: 0; } }

.drawer {
  position: fixed; inset-block: 0; inset-inline-end: 0; z-index: 221;
  inline-size: min(560px, 100vw);
  display: flex; flex-direction: column;
  background: var(--surface); border-inline-start: 1px solid var(--border);
  box-shadow: var(--sh-3);
  animation: drawer-in var(--dur-3) var(--ease-io) both;
}
@keyframes drawer-in { from { transform: translateX(24px); opacity: 0; } }

/* Reduced motion gets the drawer, just not the travel. Removing the animation entirely would be
   fine; removing the element would not. */
@media (prefers-reduced-motion: reduce) {
  .drawer, .drawer-scrim { animation-duration: 1ms; }
}

/* Sticky, because the close button is the one control that must never scroll away — a panel
   covering the screen with its exit somewhere below the fold is the definition of trapped. */
.drawer-head {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  padding: var(--sp-5); border-block-end: 1px solid var(--border);
  background: var(--surface); flex-shrink: 0;
}
.drawer-head h2 { font-size: var(--fs-lg); letter-spacing: -.018em; overflow-wrap: anywhere; }

.drawer-body { flex: 1; overflow-y: auto; padding: var(--sp-5); scrollbar-width: thin; }
.drawer-body > * + * { margin-block-start: var(--sp-5); }

/* Sections inside the drawer, separated by a rule rather than by nested cards. Cards inside a
   panel inside a drawer is three borders deep and reads as clutter at this width. */
.drawer-sec { border-block-start: 1px solid var(--border); padding-block-start: var(--sp-4); }
.drawer-sec > h3 { font-size: var(--fs-sm); font-weight: 650; margin-block-end: var(--sp-3); }
.drawer-sec > * + * { margin-block-start: var(--sp-4); }

/* One role and its explanation, with the grant/remove control pushed to the trailing edge. */
.role-row { display: flex; align-items: flex-start; gap: var(--sp-3); padding-block: var(--sp-2); }
.role-row > div:first-child { min-inline-size: 0; }
.role-row p { margin: 1px 0 0; font-size: var(--fs-xs); color: var(--muted); max-inline-size: 46ch; }

.card-head { display: flex; align-items: flex-start; gap: var(--sp-3); margin-block-end: var(--sp-4); }
.card-head h3 { font-size: var(--fs-md); }
.card-title-sub { font-size: var(--fs-xs); color: var(--faint); margin-block-start: 1px; }
.card-body { padding: var(--sp-5); }
.card-body > * + * { margin-block-start: var(--sp-4); }

/* The selected row in a master list. A background alone is too easy to miss on a
   long table, so it also carries a bar on the leading edge. */
.dt tr.is-selected > * { background: var(--accent-soft); }
.dt tr.is-selected > *:first-child { box-shadow: inset 3px 0 0 var(--accent); }


/* ==========================================================================
   Checkout, invoices and billing (D-187, D-188)
   ========================================================================== */

.btn-block { inline-size: 100%; justify-content: center; }

/* Reserved for actions that take something away. Deliberately an outline rather
   than a filled red button: cancelling is a legitimate thing a customer may
   want, and a shouting red block reads as a warning not to. */
.btn-danger { background: var(--surface); border-color: var(--critical); color: var(--critical); }
.btn-danger:hover { background: var(--critical-bg); }

.section { margin-block-start: var(--sp-7); }
.section > h2 { font-size: var(--fs-lg); margin-block-end: var(--sp-4); }
.centred { text-align: center; }

/* --- usage & analytics -----------------------------------------------------
   One measure, one colour, thin marks, a recessive baseline. Nothing here needs
   a legend: each chart has exactly one series and the heading names it. */
.statgrid {
  display: grid; gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.stat { display: flex; flex-direction: column; gap: 3px; padding: var(--sp-4) var(--sp-5); }
.stat > b { font-size: var(--fs-2xl); font-weight: 650; letter-spacing: -.022em; line-height: 1.15; }

.chartbox { padding: var(--sp-5); }
.bars {
  display: flex; align-items: flex-end; gap: 2px;   /* 2px of surface between fills */
  block-size: 132px; border-block-end: 1px solid var(--border);
}
.bar-col { flex: 1; min-inline-size: 0; block-size: 100%; display: flex; align-items: flex-end; }
.bar {
  inline-size: 100%; background: var(--accent);
  /* Rounded at the data end only. The baseline end is anchored to the axis, and
     rounding it there would lift the bar off its own zero line. */
  border-start-start-radius: 4px; border-start-end-radius: 4px;
  transition: opacity var(--dur-1);
}
.bar-col:hover .bar { opacity: .68; }
.bars-axis { display: flex; justify-content: space-between; margin-block-start: 6px; }

/* The failed share of a day, painted from the baseline up INSIDE that day's own bar (§141).
   Inside rather than beside, because the question is "what proportion of this day's traffic
   failed", and two bars side by side answer "which day was busier" instead. */
.bar { position: relative; }
.bar-bad {
  position: absolute; inset-inline: 0; inset-block-end: 0;
  background: var(--critical);
  /* No rounding: this sits at the baseline end, where the bar is square anyway. */
}

.datatable { margin-block-start: var(--sp-4); }
.datatable > summary {
  cursor: pointer; font-size: var(--fs-sm); color: var(--muted); padding-block: var(--sp-2);
}
.datatable > summary:hover { color: var(--text); }

.dt .meter { min-inline-size: 84px; }

/* --- checkout -------------------------------------------------------------
   The order summary sits BESIDE the form on a wide screen and ABOVE it on a
   narrow one. Below the form it would be under the pay button, where nobody
   reads it - and what they are buying has to be visible while they decide. */
.checkout { display: grid; gap: var(--sp-6); grid-template-columns: minmax(0, 1fr) minmax(280px, 340px); align-items: start; max-inline-size: 940px; margin-inline: auto; }
@media (max-width: 1024px) {
  .checkout { grid-template-columns: 1fr; }
  .checkout-summary { order: -1; }
}
.checkout-form h1 { font-size: var(--fs-2xl); letter-spacing: -.024em; margin-block-end: var(--sp-2); }
.checkout-form > .hint { display: block; margin-block-end: var(--sp-4); }
.checkout-form .form { margin-block-start: var(--sp-5); }

.checkout-summary { border: 1px solid var(--border); border-radius: var(--r3); background: var(--surface-2); padding: var(--sp-5); }
.checkout-summary h2 { font-size: var(--fs-md); }
.checkout-summary hr { margin-block: var(--sp-4); border: 0; border-block-start: 1px solid var(--border); }
.sum-line { display: flex; justify-content: space-between; gap: var(--sp-3); font-size: var(--fs-sm); color: var(--muted); margin-block-start: var(--sp-3); }
.sum-total { font-size: var(--fs-md); color: var(--text); }

/* A discount reads as money coming OFF, so it is the one line in the summary
   that is not ink-coloured. */
.sum-off { color: var(--ok); }

/* The promo code: a field and its Apply button on one line, with the verdict
   underneath rather than beside it — a message that pushes the button around
   makes the row jump every time somebody types. */
.promo { display: grid; gap: var(--sp-2); margin-block-start: var(--sp-2); }
.promo-row { display: flex; gap: var(--sp-2); align-items: center; }
.promo-row .input { flex: 1 1 auto; text-transform: uppercase; }
.promo-row .btn { flex: 0 0 auto; }
.promo .sm { font-size: var(--fs-xs); padding: 6px 9px; }

/* Expiry and CVC are one field to a human. Two boxes on one line say so. */
.form-pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }

/* The demo notice. Quiet enough not to compete with the form, loud enough that
   nobody types a card they actually own into this. */
.testcards { margin-block-start: var(--sp-5); padding: var(--sp-4); border: 1px dashed var(--border-strong); border-radius: var(--r2); font-size: var(--fs-xs); color: var(--muted); line-height: 1.6; }
.testcards b { color: var(--text); }
.testcard-table { margin-block-start: var(--sp-3); border-collapse: collapse; }
.testcard-table td { padding: 3px var(--sp-3) 3px 0; vertical-align: top; }
.testcard-table td:first-child { color: var(--text); white-space: nowrap; }

/* --- payment gateways (§119) ----------------------------------------------
   Both gateways currently run the same simulation, so the ONLY thing telling a
   tester the choice was carried through is what they can see. --gw is the
   gateway's brand colour, set inline from the descriptor; it is used as a left
   rule and a hover border rather than a fill, because a large block of a
   vendor's brand colour on our page reads as their button, not ours. */
.gateway-pick { display: grid; gap: var(--sp-3); margin-block-start: var(--sp-4); }
.gateway-opt { display: flex; align-items: center; gap: var(--sp-4); padding: var(--sp-4); border: 1px solid var(--border); border-inline-start: 3px solid var(--gw, var(--border-strong)); border-radius: var(--r2); background: var(--surface-2); text-decoration: none; color: inherit; transition: border-color .12s ease, background .12s ease; }
.gateway-opt:hover, .gateway-opt:focus-visible { border-color: var(--gw, var(--border-strong)); border-inline-start-color: var(--gw, var(--border-strong)); background: var(--surface-3); }
.gateway-text { min-inline-size: 0; }
.gateway-name { display: block; font-weight: 600; color: var(--text); }
.gateway-blurb { display: block; margin-block-start: 2px; font-size: var(--fs-sm); color: var(--muted); }

/* The mark. A TINT of the gateway's colour behind line art in the colour
   itself - a solid block of a vendor's brand colour reads as their button
   rather than ours, and would fight the pay button two rows below. */
.gateway-icon { flex: none; display: grid; place-items: center; inline-size: 40px; block-size: 40px; border-radius: var(--r2); background: color-mix(in srgb, var(--gw, var(--border-strong)) 15%, transparent); color: var(--gw, var(--text)); }
.gateway-icon svg { inline-size: 22px; block-size: 22px; }

/* The trailing note. Its own top gap because the chooser above it is a set of
   cards, and a line of small text sitting straight underneath one reads as
   part of that card rather than as a comment on all of them. */
.gateway-note { margin-block-start: var(--sp-4); }

/* On the form itself: a strip naming the gateway, with the way back to the
   chooser pushed to the far end. Small on purpose - it is a reminder, not a
   heading. */
.gateway-badge { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-2) var(--sp-3); border: 1px solid var(--border); border-inline-start: 3px solid var(--gw, var(--border-strong)); border-radius: var(--r2); background: var(--surface-2); }
.gateway-badge .gateway-name { font-size: var(--fs-sm); }
.gateway-badge .gateway-icon { inline-size: 28px; block-size: 28px; border-radius: var(--r1); }
.gateway-badge .gateway-icon svg { inline-size: 17px; block-size: 17px; }
.gateway-badge .hint { margin-inline-start: auto; }

/* The pay button keeps its own shape and takes the gateway's colour only as an
   outline, so a mis-set --gw can never produce unreadable text on a button. */
.gateway-pay { box-shadow: inset 0 0 0 1px var(--gw, transparent); }

/* --- the administration landing page ---------------------------------------
   Three groups that wrap rather than a fixed two-column grid: the groups are
   named after jobs, and a job should not be pushed onto its own line because
   the column count says so. */
/* align-items: start pins every card to the top of the row and lets each be its
   own height. Without it the cards are centred against the tallest in the row,
   so a group with one fewer link sits a few pixels lower than its neighbour —
   which reads as a layout fault rather than as different content. */
.adm-grid { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); align-items: start; margin-block-start: var(--sp-4); }

/* Six figures, in rows that divide evenly. `auto-fit` fits as many as will go,
   which at most desktop widths is five — leaving one card alone on a second row
   looking like something failed to load. Fixed counts per breakpoint always
   divide 6. */
.statgrid-6 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (min-width: 1240px) { .statgrid-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); } }
@media (max-width: 720px)  { .statgrid-6 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.adm-links { display: flex; flex-direction: column; gap: 2px; }

/* A whole row is the target, not just the title — a two-line link whose second
   line is not clickable is a link people miss on a phone. */
.adm-link { display: block; padding: var(--sp-2) var(--sp-3); margin-inline: calc(var(--sp-3) * -1); border-radius: var(--r2); text-decoration: none; color: inherit; }
.adm-link:hover, .adm-link:focus-visible { background: var(--surface-3); }
.adm-link > b { display: block; font-size: var(--fs-sm); font-weight: 600; }
.adm-link > span { display: block; font-size: var(--fs-xs); color: var(--muted); line-height: 1.4; }

/* --- billing --------------------------------------------------------------- */
.billcard { display: grid; grid-template-columns: minmax(0, 1fr) minmax(240px, 300px); gap: var(--sp-5); padding: var(--sp-5); }
.billcard-main h2 { font-size: var(--fs-xl); letter-spacing: -.02em; margin-block: 2px var(--sp-2); }
.billcard-side { display: flex; flex-direction: column; gap: 2px; padding-inline-start: var(--sp-5); border-inline-start: 1px solid var(--border); }

/* One block, not two at the same width. The divider has to change with the
   column — a vertical rule between two stacked blocks is a line to nowhere. */
@media (max-width: 1024px) {
  .billcard { grid-template-columns: 1fr; }
  .billcard-side {
    padding-inline-start: 0; border-inline-start: 0;
    border-block-start: 1px solid var(--border); padding-block-start: var(--sp-4);
  }
}
.billcard-acts { margin-block-start: auto; padding-block-start: var(--sp-4); }
.billstat { display: flex; align-items: baseline; gap: 8px; margin-block-start: var(--sp-4); }
.billstat .n { font-size: var(--fs-2xl); font-weight: 650; letter-spacing: -.02em; }
.billstat .u { font-size: var(--fs-sm); color: var(--muted); }

/* --- credit top-ups --------------------------------------------------------
   auto-fit rather than a fixed three, because the number of packages is master
   data an admin can change. Three cards hard-coded into a grid become two cards
   and a gap the day somebody disables one. */
.packs {
  display: grid; gap: var(--sp-3); margin-block-start: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.pack {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: var(--sp-4); border: 1px solid var(--border); border-radius: var(--r3);
  background: var(--surface);
}

/* The featured pack is marked by its border and its button, never by being
   bigger - a card that outgrows its neighbours stops them being comparable,
   which is the one job a row of prices has. */
.pack.on { border-color: var(--accent); }
.pack > .tag { margin-block-end: var(--sp-2); }
.pack-n { font-size: var(--fs-xl); font-weight: 650; letter-spacing: -.02em; line-height: 1.1; }
.pack-u { font-size: var(--fs-sm); color: var(--muted); }
.pack > p { margin-block: var(--sp-2) 0; }
.pack-price {
  display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap;
  margin-block: var(--sp-3) var(--sp-3);
}
.pack-price b { font-size: var(--fs-lg); }

/* Pushed to the bottom edge so the expiry line sits on the same baseline across
   every card, whether or not the pack above it had a description. */
.pack-exp { margin-block-start: var(--sp-2); }
.pack .btn { margin-block-start: auto; }

/* Collapsed by default. Cancelling must be findable in one click and must not be
   the most prominent thing on the page - both at once, which is what a summary
   row does and a modal does not. */
.cancel-box { margin-block-start: var(--sp-5); border: 1px solid var(--border); border-radius: var(--r2); }
.cancel-box > summary { padding: var(--sp-3) var(--sp-4); cursor: pointer; font-size: var(--fs-sm); color: var(--muted); }
.cancel-box > summary:hover { color: var(--text); }
.cancel-box .form { padding: 0 var(--sp-4) var(--sp-4); }

/* --- the invoice document --------------------------------------------------
   Styled as a document rather than as an app screen: it gets printed, and the
   print rules below strip everything that is not the document itself. */
.invoice-page { max-inline-size: 760px; margin-inline: auto; }
.invoice-actions { display: flex; align-items: center; gap: var(--sp-2); margin-block-end: var(--sp-4); }
.invoice { border: 1px solid var(--border); border-radius: var(--r3); background: var(--surface); padding: var(--sp-7); }
.invoice-head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-5); flex-wrap: wrap; margin-block-end: var(--sp-6); }
.invoice-head .nav-mark { margin-inline-end: 6px; }
.invoice-no { display: flex; flex-direction: column; align-items: flex-end; gap: 1px; text-align: end; }
.invoice-parties { display: flex; justify-content: space-between; gap: var(--sp-5); flex-wrap: wrap; margin-block-end: var(--sp-6); }
.invoice-parties > div { display: flex; flex-direction: column; gap: 1px; }
.invoice-lines { inline-size: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.invoice-lines th { text-align: start; font-size: var(--fs-micro); font-weight: 650; letter-spacing: .07em; text-transform: uppercase; color: var(--faint); padding-block-end: var(--sp-2); border-block-end: 1px solid var(--border); }
.invoice-lines td { padding: var(--sp-3) 0; vertical-align: top; }
.invoice-lines .num { text-align: end; }
.invoice-lines tfoot td { border-block-start: 1px solid var(--border); }
.invoice-lines tfoot .total td { font-size: var(--fs-md); font-weight: 650; border-block-start: 2px solid var(--border-strong); }
.invoice-foot { display: flex; flex-direction: column; gap: 3px; margin-block-start: var(--sp-6); padding-block-start: var(--sp-4); border-block-start: 1px solid var(--border); font-size: var(--fs-sm); color: var(--muted); }

/* ---------------------------------------------------------------------------
   MODEL PICKER  (the dock, and the landing page's prompt box)
   --------------------------------------------------------------------------- */

/* Auto and a named model are styled differently on purpose. Auto is the product's
   recommendation and wears the accent; a hand-picked model is a deliberate override
   and wears the neutral surface, so the badge says at a glance which of the two you
   are in without having to read the name. */
.mpick { position: relative; display: inline-flex; }
.mpick-btn {
  display: inline-flex; align-items: center; gap: 6px; max-inline-size: 100%;
  padding: 3px 7px 3px 8px; border-radius: var(--r1);
  font-size: var(--fs-xs); font-weight: 550; white-space: nowrap;
  border: 1px solid transparent; transition: background var(--dur-1), border-color var(--dur-1);
}
.mpick-btn.is-auto   { background: var(--accent-soft); color: var(--accent); }
.mpick-btn.is-manual { background: var(--surface-3); color: var(--text); }
.mpick-btn:hover     { border-color: var(--border-strong); }
.mpick-btn:disabled  { opacity: .55; cursor: not-allowed; }
.mpick-btn:disabled:hover { border-color: transparent; }
.mpick-ico  { inline-size: 13px; block-size: 13px; flex-shrink: 0; }
.mpick-chev { inline-size: 12px; block-size: 12px; flex-shrink: 0; opacity: .65; }
.mpick-name { overflow: hidden; text-overflow: ellipsis; }

/* The provider line is the first thing to go when the badge is squeezed — on a phone
   the model's own name is what identifies it, and the vendor is in the open list. */
.mpick-sub { color: var(--muted); font-weight: 450; }
@media (max-width: 560px) { .mpick-sub { display: none; } }

/* Painted in the TOP LAYER, not in the page.

   Both buttons that open this live inside a box with `overflow: hidden` — the landing page's
   prompt box and the workspace dock, where the overflow is what keeps a textarea's square
   corners inside the rounded border around it. An absolutely-positioned menu was clipped to a
   sliver with its own scrollbar. `position: fixed` would escape the overflow but not a stacking
   context, so it would start clipping again the day any ancestor grew a transform.

   `popover` puts it outside the document's paint order entirely: nothing can clip it and no
   z-index can lose to anything. The trade is that the browser no longer positions it, so the
   coordinates come from useAnchoredPopover and arrive as inline styles — which is why every UA
   default below has to be turned off explicitly. */
.mpick-pop, .mpick-pop:popover-open {
  position: fixed; inset: auto; margin: 0; padding: 0;
  inline-size: min(340px, 78vw);
  display: flex; flex-direction: column;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--r3); box-shadow: var(--sh-2); overflow: hidden;
}

.mpick-search { padding: var(--sp-2); border-block-end: 1px solid var(--border); flex-shrink: 0; }
/* The cap is the ROOM the popover was given, not a share of the window: a menu opening upward
   out of the dock has a few hundred pixels above it, and 44vh would have overshot the top. */
.mpick-list { flex: 1 1 auto; min-block-size: 0; overflow-y: auto; padding: var(--sp-1); }

.mpick-opt {
  display: grid; grid-template-columns: 1fr auto; align-items: center;
  inline-size: 100%; text-align: start; gap: 0 var(--sp-2);
  padding: 6px 8px; border-radius: var(--r2); color: var(--text);
}
.mpick-opt-main { font-size: var(--fs-sm); font-weight: 550; display: inline-flex; align-items: center; gap: 6px; }
.mpick-opt-meta { grid-column: 1; font-size: var(--fs-micro); color: var(--faint); }

/* The sentence an admin wrote. Wraps, unlike the two lines above it, because it is prose and a
   truncated description is worse than none - the half that fits is the half that says least. */
.mpick-opt-desc {
  grid-column: 1; font-size: var(--fs-micro); color: var(--muted);
  white-space: normal; line-height: 1.35; margin-block-start: 2px;
}

/* Spans three rows, not two, so the tick stays centred against the description as well. A row
   with nothing in it collapses to zero, so this is unchanged for a model that has no description. */
.mpick-tick { grid-row: 1 / span 3; align-self: center; inline-size: 14px; block-size: 14px; color: var(--accent); }

/* One highlight, driven by the cursor, and the cursor follows the mouse. A separate
   :hover rule would light a second row up whenever the pointer rested somewhere the
   keyboard had not, and then Enter and the click would disagree about the target. */
.mpick-opt.is-cursor { background: var(--surface-3); }
.mpick-opt:hover:not(:disabled) { background: var(--surface-3); }
.mpick-opt:disabled { opacity: .6; }

/* Narrower than the model list: two rows of a word and a short note. The edge it lines up
   against is decided when it opens, not here. */
.expmenu { inline-size: min(260px, 78vw); }
/* --critical, not `var(--danger, …)`. --danger has never been declared, so this
   was always painting its fallback — a hard-coded red that answers to neither
   theme and is in no token. */
.expmenu .mpick-none { color: var(--critical); }
.mpick-opt.is-autorow .mpick-opt-main { color: var(--accent); }
/* The "Images" marker on a row. A full-size tag would be taller than the line it sits on and push
   every other row in the list apart to accommodate one of them. */
.mpick-opt-main .tag { font-size: var(--fs-micro); padding: 0 5px; line-height: 1.5; }
/* Sits below the scrolling list, so it must not be the thing that shrinks: an error explaining
   why the download failed is the one line in the menu that has to survive. */
.mpick-none { padding: var(--sp-3); margin: 0; flex-shrink: 0; }

/* --- notifications ---------------------------------------------------------
   The bell in the topbar, its panel, and the same rows again on the full page.
   One .bell-item rule serves both, so the panel and the page cannot drift into
   two different-looking versions of the same list. */
.bell { position: relative; }
/* The count sits ON the bell rather than beside it. A badge that reserves its own
   column shifts the whole toolbar the first time a notification arrives.

   IT SITS ON THE CORNER, NOT IN THE MIDDLE, and that distinction was a real bug.

   The first version anchored it 2px INSIDE the top-right corner. One digit is 15px
   wide and sat neatly in the corner, which is the state it was designed against.
   But the badge is sized by its content and anchored on its right edge, so a
   two-character count — "9+", or any number past nine — grows LEFTWARD across the
   button. Measured at "9+": a 23px badge over a 28px button, centred on a 16px
   icon, covering roughly seven tenths of it. The bell disappeared and what the
   customer saw was a red blob crowding the language control 7px to its right.

   Anchoring it OUTSIDE the corner means growth moves away from the icon rather than
   over it, which is how a badge is meant to behave and how every other one behaves.
   The overhang is 2px, chosen because the toolbar leaves 7px before the language
   picker — enough to sit clear of it, not enough to touch.

   The client had 9+ notifications permanently, so he saw the broken state
   permanently, and it never showed up here because nobody tested this with more
   than one notification. */
.bell-n {
  position: absolute; inset-block-start: -2px; inset-inline-end: -2px;
  min-inline-size: 14px; block-size: 14px; padding-inline: 2px;
  display: grid; place-items: center;
  font-size: 9px; font-weight: 700; line-height: 1;
  color: #fff; background: var(--critical); border-radius: var(--rf);
  border: 2px solid var(--surface);
}

.bell-menu {
  position: absolute; inset-block-start: calc(100% + 8px); inset-inline-end: 0; z-index: 60;
  inline-size: min(360px, calc(100vw - var(--sp-5) * 2));
  display: flex; flex-direction: column;
  border: 1px solid var(--border); border-radius: var(--r3);
  background: var(--surface); box-shadow: var(--sh-3); overflow: hidden;
}
.bell-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-block-end: 1px solid var(--border);
}

/* Capped and scrollable. A panel that grows with the list eventually runs off the
   bottom of the screen, taking its own "see all" link with it. */
.bell-list { max-block-size: min(400px, 58vh); overflow-y: auto; }
.bell-empty { padding: var(--sp-5) var(--sp-4); margin: 0; color: var(--muted); text-align: center; }

.bell-item {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-block-end: 1px solid var(--border);
  cursor: pointer;
}
.bell-item:last-child { border-block-end: 0; }
.bell-item:hover { background: var(--surface-2); }
/* Tinted background AND a bar, not colour alone: unread is a state a colourblind
   reader has to be able to see, and the bar survives both themes. */
.bell-item.unread { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }
.bell-item.unread:hover { background: var(--accent-soft); filter: brightness(0.98); }

.bell-ico { flex-shrink: 0; margin-block-start: 1px; color: var(--muted); }
.bell-item.unread .bell-ico { color: var(--accent); }
.bell-body { flex: 1; min-inline-size: 0; }
.bell-t { font-weight: 600; line-height: 1.35; }
.bell-body > p {
  margin: 2px 0 0; font-size: var(--fs-sm); color: var(--muted); line-height: 1.45;
}
.bell-meta {
  display: flex; align-items: center; gap: var(--sp-3);
  margin-block-start: var(--sp-2); font-size: var(--fs-sm);
}
.bell-meta a { font-weight: 600; }

/* Revealed on hover, present always for keyboard and touch, where there is no hover
   to reveal it with. */
.bell-x { flex-shrink: 0; opacity: 0; transition: opacity var(--dur-1); }
.bell-item:hover .bell-x, .bell-x:focus-visible { opacity: 1; }
@media (hover: none) { .bell-x { opacity: 1; } }

.bell-foot {
  display: block; padding: var(--sp-3); text-align: center;
  font-size: var(--fs-sm); font-weight: 600;
  border-block-start: 1px solid var(--border); background: var(--surface-2);
}

/* Self-service account screens. These used to be capped at 640px on the reasoning that a short
   form does not want a wide page. What that actually produced was a 640px column inside an
   1180px one on a 1400px screen — the same shell, the same padding, the same cards, at a
   different width from every other screen in the app, which reads as a page that failed to load
   rather than as a deliberately narrow form.

   So the page is now the same width as everything else and the CAP MOVED TO THE FIELDS, which
   is where it belonged: a name is a name whatever the window is doing, and a 1100px-wide text
   box is a worse thing to hand somebody than a wide page. Anything genuinely wide — a switch
   list, a table — is unaffected, because only inputs are capped. */
.account-page .form-row > .input { max-inline-size: 46rem; }

/* The profile photo at reading size, and the initials that stand in for it.

   Deliberately NOT a modifier on .avatar: that one is a 26px BUTTON in the rail — it carries a
   cursor, a hover shadow and a focus ring, all of which are wrong on a picture that does nothing
   when you click it. Sharing the class would mean overriding four properties to get back to a
   plain image.

   object-fit: cover is what makes an ordinary photograph usable. A portrait from a phone dropped
   into a square box is otherwise squashed into a funhouse mirror, and the person it squashes is
   the user looking at it. */
.avatar-lg {
  inline-size: 72px; block-size: 72px; border-radius: var(--rf); flex-shrink: 0;
  object-fit: cover; display: block;
  border: 1px solid var(--border-strong); background: var(--surface-2);
}
.avatar-lg.is-empty {
  display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent); border-color: var(--accent);
  font-size: var(--fs-xl); font-weight: 700; line-height: 1;
}

/* A real list, for the one screen with genuine consequences to enumerate. list-style is restored
   because the reset removes it globally — a warning where the reader has to work out that five
   sentences are five separate outcomes is a warning that does not land. */
.bullets { padding-inline-start: var(--sp-5); }
.bullets li { list-style: disc; margin-block-start: var(--sp-2); }
.bullets li::marker { color: var(--faint); }

/* The full page reuses the rows, inside a card instead of a popover. No width cap of its own —
   see .account-page above for why these screens stopped being narrower than the rest of the app.
   The wrapper stays because the feed and the empty state want to be one addressable block. */
.notif-list { border: 1px solid var(--border); border-radius: var(--r3);
  background: var(--surface); overflow: hidden; }
.notif-list .bell-item { padding: var(--sp-4) var(--sp-5); }

/* The switch rows, now on their own page inside .panel-body. No margin of its own and no
   rules about the form around it: the form wraps two panels rather than sitting inside one,
   so anything positioning a button "inside .notif-prefs" would position nothing. */
.notif-prefs .row { display: flex; align-items: flex-start; gap: var(--sp-3);
  padding-block: var(--sp-3); border-block-end: 1px solid var(--border); }
.notif-prefs .row:last-of-type { border-block-end: 0; }
.notif-prefs .row > div { flex: 1; min-inline-size: 0; }
/* Capped, not stretched. The row is as wide as the page now, but a sentence of explanation
   set across 1100px is one the eye loses its place in on the way back to the left margin. */
.notif-prefs .row p { margin: 1px 0 0; font-size: var(--fs-sm); color: var(--muted);
  max-inline-size: 78ch; }
.notif-prefs .row .check { align-items: center; flex-shrink: 0; }

/* A sub-page's way back, above the heading. Muted, because it is an exit and not the thing
   the page is for. */
.back-link { display: inline-block; font-size: var(--fs-sm); color: var(--muted);
  margin-block-end: 2px; }
.back-link:hover { color: var(--text); }

@media print {
  /* Everything that is not the document. A printed invoice with a navigation
     bar across the top is the mark of a page nobody tested on paper. */
  .noprint, .topbar, .nav, .appnav, .sidebar, .footer { display: none !important; }
  .invoice { border: 0; padding: 0; }
  .invoice-page { max-inline-size: none; }
  /* The shell's own scroller and gutter. On paper they become a wasted top margin and a
     clipped page, because the printer is already supplying both. */
  .app, .appmain, .screen, .scrollarea { display: block !important; overflow: visible !important; }
  .pad { padding: 0; max-inline-size: none; }
  body { background: #fff; color: #000; }
}

/* ---------------------------------------------------------------------------
   LANGUAGE SWITCHER (S117)

   Sits with theme and accent in .auth-appear, because it is the same kind of
   thing: something you set once about how the page is presented, not a step in
   signing in. Styled to match the icon buttons beside it rather than as a form
   control, so the three read as one cluster.

   The whole block only ever applies when more than one language is enabled —
   the partial renders nothing at all otherwise.
   --------------------------------------------------------------------------- */
.lang-picker {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  block-size: 32px; padding-inline: var(--sp-2);
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--r2);
}
.lang-picker .lang-ico { display: inline-flex; color: var(--faint); }
.lang-picker .lang-ico .gi { inline-size: 15px; block-size: 15px; }

/* A bare select: the pill around it is the border, so a second one inside is a
   box in a box. Appearance is reset because the native control paints a grey
   well that ignores the dark theme entirely.

   The background is a real colour and NOT transparent, which is the whole fix
   for the dropdown that opened white-on-white in dark mode. Styling an `option`
   at all makes Chromium stop deferring to the OS popup and paint its own — and
   it paints that popup using the SELECT's background, where `transparent`
   resolves against white. The closed pill looked correct the entire time, which
   is why this only showed up once the list was opened.

   colour-scheme is stated as well so the scrollbar inside a long list, which is
   drawn by the browser and reads no CSS of ours, matches the popup it sits in. */
.lang-picker select {
  appearance: none; -webkit-appearance: none;
  background: var(--surface-2); color: var(--text);
  color-scheme: inherit;
  border: 0; outline-offset: 2px;
  font: inherit; font-size: var(--fs-xs);
  padding-inline: var(--sp-1) var(--sp-2); padding-block: 0;
  cursor: pointer; max-inline-size: 140px;
}
.lang-picker select:focus-visible { outline: 2px solid var(--accent); border-radius: var(--r1); }

/* Each row, in case a build paints rows independently of the popup surface. */
.lang-picker select option { background: var(--surface-2); color: var(--text); }

/* The selected row is drawn with the UA's own highlight, which in dark mode is a
   pale blue that the accent already covers better. */
.lang-picker select option:checked { background: var(--accent-soft); color: var(--text); }

/* ---------------------------------------------------------------------------
   AUTH FOOT — two opposite errands, one line

   "New here?" is for somebody who has never had an account; "Locked out?" is for
   somebody who has and cannot get in. Stacked, they read as one paragraph of
   small print that neither audience finishes. Apart, each is findable by the
   person it is for.

   Wraps to two centred lines on a narrow screen rather than squeezing, because
   two links pushed to opposite edges of a phone look like a mistake.
   --------------------------------------------------------------------------- */
/* RETIRED on the signed-out pages, 21 September 2026. The handoff moves "Locked out? Contact
   support" out of the form entirely and into the page's own footer (.auth-legal), which leaves only
   "New here?" under the form — one sentence, so there is nothing left to split. The rule stays for
   .auth-solo, which has no page footer to move it to. */
.auth-solo .auth-foot-split {
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4);
  align-items: baseline; justify-content: space-between;
}

/* ---------------------------------------------------------------------------
   RIGHT-TO-LEFT EXCEPTIONS

   The interface mirrors as a whole, which is correct and is what a reader of
   Urdu or Arabic expects. But a few things are not prose and must NOT mirror:
   an email address, a password, a URL and a code are Latin sequences whose
   order carries meaning, and a right-to-left field puts the cursor and the
   punctuation in the wrong place while typing them.
   --------------------------------------------------------------------------- */
[dir="rtl"] input[type="email"],
[dir="rtl"] input[type="password"],
[dir="rtl"] input[type="url"],
[dir="rtl"] input[type="tel"],
[dir="rtl"] input[inputmode="email"],
[dir="rtl"] .mono,
[dir="rtl"] code, [dir="rtl"] pre {
  direction: ltr;

  /* Still aligned to the reading edge, so the FIELD sits where a right-to-left
     reader looks for it even though its CONTENTS run the other way. */
  text-align: right;
}

/* Untranslated English inside a right-to-left page.

   Without this, "Welcome back." renders as ".Welcome back" and "Forgot password?"
   as "?Forgot password". The full stop and question mark are bidi-NEUTRAL
   characters: with no direction of their own they take the direction of the
   surrounding paragraph, which here is right-to-left, so they jump to the far
   end of an otherwise left-to-right sentence.

   This is not a translation problem — it happens to any English left in an Urdu
   or Arabic page, and a half-finished translation is a state this product is
   designed to be usable in (a missing phrase falls back to English on purpose).

   `unicode-bidi: plaintext` resolves each element's direction from its own first
   strong character instead of inheriting it: an Urdu phrase stays right-to-left,
   an English one becomes left-to-right, and the punctuation stays put in both.
   The mixed alignment that results is honest — it disappears as phrases are
   translated. */
[dir="rtl"] :is(h1, h2, h3, h4, h5, p, label, li, dt, dd, td, th, legend, figcaption,
                .hint, .tag, .btn, .nav-txt, .lbl, .faint, .sm, .xs, a, button, option, span) {
  unicode-bidi: plaintext;
}

/* The navigation avatar can now hold a picture as well as initials (D-576). The circle already
   has its size and shape; the image only has to fill it without distorting a non-square photo. */
.avatar > img {
  inline-size: 100%; block-size: 100%;
  object-fit: cover; border-radius: 50%; display: block;
}

/* ---------------------------------------------------------------------------
   WORKING OVERLAY

   Shown while a submit that takes real time is in flight — today, machine
   translation, which is many vendor round trips.

   A disabled button was not enough: it stops a second press of that button and
   nothing else. The admin could still open another language, start a second
   run, or navigate away mid-flight and never learn what happened. This covers
   the page, so the only thing to do is wait.
   --------------------------------------------------------------------------- */
.blocker {
  position: fixed; inset: 0; z-index: 9999;
  display: flex; align-items: center; justify-content: center;
  /* Nearly opaque, not a tint. The point is that the page underneath is out of
     reach — at 78% it read as a rendering glitch rather than a deliberate state. */
  background: color-mix(in srgb, var(--bg) 94%, transparent);
  backdrop-filter: blur(4px);
}
.blocker[hidden] { display: none !important; }

.blocker-card {
  display: flex; align-items: center; gap: var(--sp-3);
  max-inline-size: 420px; padding: var(--sp-5) var(--sp-6);
  /* --surface, not --surface-1, which does not exist. An undefined custom property
     makes the whole declaration invalid, so the card had NO background at all and
     the text floated over the blurred page. */
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r3); box-shadow: 0 8px 40px rgb(0 0 0 / .28);
}
.blocker-card b { display: block; font-size: var(--fs-sm); }
.blocker-card span { display: block; font-size: var(--fs-xs); color: var(--faint); margin-block-start: 2px; }

.blocker-spin {
  inline-size: 22px; block-size: 22px; flex-shrink: 0;
  border: 2.5px solid var(--border); border-top-color: var(--accent);
  border-radius: 50%; animation: bv-spin .8s linear infinite;
}
@keyframes bv-spin { to { transform: rotate(360deg); } }

/* Still a clear "working" state without motion — the ring stays, it just does
   not turn. */
@media (prefers-reduced-motion: reduce) {
  .blocker-spin { animation: none; border-top-color: var(--accent); }
}

/* ---------------------------------------------------------------------------
   ASKING BEFORE A DESTRUCTIVE OR EXPENSIVE ACTION

   Replaces window.confirm(). The native one cannot be styled, cannot be read in
   the visitor's language, names the origin ("localhost:56650 says") above the
   question, and puts OK first with no clue that the action deletes something or
   spends money. Every one of those is a reason somebody presses OK too fast.

   Built on <dialog> rather than a div, so the browser supplies the top layer,
   the Escape key, the focus trap and the return to the button that opened it —
   all of which a hand-rolled overlay gets subtly wrong.
   --------------------------------------------------------------------------- */
.ask {
  /* The element is only the positioning box: the visible card is .ask-box, so
     ::backdrop covers the whole viewport and the card can carry its own shadow
     without the dialog's default border and padding fighting it. */
  border: 0; padding: 0; background: transparent; color: var(--text);
  inline-size: min(430px, calc(100vw - 2 * var(--sp-4)));
  /* Centred by the margin pair, which is what a <dialog> honours. */
  margin: auto;
}
.ask::backdrop { background: rgb(0 0 0 / .45); backdrop-filter: blur(2px); }

.ask-box {
  display: grid; gap: var(--sp-4); padding: var(--sp-5);
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--r3); box-shadow: var(--sh-3);
  text-align: start;
}

.ask-head { display: flex; align-items: flex-start; gap: var(--sp-3); }

/* The tone badge. It is the part that is read first and the part window.confirm
   has no way of saying: this one deletes, that one costs money. */
.ask-ico {
  flex: none; inline-size: 36px; block-size: 36px; border-radius: var(--rf);
  display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent);
}
.ask-ico > svg { inline-size: 19px; block-size: 19px; }
.ask[data-tone="danger"] .ask-ico { background: color-mix(in srgb, var(--critical) 15%, transparent); color: var(--critical); }
.ask[data-tone="spend"]  .ask-ico { background: color-mix(in srgb, var(--warning)  20%, transparent); color: var(--warning); }

/* Only one icon shows; the tone picks it. Kept in the markup rather than drawn
   by script so the dialog is complete HTML with JavaScript disabled. */
.ask-ico > [data-ask-ico] { display: none; }
.ask:not([data-tone="danger"]):not([data-tone="spend"]) .ask-ico > [data-ask-ico="ask"],
.ask[data-tone="danger"] .ask-ico > [data-ask-ico="danger"],
.ask[data-tone="spend"]  .ask-ico > [data-ask-ico="spend"] { display: block; }

.ask-title { margin: 0; font-size: var(--fs-lg); line-height: 1.35; }
.ask-text  { margin: 5px 0 0; font-size: var(--fs-sm); color: var(--muted); }

/* Cancel first in the DOM so the keyboard reaches the safe option on the way to
   the dangerous one, and because it is the one a mis-tap should land on. */
.ask-btns { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--sp-3); }

.ask[data-tone="danger"] .ask-go { background: var(--critical); border-color: var(--critical); color: #fff; }
.ask[data-tone="danger"] .ask-go:hover { background: color-mix(in srgb, var(--critical) 84%, #000); }

/* Appears rather than blinks into place. @starting-style is what lets a
   top-layer element animate in — a plain transition has nothing to start from
   because the element did not exist a frame earlier. */
.ask[open] { animation: ask-in .14s ease-out; }
.ask[open]::backdrop { animation: ask-fade .14s ease-out; }
@keyframes ask-in { from { opacity: 0; transform: translateY(-6px) scale(.985); } }
@keyframes ask-fade { from { opacity: 0; } }

/* --- attaching a web page (§122) -------------------------------------------
   The address field replaces the Link button in place rather than opening a
   panel: it is one field and one action, and a popover for that is a lot of
   chrome around a text box. It is allowed to be wider than the button it came
   from, because a URL that scrolls inside a 120px input is unreadable. */
.linkbox { display: inline-flex; align-items: center; gap: var(--sp-2); min-inline-size: 0; }
.linkbox .input { inline-size: clamp(180px, 32vw, 380px); }

/* The composer's trailing controls: the answer count and Send, as one unit.

   They were loose children beside a `.spacer`, which works only while the
   toolbar fits on one line — `margin-inline-start: auto` right-aligns within a
   line, so the moment the row wrapped, Send dropped to a second row on the LEFT
   while the answer count stayed up on the right. Adding the Link button was
   enough to trigger it at an ordinary window width. As a group they wrap
   together and stay on the right. */
.dock-actions { display: flex; align-items: center; gap: var(--sp-1); margin-inline-start: auto; }

/* The primary action on a list screen, pushed to the far end of the heading.
   Aligned to the baseline of the title rather than stretched with it, because
   .page-head is align-items: flex-end and a full-height button next to a
   two-line description looks like a banner. */
.page-action { margin-inline-start: auto; flex: none; }

/* ---- The announcement banner (§7.12) -------------------------------------------------------
   One line from the client to everybody, across the top of every screen.

   FULL WIDTH AND ABOVE THE SCROLLING AREA, not inside it. A notice that scrolls away with the
   content is one that is missed by anybody who was already reading — and the whole point of this
   is that it reaches people who did not come looking for it.

   The three kinds reuse the status token pairs the rest of the product already uses, so the
   banner follows the theme rather than carrying its own colours. `critical` has no -fg token of
   its own — the pair was never needed before — so it borrows --critical for text on light and is
   given the same treatment dark mode gives the other two. */
.announce {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 8px var(--sp-4);
  font-size: var(--fs-sm);
  border-block-end: 1px solid var(--border);
  flex: none;
}
.announce-text { flex: 1 1 auto; }

.announce-info     { background: var(--accent-soft);  color: var(--text); }
.announce-warning  { background: var(--warning-bg);   color: var(--warning-fg); }
.announce-critical { background: var(--critical-bg);  color: var(--critical); }

/* Quiet, and on the trailing edge. It is a way out, not an action — a prominent × on a
   maintenance notice invites the reader to dismiss it before they have read it. */
.announce-x {
  flex: none; line-height: 1; font-size: var(--fs-lg);
  padding: 0 4px; border-radius: var(--r1);
  color: inherit; opacity: .55;
  transition: opacity var(--dur-1);
}
.announce-x:hover { opacity: 1; }

/* Hidden by the dismiss script, which sets this attribute before the banner is painted. */
.announce[hidden] { display: none; }

/* ============================================================================
   The picture editor (#3)

   Reuses .palette for the backdrop and .palette-box for the panel, like every
   other dialog here, and adds only what a canvas needs: a stage that keeps the
   picture inside the viewport however tall it is, and a crop rectangle drawn
   over it.
   ============================================================================ */

/* CENTRED, unlike every other user of .palette.

   That backdrop is written for the command palette: a short list that wants to sit
   under the reader's eye-line, so it is top-anchored with 12vh above it. Reusing it
   for a dialog whose entire content is one large picture was wrong. 12vh of dead
   space plus a box that still helped itself to 90vh is more than a screen, so on any
   window that is not tall the footer - Cancel and Save a copy - ran off the bottom
   with no scroller to reach it, and the squeeze left the canvas less height than it
   had been told to assume, silently clipping the picture the crop tool was asking
   the user to select a region of. */
.palette:has(> .ie-box) {
  align-items: center;
  padding-block: var(--sp-4);
}

.ie-box {
  position: relative;

  /* WIDER AND TALLER THAN THE OTHER DIALOGS, because this one is a workspace rather than a
     question. 880px was inherited from the command palette and left a crop tool the width of a
     form; a picture being cropped wants as much of the window as the window will give. */
  inline-size: min(1400px, 100% - var(--sp-4));

  /* Against the padding that is actually there, rather than a guessed 90. */
  max-block-size: calc(100vh - var(--sp-4) * 2);

  /* And it TAKES that height rather than merely being allowed it. Sized to content, a dialog whose
     content is one picture gives the stage exactly the picture's height — which is the height the
     stage was supposed to be deciding. */
  block-size: calc(100vh - var(--sp-4) * 2);

  padding: var(--sp-5);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.ie-head b { display: block; }
.ie-head p { margin: 3px 0 0; }

/* The picture is the one thing allowed to take the remaining height, and it is
   centred rather than stretched: a tall photograph in a wide dialog would
   otherwise be distorted to fill it. */
.ie-stage {
  flex: 1;
  min-block-size: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r2);
  /* Hidden at 100%, where the picture is fitted and there is nothing outside it to reach. */
  overflow: hidden;
  padding: var(--sp-2);
}

/* ZOOMED IN, the picture is deliberately larger than the stage, so the stage becomes the scroller. */
.ie-stage.is-zoomed { overflow: auto; }

/* CENTRED AT EVERY ZOOM, and this is why it is a margin rather than align-items.

   The first version switched to align-items: start when zoomed, because a flex item larger than its
   container and centred overflows in BOTH directions and the half above the top edge cannot be
   scrolled to — a scrollbar has no negative side. That worked and looked wrong: the picture sat
   centred at 100% and jumped to the top-left corner the moment the slider moved, so the same
   picture had two different positions for no reason the customer could see.

   auto margins on a flex item centre it when there is room AND collapse to zero when there is not,
   so the overflow lands entirely on the scrollable side. One rule, both cases, no jump. */

/* Hugs the canvas exactly, because the crop rectangle is positioned in percentages of it. No size
   limits of its own — the canvas is already sized to fit, and a limit here would clip it again. */
.ie-surface { position: relative; display: inline-block; flex: none; margin: auto; }

/* The canvas is drawn at the picture's FULL resolution and shrunk here. The
   export therefore loses nothing to the size of somebody's window - what is
   saved is the picture, not the preview of it. */
/* THE SIZE COMES FROM JAVASCRIPT, and the rules that used to live here are gone.

   They read max-inline-size: 100%; max-block-size: 100% and reasoned that 100% meant the stage.
   It never did: the canvas is a grandchild of the stage and .ie-surface between them is an
   inline-block sized BY its content, so both percentages were indefinite and computed to no limit.
   Measured, a 1024x1024 picture rendered at 1024x1024 inside a 204px stage, and overflow: hidden
   cut the rest off silently — with the crop tool asking for a selection against a picture the
   customer could only see the middle of.

   ImageEditor measures the stage and sets the pixel size, which also gives zoom somewhere to live. */
.ie-canvas {
  display: block;
  max-inline-size: none;
  max-block-size: none;
  touch-action: none;               /* a crop drag must not scroll the page under it */
  user-select: none;
  border-radius: var(--r1);
}

.ie-canvas.is-crop { cursor: crosshair; }
.ie-canvas.is-mark { cursor: cell; }

/* Positioned in percentages of the surface, so it stays over the same part of
   the picture when the window is resized mid-drag. */
.ie-croprect {
  position: absolute;
  border: 1.5px dashed var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  pointer-events: none;             /* the drag belongs to the canvas underneath */
  border-radius: 2px;
}

.ie-tools { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }

/* Straighten (backlog 8.3). A short slider, because the whole range is fifteen degrees
   either way — a full-width one would suggest this is the rotate control, which is two
   buttons along and does something different. */
.ie-range { inline-size: 150px; accent-color: var(--accent); }

/* Tabular, so the number does not jog sideways as the slider passes 9.5°. */
.ie-deg { inline-size: 5ch; text-align: end; font-variant-numeric: tabular-nums; }

.ie-inks { display: flex; align-items: center; gap: 4px; }

.ie-ink {
  inline-size: 20px;
  block-size: 20px;
  border-radius: 50%;
  border: 2px solid var(--border-strong);
  padding: 0;
  cursor: pointer;
}

/* An outline rather than a tick: a tick drawn on a yellow swatch is invisible,
   and on a black one it is the only thing visible. */
.ie-ink.is-on { outline: 2px solid var(--accent); outline-offset: 2px; }

.ie-sliders { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--sp-2) var(--sp-4); }

.ie-slider { display: flex; align-items: center; gap: var(--sp-2); }
.ie-slider .lbl { inline-size: 7.5em; flex: none; }
.ie-slider input[type="range"] { flex: 1; min-inline-size: 0; }
.ie-slider .num { inline-size: 3.5em; text-align: end; flex: none; }

.ie-foot { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }

@media (max-width: 640px) {
  /* .ie-canvas is no longer capped in vh here. It used to need 45vh because the 62vh
     above was too tall for a phone - a guess correcting a guess. It now measures
     against the stage, which is already the right answer at every size. */
  .ie-slider .lbl { inline-size: 6em; }
}

/* Signed out in another tab.

   Fixed to the foot of the window rather than pushed into the page flow: it
   arrives while the user is looking at something else, and a banner that
   reflowed the page under a half-written message would be its own small
   disaster. */
.signout-note {
  position: fixed;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface);
  border-block-start: 1px solid var(--border-strong);
  box-shadow: var(--sh-3);
  font-size: var(--fs-sm);
}

.signout-note[hidden] { display: none; }

/* "Try another model" (#6). Inline in the action row, and given a floor so the
   row does not jump when the button is replaced by the picker. */
.act-model { display: inline-flex; align-items: center; min-block-size: 22px; }

/* --- the admin handbook ------------------------------------------------------
   Read by two different people on the same page: an operator who wants to know
   which box to type in, and whoever is working out why the product did what it
   did. The layout is what keeps them apart — plain sections open and first, the
   technical one folded away behind a disclosure. */

.hb-search { display: flex; gap: var(--sp-2); margin-block: var(--sp-4) var(--sp-3); flex-wrap: wrap; align-items: center; }

/* Two forms, one row. `display: contents` lets each form's children become items of
   the flex row directly, so a form boundary � which is what keeps the free GET
   search and the paid POST apart � does not become a layout boundary. */
.hb-search > form { display: contents; }
/* The box takes the room; the buttons take what they need. A search field that
   is the same width as its button is a search field nobody can read their own
   query back from. */
.hb-search .input { flex: 1 1 320px; min-inline-size: 0; }

.hb-count { margin-block-end: var(--sp-3); }

.hb-problems { margin: 0; padding-inline-start: 20px; display: flex; flex-direction: column; gap: 4px; font-size: var(--fs-sm); }
.hb-problems code { font-family: var(--mono); font-size: .9em; color: var(--muted); }

.hb-results { display: flex; flex-direction: column; gap: var(--sp-2); }
.hb-result {
  display: block; padding: var(--sp-3); border: 1px solid var(--border);
  border-radius: var(--r3); background: var(--surface-2); text-decoration: none; color: inherit;
}
.hb-result:hover, .hb-result:focus-visible { border-color: var(--border-strong); background: var(--surface-3); }
.hb-result-head { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; }
.hb-result-head > b { font-size: var(--fs-md); }
/* The matched words, not the opening sentence — every article opens by naming
   itself, so a list of first sentences says nothing about why each result is
   there. Two lines is enough to recognise the passage and short enough that ten
   results still fit on a screen. */
.hb-result-snip {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  margin-block-start: 4px; font-size: var(--fs-sm); color: var(--muted); line-height: 1.5;
}

.hb-crumb { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-sm); margin-block-end: var(--sp-2); }
.hb-crumb a { color: var(--accent); text-decoration: none; }
.hb-crumb a:hover { text-decoration: underline; }

.hb-article { display: flex; flex-direction: column; gap: var(--sp-3); margin-block: var(--sp-4); }
.hb-section h2 { font-size: var(--fs-xl); letter-spacing: -.01em; margin: 0 0 var(--sp-3); }

/* An anchor landing under the sticky header is the same as an anchor that did
   not work: the reader arrives at a heading they cannot see. */
.hb-article :is(h2, summary) { scroll-margin-block-start: var(--sp-6); }

/* The technical half. Closed by default and visibly optional — not hidden, which
   would be a different promise, but not competing with the plain English above
   it either. */
.hb-tech { padding: var(--sp-3) var(--sp-4); }
.hb-tech > summary { cursor: pointer; display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; }
.hb-tech > summary > b { font-size: var(--fs-md); }
.hb-tech[open] > summary { margin-block-end: var(--sp-3); padding-block-end: var(--sp-3); border-block-end: 1px solid var(--border); }

/* .sc-answer carries the prose styling, shared with the public shared-answer
   page because both are drawn by the same renderer. This only sets the measure. */
.hb-body { max-inline-size: 76ch; }

.hb-shot { margin: var(--sp-4) 0; }
.hb-shot a { display: block; }
/* Bordered rather than bare: a screenshot of our own UI on a page using the same
   UI has no edge of its own, and without one it reads as part of the page the
   reader is on rather than as a picture of another screen. */
.hb-shot img {
  display: block; max-inline-size: 100%; height: auto;
  border: 1px solid var(--border); border-radius: var(--r2); background: var(--surface-2);
}
.hb-shot figcaption { margin-block-start: var(--sp-2); font-size: var(--fs-xs); color: var(--muted); line-height: 1.5; }

/* The "?" that every admin screen carries, pointing at its own page in the
   handbook. Quiet, in the heading row, and only drawn when an article actually
   claims that screen — a help link that lands on an index is one people learn to
   ignore. */
.hb-help {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 22px; block-size: 22px; border-radius: 999px;
  border: 1px solid var(--border); color: var(--muted);
  font-size: var(--fs-xs); font-weight: 600; text-decoration: none; flex: none;
}
.hb-help:hover, .hb-help:focus-visible { border-color: var(--accent); color: var(--accent); }

/* The way to the screen being described, under the sentence describing it. */
.hb-screens { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin-block-start: var(--sp-3); }

/* The global reset strips list markers (line 379), which is right for the navigation lists that
   make up most of the product. Prose is the exception: without a marker a bullet reads as an
   indented paragraph, and a list of five reads as five paragraphs that lost their spacing.

   Restored here rather than on .sc-answer so the public shared-answer page keeps the appearance it
   was signed off with. */
.hb-body ul { list-style: disc; }
.hb-body ol { list-style: decimal; }
.hb-body li { display: list-item; margin-block-end: var(--sp-2); }
.hb-body li::marker { color: var(--faint); }

/* The handbook index: one full-width card per area, articles gridded inside it.

   Not areas-as-columns. That made each area as tall as its own contents, so an
   area with one article sat beside one with eight and read as something that had
   failed to load. How much has been written about an area is not a fact about the
   area, and it should not decide the shape of the page. */
.hb-areas { display: flex; flex-direction: column; gap: var(--sp-3); margin-block-start: var(--sp-4); }
.hb-area-links { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 2px; }
/* The summaries are two and three lines, so rows would otherwise step unevenly
   against each other down the card. */
.hb-area-links > .adm-link { align-content: start; }

/* --- Ask AI: the opt-in row under the search box -----------------------------
   The price sits beside the switch that incurs it, before it is flipped. Not in
   a tooltip and not afterwards: nobody should discover from their balance that a
   help page was chargeable. */
.hb-ai-opt {
  /* Last in the row whatever the DOM order. The ask form comes after the search form
     in the markup so its button is reachable, but the options belong underneath both. */
  order: 10; flex: 1 1 100%; display: flex; align-items: center; gap: var(--sp-3);
  flex-wrap: wrap; padding-block-start: var(--sp-1);
}
.hb-ai-tick { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; font-size: var(--fs-sm); }
.hb-ai-model { display: inline-flex; align-items: center; gap: var(--sp-2); }
.hb-ai-model select { inline-size: auto; min-inline-size: 200px; }

/* The one control on the screen that spends money. Disabled rather than hidden —
   hidden would make the tick box look like it does nothing. */
#bv-ask[disabled] { opacity: .45; cursor: not-allowed; }

.hb-answer { margin-block: var(--sp-3); }

/* Where every claim came from. Numbered to match the brackets in the answer, so a
   reader can follow one sentence to one section rather than to a page. */
.hb-cites { display: flex; flex-direction: column; gap: 4px; margin-block-start: var(--sp-4); padding-block-start: var(--sp-3); border-block-start: 1px solid var(--border); }
.hb-cite { display: flex; align-items: baseline; gap: var(--sp-2); padding: 4px 0; font-size: var(--fs-sm); text-decoration: none; color: inherit; }
.hb-cite:hover { color: var(--accent); }
.hb-cite-n {
  flex: none; inline-size: 20px; block-size: 20px; border-radius: var(--r1);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-3); color: var(--muted);
  font-size: var(--fs-xs); font-variant-numeric: tabular-nums;
}

/* A download in flight. The link keeps its size so the row does not jump when the
   label changes, and stops looking pressable while it is working. */
.btn.is-busy { opacity: .6; pointer-events: none; }

/* `.btn` sets display: inline-flex, and an author rule beats the browser's own
   `[hidden] { display: none }` — so a hidden button stays on screen and the
   attribute looks broken. Stated here rather than fought at each call site. */
.btn[hidden] { display: none; }

/* "AI can make mistakes." Pushed to the end of the line so it never competes with
   the keyboard hints, which are the actionable half of that row. */
.dock-caveat { margin-inline-start: auto; color: var(--faint); }
@media (max-width: 720px) { .dock-caveat { margin-inline-start: 0; } }

/* Finding a screen. Sits under the New chat button, above the list it filters —
   a search box below its own results is one people miss.

   Gone in the collapsed rail: there is no room for a text field at 58px, and the
   rail's own group menus are the way through at that width. */
.nav-find { padding: 0 var(--sp-3) var(--sp-2); }
.nav-find .input { inline-size: 100%; }
.nav-find[hidden] { display: none; }
[data-nav="collapsed"] .nav-find { display: none; }

/* The same trap as .btn: .nav-item sets its own display, and an author rule beats
   the browser's [hidden]. Without this the filter would hide nothing. */
.nav-item[hidden], .nav-sect[hidden] { display: none !important; }

/* ===========================================================================================
   The tool directory (backlog 7.1) — /tools and /admin/tools
   =========================================================================================== */

/* Auto-fit rather than a fixed column count: the categories hold between four and twelve tools,
   and a 4-column grid holding five cards leaves one alone on a row looking like a mistake. */
.toolgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--sp-3);
}

.toolcard {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r3);
  background: var(--surface-2);
}

.toolcard p { color: var(--muted); margin: 0; }

.toolcard-top { display: flex; align-items: flex-start; gap: var(--sp-2); }

.toolcard-name { display: flex; flex-direction: column; min-inline-size: 0; flex: 1; }
.toolcard-name b { color: var(--text); overflow-wrap: anywhere; }

.toollogo {
  flex: 0 0 auto;
  inline-size: 40px;
  block-size: 40px;
  border-radius: var(--r2);
  object-fit: contain;
  background: var(--surface-3);
}

/* The LETTERED TILE, used for every tool as shipped. Eighty companies' logos are eighty trademarks
   we hold no licence to redistribute, so the product draws its own mark and an administrator may
   upload a real one where they have the right. The hue comes from the slug, so a tool keeps the same
   colour between visits and the wall of tiles reads as designed rather than random. */
.toolmono {
  display: grid;
  place-items: center;
  font-size: 15px;
  font-weight: 650;
  letter-spacing: .02em;
  color: hsl(var(--tool-hue, 210) 62% 34%);
  background: hsl(var(--tool-hue, 210) 68% 93%);
  border: 1px solid hsl(var(--tool-hue, 210) 45% 84%);
}

:root:not([data-theme="light"]) .toolmono { color: hsl(var(--tool-hue, 210) 70% 78%); background: hsl(var(--tool-hue, 210) 34% 20%); border-color: hsl(var(--tool-hue, 210) 28% 30%); }
:root[data-theme="dark"]        .toolmono { color: hsl(var(--tool-hue, 210) 70% 78%); background: hsl(var(--tool-hue, 210) 34% 20%); border-color: hsl(var(--tool-hue, 210) 28% 30%); }
:root[data-theme="light"]       .toolmono { color: hsl(var(--tool-hue, 210) 62% 34%); background: hsl(var(--tool-hue, 210) 68% 93%); border-color: hsl(var(--tool-hue, 210) 45% 84%); }

/* "Not connected" — the quietest badge on the page, because it is the most common and it is not a
   fault. A tone as loud as a warning would make the directory look broken rather than honest. */
.tag-muted { background: var(--surface-3); color: var(--muted); }

.btn-sm { padding: 5px 10px; font-size: var(--fs-sm); align-self: flex-start; }

/* An explanatory panel rather than a status: no icon, no colour, just quieter text. Used for the
   badge key at the top of /tools. */
.note { padding: var(--sp-3); margin-block-end: var(--sp-4); }
.note p { color: var(--muted); margin: 0; }

/* Two-column field grid for the admin forms, collapsing on narrow screens. */
.grid2 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--sp-3);
}

.fld { display: flex; flex-direction: column; gap: 4px; }
.fld > span { font-size: var(--fs-sm); color: var(--muted); }
.fld .hint { color: var(--muted); }
.fld.chk { flex-direction: row; align-items: center; gap: var(--sp-2); }
.fld.chk > span { color: var(--text); }


/* ---- The admin tool directory, as a table -----------------------------------------------------
   75 stacked edit forms was 23,000 pixels of page and unusable. One row per tool, edited on its own
   screen. */
.dt .tool-name { display: flex; align-items: center; gap: var(--sp-2); }
.dt .toollogo { inline-size: 28px; block-size: 28px; font-size: 11px; }
.tool-sum { color: var(--muted); display: block; max-inline-size: 46ch; overflow: hidden;
            text-overflow: ellipsis; white-space: nowrap; }

/* A hairline between groups of fields on an edit form. */
hr.sep { border: 0; block-size: 1px; background: var(--border); margin-block: var(--sp-2); }

/* ---- Tool directory: filter row -----------------------------------------------------------------
   .input is full-width by design, which made each control claim its own line. Here they belong on ONE
   row on a desktop — search stretching, the category box a fixed width — and stacked full-width on a
   phone, where a row of three would leave the search box too narrow to read what you typed. */
.filters .input { inline-size: auto; flex: 0 0 auto; }
.filters input[type="search"] { flex: 1 1 260px; min-inline-size: 0; }
.filters select { flex: 0 0 200px; }

@media (max-width: 640px) {
  .filters { flex-direction: column; align-items: stretch; }
  .filters .input, .filters .btn { inline-size: 100%; flex: 1 1 auto; }
  /* In a column the 260px and 200px bases above become HEIGHTS: the search box was 260px tall on a phone. */
  .filters input[type="search"], .filters select { flex: 0 0 auto; }
  .filters .spacer { display: none; }
}

/* ---- Tool cards: the button sits on the baseline ------------------------------------------------
   Summaries run to different lengths, so a button that simply followed the text landed at a different
   height in every card and the row read as ragged. margin-block-start:auto pushes it to the bottom of
   the card, so every button in a row lines up whatever is above it. */
.toolcard > .btn { margin-block-start: auto; align-self: flex-start; }
.toolcard > .faint { margin-block-start: auto; }

/* ---- Collapsible category sections --------------------------------------------------------------
   Ten categories of cards is a long page. Each folds, and the heading row is the control.

   The caret is the product's existing .fold-caret - an inline-flex span around an SVG - and that is
   not cosmetic reuse: the first attempt used a text arrow in a plain <span>, which never rotated,
   because TRANSFORMS DO NOT APPLY TO NON-REPLACED INLINE ELEMENTS. Matching the established component
   fixes the bug and makes this fold behave like every other fold in the product. */
.foldable > summary { list-style: none; cursor: pointer; user-select: none; }
.foldable > summary::-webkit-details-marker { display: none; }
.foldable > summary .fold-caret { color: var(--faint); }
.foldable[open] > summary .fold-caret { transform: rotate(90deg); }
.foldable > summary:hover .fold-caret { color: var(--text); }

/* ---- Nav scroll arrows (18 August) --------------------------------------------------------------
   The rail already scrolled; what it lacked was a way to scroll it without a wheel. These two buttons
   are that way. Hidden until script measures a real overflow, so a tall screen never sees them.

   flex: 0 0 auto IS THE IMPORTANT LINE. .nav-scrollwrap is a flex column, and a flex child defaults to
   shrinking when its siblings do not fit — which is exactly how a previous attempt at this squashed
   the nav icons out of shape. Everything in that column now states whether it may shrink, and only the
   scroller may. */
.nav-arrow {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  inline-size: 100%;
  block-size: 20px;
  padding: 0;
  border: 0;
  background: var(--surface-2);
  color: var(--muted);
  cursor: pointer;
  transition: background var(--dur-1), color var(--dur-1);
}

.nav-arrow:hover { background: var(--surface-3); color: var(--text); }
.nav-arrow:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.nav-arrow > svg { inline-size: 13px; block-size: 13px; }

/* [hidden] needs saying explicitly: the rule above sets display, and an author display always beats
   the browser's [hidden]. The same trap .nav-item documents further up this file. */
.nav-arrow[hidden] { display: none !important; }

/* The scroller is the ONLY thing in the column allowed to give up space. */
.nav-scrollwrap > .nav-scroll { flex: 1 1 auto; min-block-size: 0; }

/* ...and an icon never changes size to make room. A nav item is its own height, always. */
.nav-scroll .nav-item { flex: 0 0 auto; }
.nav-scroll .nav-ico { flex: 0 0 auto; }

/* With the arrows carrying the job, the thin scrollbar is redundant on the collapsed rail where there
   is no width to spare for it. It stays on the expanded nav, where it fits and is useful. */
[data-nav="collapsed"] .nav-scroll { scrollbar-width: none; }
[data-nav="collapsed"] .nav-scroll::-webkit-scrollbar { display: none; }

/* The bottom fade had nothing to fade. .nav-scroll carries a larger bottom padding than top, so the
   24px mask at that edge landed on empty space while the top one sat over the icons — which reads as
   the bottom arrow having lost its shadow. Only while there IS more below, where the arrow itself now
   provides the breathing room the padding was for. */
.nav-scrollwrap[data-more="bottom"] .nav-scroll,
.nav-scrollwrap[data-more="both"] .nav-scroll { padding-block-end: var(--sp-2); }

/* ---- Download an answer as a file (backlog 11.3) -----------------------------------------------
   A <details> rather than a scripted menu: keyboard-operable with no state in the component. Closing on
   an outside click is NOT free and is handled by a delegated listener in the layout. */
.act-dl { position: relative; display: inline-block; }
.act-dl > summary { list-style: none; cursor: pointer; }
.act-dl > summary::-webkit-details-marker { display: none; }

.act-dl-menu {
  position: absolute;
  z-index: 30;
  inset-block-start: calc(100% + 4px);
  inset-inline-start: 0;
  min-inline-size: 168px;
  padding: 4px;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r2);
  box-shadow: var(--shadow-2, 0 8px 24px rgba(0, 0, 0, .18));
}

.act-dl-menu a {
  padding: 7px 10px;
  border-radius: var(--r1);
  font-size: var(--fs-sm);
  color: var(--text);
  text-decoration: none;
  white-space: nowrap;
}

.act-dl-menu a:hover { background: var(--surface-3); }
.act-dl-menu a:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Near the right-hand edge the menu would otherwise run off the thread. */
@media (max-width: 600px) {
  .act-dl-menu { inset-inline-start: auto; inset-inline-end: 0; }
}

/* ---------------------------------------------------------------------------
   THE FILE VIEWER (backlog 8.1, 8.2, 8.4)

   Deliberately the same shell as the picture editor above: .palette with a
   .palette-back behind it, centred rather than dropped from the top. The client
   singled out the crop modal because it does something, and a viewer that
   opened differently from the editor beside it would read as two products.

   Wider than the editor, because the two are looking at different shapes. A
   picture is one object and fits; a spreadsheet is columns, and columns need
   room before the reader starts scrolling sideways to find the one they want.
   --------------------------------------------------------------------------- */
.palette:has(> .viewer) { align-items: center; padding-block: var(--sp-4); }

.viewer {
  position: relative;
  inline-size: min(1100px, 100%);
  max-block-size: calc(100vh - var(--sp-4) * 2);
  display: flex; flex-direction: column;
}

.viewer-head {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  /* Room for the close cross, which is positioned against the panel. */
  padding-inline-end: 44px;
  border-block-end: 1px solid var(--border);
}

/* The name is the only thing in the header that can be long, so it is the only
   thing allowed to shrink. Truncated rather than wrapped: a two-line header
   moves the toolbar under it every time a file has a long name. */
.viewer-name {
  font-weight: 600; font-size: var(--fs-sm);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.viewer-body { flex: 1 1 auto; min-block-size: 0; display: flex; flex-direction: column; }

/* The toolbar. Wraps rather than scrolls: on a phone the CSV controls are four
   things and a scrolling strip hides whichever one you have not learned yet. */
.viewer-tools {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3);
  padding: var(--sp-3) var(--sp-5);
  border-block-end: 1px solid var(--border);
  font-size: var(--fs-xs); color: var(--muted);
}
.viewer-tools .input { max-inline-size: 220px; }
.viewer-stat { font-variant-numeric: tabular-nums; white-space: nowrap; }
.viewer-warn { color: var(--warning-fg); font-weight: 550; }
.viewer-sep { inline-size: 1px; block-size: 18px; background: var(--border); }
.viewer-pick select { max-inline-size: 140px; }

.viewer-msg { margin: 0; padding: var(--sp-6) var(--sp-5); color: var(--muted); }

/* ---- text -----------------------------------------------------------------
   pre with a line number in front of every line. `white-space: pre-wrap` so a
   long log line wraps instead of forcing the whole panel to scroll sideways —
   the number stays put because it is a separate inline-block. */
.viewer-text {
  flex: 1 1 auto; min-block-size: 0; overflow: auto; margin: 0;
  padding: var(--sp-4) var(--sp-5);
  font-family: var(--mono); font-size: var(--fs-xs); line-height: 1.55;
  white-space: pre-wrap; word-break: break-word;
}
.viewer-line { display: block; }
.viewer-ln {
  display: inline-block; inline-size: 4.5ch; margin-inline-end: var(--sp-3);
  color: var(--faint); text-align: end; user-select: none;
  font-variant-numeric: tabular-nums;
}

/* ---- CSV ------------------------------------------------------------------
   The grid scrolls in both directions inside its own box, so the page never
   does. Headings stay put on the vertical scroll, which is the difference
   between a table you can read at row 300 and one you cannot. */
.viewer-grid { flex: 1 1 auto; min-block-size: 0; overflow: auto; }
.viewer-grid .dt { font-size: var(--fs-xs); }
.viewer-grid thead th {
  position: sticky; inset-block-start: 0; z-index: 1;
  background: var(--surface-2);
}
.viewer-grid td { white-space: nowrap; max-inline-size: 40ch; overflow: hidden; text-overflow: ellipsis; }

/* A heading IS the sort control, so it is a button filling the cell rather than
   a button beside the text - a click anywhere on the heading is what people try. */
.viewer-sort {
  display: block; inline-size: 100%; text-align: start;
  font: inherit; font-weight: 600; color: inherit; cursor: pointer;
  background: none; border: 0; padding: 0;
}

/* ---- PDF ------------------------------------------------------------------
   A neutral ground behind the page, so a white page reads as a sheet of paper
   rather than as the panel itself. Fixed in both themes on purpose: a document
   is a document, and tinting it to match the app misrepresents what will print. */
.viewer-page {
  flex: 1 1 auto; min-block-size: 0; overflow: auto;
  display: flex; justify-content: center; align-items: flex-start;
  padding: var(--sp-4);
  background: #52525b;
}
.viewer-page canvas { box-shadow: var(--sh-2); background: #fff; }

.viewer-img { max-inline-size: 100%; max-block-size: 70vh; margin: auto; display: block; }

/* ---- no viewer (8.4) ------------------------------------------------------
   The item that is about NOT over-promising. It gets real space rather than a
   line of small print, because the message is the whole content of the panel:
   nothing is broken, here is what opens it, here is the file. */
.viewer-none {
  flex: 1 1 auto;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sp-4); padding: var(--sp-7) var(--sp-5); text-align: center;
}
.viewer-none-ico { inline-size: 44px; block-size: 44px; color: var(--faint); }
.viewer-none-say { margin: 0; max-inline-size: 46ch; color: var(--muted); }

/* ---- Advertising (backlog 9.1) --------------------------------------------------------------
   A SPONSORED SLOT MUST NOT LOOK LIKE THE PRODUCT'S OWN CONTENT. It is deliberately quieter than
   a panel and carries a visible label and a hairline: a reader glancing at it should be able to
   tell in one look that somebody paid for it, without having to read the badge. The temptation to
   make it blend in is exactly the thing disclosure rules exist to stop. */
.ad-slot {
  display: block; margin-block: var(--sp-4);
  border: 1px solid var(--border); border-radius: var(--r2);
  background: var(--surface-2);
  /* The leading rule is the second, wordless signal that this is bought space. */
  border-inline-start: 3px solid var(--border-strong);
  overflow: hidden;
}

.ad-mark {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3) 0;
}

.ad-badge {
  font-size: var(--fs-micro); font-weight: 650; letter-spacing: .06em;
  text-transform: uppercase; color: var(--faint);
  padding: 2px 6px; border: 1px solid var(--border); border-radius: var(--r1);
  background: var(--surface);
}

.ad-by { font-size: var(--fs-xs); color: var(--faint); }

.ad-body {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3) var(--sp-3);
  color: inherit; text-decoration: none;
}

.ad-body:hover .ad-head { text-decoration: underline; }

.ad-pic {
  inline-size: 48px; block-size: 48px; flex: none;
  object-fit: cover; border-radius: var(--r1); background: var(--surface-3);
}

.ad-text { display: flex; flex-direction: column; gap: 2px; min-inline-size: 0; }
.ad-head { font-size: var(--fs-md); font-weight: 600; }
.ad-sub  { font-size: var(--fs-sm); color: var(--muted); }

.ad-cta {
  margin-inline-start: auto; flex: none;
  font-size: var(--fs-sm); font-weight: 600; color: var(--accent);
  white-space: nowrap;
}

/* Under an answer the slot is one line, because the thing above it is what the reader came for. */
.ad-slot.is-inline { margin-block: var(--sp-3) 0; background: transparent; }
.ad-slot.is-inline .ad-body { padding-block: var(--sp-2); }

@media (max-width: 560px) {
  .ad-body { flex-wrap: wrap; }
  .ad-cta  { margin-inline-start: 0; }
}

/* The revenue mix bar (backlog 9.3.2). Two stacked segments with a hairline gap between them, so
   they read as two quantities rather than one wide block — the same 2px separation the rest of the
   product uses between adjacent fills. */
.rev-bar {
  display: flex; gap: 2px; align-items: center;
  inline-size: 140px; block-size: 10px;
  background: var(--surface-3); border-radius: var(--r1); overflow: hidden;
}

.rev-sub { block-size: 100%; background: var(--accent); }
.rev-ad  { block-size: 100%; background: var(--warning); }

/* The apps screen (§12.12). A row per app: name and what it may do on the left, the one action on
   the right — so "what am I agreeing to" and "agree" are never on separate screens. */
.app-row { gap: var(--sp-4); padding-block: var(--sp-3); border-block-end: 1px solid var(--border); }
.app-row:last-child { border-block-end: 0; }

.app-caps { margin: 0; padding-inline-start: var(--sp-4); }
.app-caps li { margin-block: 1px; }

/* ---- The apps gallery (§12.12) --------------------------------------------------------------
   A CARD GRID, not the row list this started as. An app directory is browsed rather than read: the
   reader is scanning for one they recognise, and a name plus a mark is what they scan by. Rows made
   them read every line to find Dropbox.

   Sized so two fit side by side on a laptop and one on a phone, with the action always in the same
   place on every card — a button that moves as the description grows is one people hunt for. */
.app-grid {
  display: grid; gap: var(--sp-4);
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));

  /* EVERY CARD IN A ROW THE SAME HEIGHT, which is the grid default and is back after a detour.

     It was set to `align-items: start` because the Microsoft card listed three products while its
     neighbour listed none, and the short one ended up sixty per cent empty with its footer stranded
     at the bottom. THAT CARD IS NOT IN THIS GRID ANY MORE — it is the spotlight — and everything
     left is the same shape, so ragged heights are now just untidy.

     The footer's `margin-block-start: auto` is what makes this work: equal heights with the action
     pinned to the bottom edge of each card is what a row of cards is supposed to look like. */
  align-items: stretch;
}

.app-card {
  display: flex; flex-direction: column; gap: var(--sp-4);

  /* ROOMIER THAN THE REST OF THE PRODUCT, on purpose. This is a directory somebody browses rather
     than a table they work in, and the client's objection to the first version was that it read as
     basic — which was mostly this number. */
  padding: var(--sp-5);
  border: 1px solid var(--border); border-radius: var(--r3);
  background: var(--surface);
  transition: border-color .12s ease, transform .12s ease;
}

/* A hairline lift rather than a shadow: the page already has panels, and a second elevation would
   make the cards read as floating above their own section. */
.app-card:hover { border-color: var(--border-strong); }

.app-card-head { display: flex; align-items: center; gap: var(--sp-3); }
.app-card-head > .tag { flex: none; }

.app-card-title { font-size: var(--fs-md); min-inline-size: 0; overflow-wrap: anywhere; }

/* ---- The products inside one app (§12.14) ----------------------------------------------------
   "Microsoft 365" alone does not tell anybody they are getting OneDrive, Outlook and their calendar.
   One consent still covers all three — it is one Azure registration — but the card names them.

   Rows with hairlines between, rather than chips: a chip is for a word, and each of these is a
   product name plus a sentence about what it does. */
.app-parts { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }

/* THE MARK IS CENTRED AGAINST THE ROW IT LABELS.

   It used to be flex-start, which put the 30px mark's centre five pixels below the product name's —
   small enough to argue about and obvious enough that the client spotted it on the one card they
   wanted to show. Centring is the convention for an icon beside a two-line entry, and it needs no
   arithmetic that can drift.

   Two cleverer versions were tried and thrown away: a grid whose first row was sized by the mark
   itself (so centring in it changed nothing), and display: contents to dissolve the wrapper (which
   took the mark out of the grid altogether, 348px above where it belonged). Both were more
   machinery than a five-pixel alignment can justify. */
.app-part {
  display: flex; align-items: center; gap: var(--sp-3);
  padding-block: var(--sp-3);
  border-block-start: 1px solid var(--border);
}

/* A CLASS, NOT `> span` — THE SAME FAULT AS .app-row-name BELOW, IN A SECOND PLACE.

   The icon tile is a <span> and a direct child of this element too, so `.app-part > span` matched it
   as well, at higher specificity (0,1,1) than `.app-icon` itself (0,1,0). The tile became a flex
   COLUMN: `align-items: center` still centred the mark ACROSS the tile, which is why it measured
   perfectly aligned with the text beside it, while the main axis fell to the default
   `justify-content: flex-start` and dropped the mark against the top edge. Measured: 5.5px above
   centre on a 30px tile holding a 19px mark — which is precisely "flush to the top".

   That half-right symptom is what made it hard to see: horizontally it was correct.

   The identical bug was found and fixed for `.app-row-name > span` and the note there says a child
   selector naming a SHAPE rather than a role will eventually catch a sibling it was never meant to.
   It already had. Both are named now, and `icon-centring-check.mjs` measures the painted mark
   against its tile on every screen that draws one, so a third copy cannot ship quietly. */
.app-part-text { display: flex; flex-direction: column; gap: 2px; min-inline-size: 0; }

.app-part:first-child { border-block-start: 0; padding-block-start: 0; }
.app-part b { font-size: var(--fs-sm); }

/* Smaller than the app's own mark, and lighter: these are parts of the thing above them, and a
   sub-item drawn at the same weight as its parent stops reading as a sub-item. */
.app-part .app-icon { --tile: 30px; --mark: 19px; background: none; border: 0; }

.app-part-does { font-size: var(--fs-xs); color: var(--muted); line-height: 1.45; }

/* THE PROVIDER'S OWN MARK (§12.14).

   This started as a tinted initial, to avoid shipping other people's trademarks across a directory
   that will keep growing. The client looked at it beside ChatGPT and Claude and said it read as
   unfinished, and they were right: a logo is how somebody FINDS the app they came for, and a grid
   of coloured letters makes every card look the same at the distance people actually scan from.

   So the marks are real, each in its own colours, and none of them are recoloured to our accent — a
   recoloured logo is a modified trademark and also stops being the thing people recognise.

   The tile behind them is neutral rather than tinted: six brand palettes against an accent-tinted
   square is six small colour clashes, and the client can change that accent. */
.app-icon {
  --tile: 44px;
  --mark: 26px;

  /* OPTICAL SIZE, not measured size. Four dense Microsoft squares read heavier than a thin OneDrive
     cloud at identical pixels, so each mark carries a multiplier and both the card tile and the
     smaller table tile stay balanced from one number. */
  --mark-scale: 1;

  flex: none; inline-size: var(--tile); block-size: var(--tile);
  display: grid; place-items: center;
  border-radius: var(--r2);
  background: var(--surface-2);
  border: 1px solid var(--border);
  overflow: hidden;
}

.app-icon svg {
  inline-size: calc(var(--mark) * var(--mark-scale));
  block-size: calc(var(--mark) * var(--mark-scale));
  display: block;
}

.app-icon.is-microsoft { --mark-scale: .82; }
.app-icon.is-notion    { --mark-scale: .84; }
.app-icon.is-slack     { --mark-scale: .86; }
.app-icon.is-dropbox   { --mark-scale: .92; }
.app-icon.is-youtube   { --mark-scale: 1.02; }
.app-icon.is-drive     { --mark-scale: 1.02; }
.app-icon.is-github    { --mark-scale: .88; }
.app-icon.is-teams     { --mark-scale: .92; }

/* The built-in features (client, 26 Aug) — Maps, news, web search. Drawn by us, so unlike every
   vendor mark above they take the page's accent, and they are stroked rather than filled so they
   read as one family beside seven filled logos. */
.app-icon.is-builtin   { --mark-scale: .92; color: var(--accent); }

/* Notion's and GitHub's marks are black-on-white and have to follow the page's ink to stay visible
   in dark mode — the two whose own guidelines both allow and require that. */
.app-icon-ink { color: var(--ink); }

/* Every app the client adds after this one, until somebody draws its mark. Deliberate, not broken. */
.app-icon-letter {
  font-size: var(--fs-lg); font-weight: 700;
  color: var(--accent);
}

.app-card-name { display: flex; flex-direction: column; gap: 3px; min-inline-size: 0; flex: 1 1 auto; }
.app-card-name strong { font-size: var(--fs-md); }

/* The description sits under the name now rather than in its own block below the header. The
   category label that used to be here was removed: it is already the heading of the section the
   card is in, and repeating it on every card is noise. */
.app-card-name p { margin: 0; font-size: var(--fs-sm); color: var(--muted); line-height: 1.5; }

.app-card-cat { font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: .06em; color: var(--faint); }
/* Full card width, and NOT `flex: 1 1 auto` — that made it absorb the card's spare height and pushed
   the footer away from the content it belongs to. The footer is pinned by its own margin instead. */
.app-card-desc { margin: 0; font-size: var(--fs-sm); color: var(--muted); line-height: 1.55; }

/* ---- the built-in features (client, 26 Aug) --------------------------------------------------
   A card with nothing to press, so it earns its place on a different footing from the apps around
   it: what it offers is a sentence you could type. The tinted panel is what stops that sentence
   reading as one more line of description. */
.app-card.is-builtin { border-color: var(--accent-line); }

.app-card-try {
  margin: 0; display: flex; align-items: baseline; gap: var(--sp-2);
  padding: 9px 11px; border-radius: var(--r2); background: var(--accent-soft);
  font-size: var(--fs-sm); line-height: 1.5;
}

.app-try-lbl {
  flex: none; font-size: var(--fs-xs); font-weight: 650; letter-spacing: .04em;
  text-transform: uppercase; color: var(--accent);
}

/* Italic, because it is something being quoted rather than something being said. */
.app-try-text { color: var(--text); font-style: italic; min-inline-size: 0; }

/* Answers the question anybody asks on seeing three products and one button. */
.app-foot-note { font-size: var(--fs-xs); color: var(--faint); }

/* What it may do, as chips. Short enough to scan before pressing anything, which is the point —
   consent that has to be read in a paragraph is consent nobody reads. */
.app-perms { display: flex; flex-wrap: wrap; gap: 4px; }

.app-perm {
  font-size: var(--fs-micro); color: var(--faint);
  padding: 2px 7px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface-2);
}

/* Always last, always the same place on every card. `margin-block-start: auto` is what pins it
   there: without it the button on a card with a two-line description sits higher than the button
   beside it, and a control that moves between neighbours is one people hunt for. */
.app-card-foot {
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  margin-block-start: auto;
  padding-block-start: var(--sp-3); border-block-start: 1px solid var(--border);
}

/* The connected account, which can be a long address. Truncated rather than wrapped, so it cannot
   push the buttons onto a second line — the full value is on the title attribute. */
.app-card-foot .app-account {
  min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* How many apps are in a category, beside its heading. Quiet — it is orientation, not a figure. */
.app-count {
  font-size: var(--fs-xs); font-weight: 400;
  margin-inline-start: var(--sp-2);
}

/* ---- The name and category on a card --------------------------------------------------------
   A NAMED WRAPPER, never `.app-card-head > *`. The icon tile is a direct child of the header too,
   and a child selector aimed at the text is exactly how the admin list ended up with its mark pinned
   to the top of its tile — same shape of bug, one screen over. */
.app-card-ident {
  display: flex; flex-direction: column; gap: 2px;
  min-inline-size: 0; flex: 1 1 auto;
}

/* ---- The spotlight (§12.14) ------------------------------------------------------------------

   ONE APP ON THE STAGE, ABOVE THE GRID.

   The screen's real job is getting the FIRST connection made, and a grid of six equal cards cannot
   do it: nothing in it says which one would change somebody's day, so they connect none. This says
   it about one app, in a sentence they could type.

   TINTED, WHICH IS THE ONE PLACE ON THIS SCREEN THAT IS. Everything below is a card on the page's
   own surface. The tint is what makes this read as an offer rather than as the first row of the
   list — and it comes from --accent-soft, so it follows whichever accent the client picks and has a
   dark-theme value already, rather than being a colour chosen here that works in one theme.

   Two columns: the case on the left, what is inside the app on the right, divided by a hairline. It
   stacks below 860px, where two columns would leave the sentence about thirty characters wide. */
.spot {
  display: grid;
  /* NEARLY EQUAL COLUMNS. The case was given 1.35fr and then 1fr against .9fr, and both were wrong
     in the same direction: its text is capped at a readable measure, so the extra width became dead
     space it could not use, while the product list opposite wrapped to three lines a row. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: var(--sp-6);
  padding: var(--sp-6);
  border-radius: var(--r4);
  background: var(--accent-soft);

  /* THE FILL IS NOT ENOUGH ON ITS OWN, AND THE NUMBERS SAY WHY.

     The page ground is #eeebe3 and --accent-soft is #edeef5. Those are the same lightness to within
     half a per cent — they differ only in hue, warm against cool, which the eye barely reads at that
     lightness. In the light theme the panel simply disappeared into the page.

     So the SEPARATION IS CARRIED BY THE EDGE, not by the fill: a border mixed from the accent itself
     rather than the pale --accent-line, and a shadow. Both derive from the accent token, so the six
     accents the client can pick all get a visible panel without six hand-picked colours. */
  border: 1px solid var(--accent-line);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  box-shadow: 0 1px 2px rgb(16 20 40 / .05), 0 10px 30px -18px rgb(16 20 40 / .35);

  /* A WASH, NOT A SLAB. Deeper at the top where the name and the sentence are, settling to the plain
     tint by the time it reaches the button. A flat fill of the same colour reads as a coloured
     rectangle; this reads as light falling on the top of a card, which is the difference between the
     mockup and what was built from it. Mixed from the accent, so all six accents get it. */
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--accent-soft) 82%, var(--accent) 18%),
    var(--accent-soft) 68%);

  /* Arrives rather than appears. One movement, the same 8px rise the page itself uses, so the
     spotlight reads as part of the page loading instead of as an advert sliding in. Switched off
     wholesale by the global prefers-reduced-motion rule. */
  animation: page-in var(--dur-3) var(--ease) both;
}

.spot-lead { display: flex; flex-direction: column; gap: var(--sp-4); min-inline-size: 0; }

.spot-head { display: flex; align-items: center; gap: var(--sp-4); min-inline-size: 0; }

/* Bigger than the card tile, and on the page's own surface rather than the tint — a mark on a
   tinted ground next to the same mark on white below it stops looking like the same app. */
.spot-head .app-icon {
  --tile: 56px; --mark: 34px;
  background: var(--surface); border-color: var(--accent-line);
}

.spot-ident { min-inline-size: 0; }
.spot-ident h2 { margin: 0; font-size: var(--fs-xl); }
.spot-tag { margin: 3px 0 0; font-size: var(--fs-sm); color: var(--muted); line-height: 1.5; }

/* THE SENTENCE, DRAWN AS SOMETHING TYPED.

   Not italics and not a pull quote: those read as marketing, and the reader discounts them. The
   product's own composer is a rounded box on the page surface, so the same sentence in that shape
   reads as an example of what to do next — which is what it is. */
/* THE SENTENCE, AS A QUOTATION.

   This was a bordered box on the page surface, drawn to look like the product's own composer. The
   client put the mockup and the build side by side and the box was the difference: a panel with a
   white rectangle in it reads as a form control, and a form control that cannot be typed into is a
   dead end. What the mockup had was a quotation — italic, serif, unboxed, in quotation marks — which
   is not the interface talking, but a person.

   It carries no background of its own now, which is also why the wash above had to come back: the
   sentence sits in the deepest part of it. */
.spot-says {
  margin: 0;

  /* Held to a measure. Left to fill the column this ran about 900px wide while the explanation below
     it — smaller type, so a tighter measure — ran 385px. Two left-aligned blocks of different widths
     are ordinary typography; one two and a half times the other is a panel with a hole in it. */
  max-inline-size: 42ch;

  /* No orphan. The sentence is translated, so its length is not ours to control, and a quote whose
     last word sits alone on line two reads as a layout accident. */
  text-wrap: pretty;

  font-family: var(--font-quote);
  font-style: italic;
  font-size: 1.28rem; line-height: 1.38;
  color: var(--ink);
}

/* QUOTATION MARKS FROM THE BROWSER, NOT FROM THE STRING.

   Typing them into the phrase would ship English marks to every language — French wants guillemets,
   German wants its low-then-high pair — and would put punctuation inside a value a translator is
   meant to be able to rewrite. `open-quote` picks them from the document language instead. */
.spot-says::before { content: open-quote; }
.spot-says::after  { content: close-quote; }

/* Held to a readable measure. Left to the column it would run to whatever width the viewport gives
   it, and a 120-character line is one people lose their place in. */
.spot-explains {
  margin: 0;
  font-size: var(--fs-sm); color: var(--muted); line-height: 1.6;
  max-inline-size: 56ch;
}

.spot-actions { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }

/* What it costs them, beside the button. What stops people pressing Connect on something unfamiliar
   is not doubt about the app — it is not knowing how long they are agreeing to be busy for. */
.spot-effort { font-size: var(--fs-xs); color: var(--faint); }

/* NO DIVIDING RULE. There was a `border-inline-start` here, on a column that is centred in its
   row — so the line spanned only the list's own height and floated in the middle of the panel,
   reading as a stray rule rather than as a division. The tiles below do the dividing now. */
.spot-parts {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--sp-2);
  align-self: center;
}

/* EACH PRODUCT ITS OWN TILE.

   These were bare rows of text, and against the panel's tint they read as words floating in a gap —
   which is most of what "not decent" was. A tile on the page's own surface separates each product
   from the next and gives the right-hand column something to fill. */
.spot-part {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--accent-line);
  border-radius: var(--r2);

  /* Staggered in behind the panel, one after another, so the eye is walked down the three products
     rather than shown all of them at once. Delays only — the movement is the same as everything
     else on the page. */
  animation: page-in var(--dur-3) var(--ease) both;
}

.spot-part:nth-child(1) { animation-delay: 90ms; }
.spot-part:nth-child(2) { animation-delay: 150ms; }
.spot-part:nth-child(3) { animation-delay: 210ms; }
.spot-part:nth-child(4) { animation-delay: 270ms; }
.spot-part:nth-child(n+5) { animation-delay: 330ms; }

/* THE MARK ALONE, WITH NO TILE BEHIND IT.

   A bordered square inside a bordered tile is two boxes doing one job, and at this size the inner
   one crowds the mark it is supposed to frame. The tile IS the frame here. */
.spot-part .app-icon {
  --tile: 30px; --mark: 22px;
  background: none; border: 0;
}

.spot-part-text { display: flex; flex-direction: column; gap: 2px; min-inline-size: 0; }
.spot-part-text b { font-size: var(--fs-sm); }
.spot-part-does { font-size: var(--fs-xs); color: var(--muted); line-height: 1.45; }

@media (max-width: 860px) {
  .spot { grid-template-columns: 1fr; gap: var(--sp-5); padding: var(--sp-5); }

  .spot-parts { align-self: stretch; }

  /* The cap comes off once the panel is one column. A measure protects PARAGRAPHS — the eye has
     to find the next line — and the quote is a single sentence, so a long line costs it nothing
     while the empty half of a stacked panel looks like something failed to load. The explanation
     below it keeps its cap, because that one is a paragraph. */
  .spot-says { font-size: 1.14rem; max-inline-size: none; }
}

/* ---- Admin: the app list and its edit screen ------------------------------------------------ */

.app-row-name { display: flex; align-items: center; gap: var(--sp-3); min-inline-size: 0; }

/* A CLASS, NOT `> span`, and this is why.

   The icon tile is a <span> and a direct child of this element too, so `.app-row-name > span` matched
   it as well — at higher specificity than `.app-icon` itself. The tile became a flex COLUMN, its
   `place-items: center` stopped applying, and the mark sat hard against the top edge with all the
   slack below it. That is what "the icon is touching the top" was.

   A child selector that names a shape rather than a role will eventually catch a sibling it was
   never meant to. */
.app-row-text { display: flex; flex-direction: column; gap: 1px; min-inline-size: 0; }

/* Smaller in a table row than on a card — a 40px tile in a data row makes every row 40px tall and
   halves how much of the list fits on screen, which is the whole reason the list exists. */
.app-row-name .app-icon { --tile: 30px; --mark: 19px; }

.app-edit-head { display: flex; align-items: center; gap: var(--sp-3); min-inline-size: 0; }
.app-edit-head h1 { margin: 0; }
.app-edit-head p { margin: 2px 0 0; }

/* The redirect address: readable, selectable, and impossible to mistake for an editable field. */
.copyfield input { inline-size: 100%; background: var(--surface-2); cursor: text; }

.app-card.is-on { border-color: var(--ok); }
.app-card.is-stale { border-color: var(--warning); }

/* ANNOUNCED, NOT BUILT (§12.14).

   Held back rather than greyed out. Disabling a card's text is how a product says "this is broken
   for you"; what this one means is "this is coming", which is a promise and should look like one.
   So the card keeps full contrast on everything a person reads, and only the MARK is quietened —
   the identity is what recedes, not the words.

   The dashed border is the whole signal. It reads as an outline, something not yet filled in, and
   it survives forced-colours mode where a background tint would not. */
.app-card.is-soon {
  border-style: dashed;

  /* IT KEEPS THE CARD SURFACE. This was `background: none`, on the reasoning that an unfilled
     outline reads as something not yet built — which is true on white and false on this product,
     whose page ground is a warm #eeebe3. A transparent card on it is a dashed rectangle around
     nothing, and in the light theme the announced apps were as hard to find as the panel above them.

     The dashed border still carries the whole signal; it just needs a card to be drawn on. */
  background: var(--surface);
}

/* THE LOGO STAYS IN COLOUR, even here.

   It was greyed out first, on the reasoning that the identity should recede. Looking at it showed
   that was backwards: a desaturated Google Drive is an unidentifiable grey triangle, and
   recognisability is the entire reason for using real marks rather than lettered tiles. The dashed
   border, the "Coming soon" tag and the line in the footer already say it three times over — a
   fourth signal that costs the card its recognisability is a bad trade. */
.app-card.is-soon .app-icon {
  background: var(--surface-3); border-color: var(--border);
}

/* Not a button, so nothing invites a press that would do nothing. */
.app-soon-note { font-size: var(--fs-xs); color: var(--faint); font-style: italic; }

.tag-soon { background: var(--surface-3); color: var(--faint); border: 1px dashed var(--border-strong); }

.app-account { font-size: var(--fs-xs); color: var(--faint); overflow-wrap: anywhere; }

/* ---- What an answer did with a connected app (§12.14) ---------------------------------------
   Above the answer, quiet, and never competing with it. This is an ACCOUNT of what the product did
   inside somebody's mailbox — it has to be legible and it must not look like the answer.

   A rail rather than a box: a bordered panel above every tool-using answer would double the visual
   weight of the message, and this is a footnote that happens to come first. */
.tool-steps {
  margin: 0 0 var(--sp-3); padding: 0;
  list-style: none;
  display: flex; flex-direction: column; gap: var(--sp-2);
}

/* WRAPPING, so the working can take a line of its own (backlog 18.5). Without it the disclosure
   stayed inside the row and squeezed the label into a narrow column beside a wide code block —
   "Running code" broken across three lines next to a page of Python. */
.tool-step {
  position: relative;
  padding-inline-start: var(--sp-4);
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  font-size: var(--fs-xs); line-height: 1.6; color: var(--faint);
}

/* THE LINE DOWN THE SIDE OF EACH POINT, not a rail the points hang off.

   It was one continuous border down the whole list, with the dots sitting beside it — which reads
   as a quotation mark with bullets next to it rather than as a sequence. Drawn per step instead,
   so the line STARTS at the first point and STOPS at the end of the last: the shape itself says
   these things happened in this order, and nothing dangles above the beginning or below the end.

   IT RUNS BESIDE ITS OWN BODY, NOT ONLY BETWEEN DOTS, and that correction is the whole of the
   second report. Drawn only on `:not(:last-child)` it disappeared entirely from a turn with ONE
   point — which is the commonest turn there is: a model that thought and then answered, with no
   tool at all. What was left was a lone dot, a label, and a box: a heading, not a timeline. The
   line beside the thinking is what says the box belongs to the point above it. */
.tool-step::before {
  content: "";
  position: absolute;
  inset-inline-start: 3px;
  inset-block-start: calc(0.5em + 8px);
  /* Negative, so it crosses the list's own gap and meets the next dot rather than stopping short
     of it — a line with a break in it is two lines. */
  inset-block-end: calc(-1 * var(--sp-2) - 0.5em);
  /* The same weight as the dots it joins — drawn in the quieter --border it was invisible against
     the panels beside it, which is a connector that connects nothing. */
  border-inline-start: 2px solid var(--border-strong);
}

/* The last point has nothing to join to, so its line stops at the end of its own body rather than
   reaching into a gap that leads nowhere. */
.tool-step:last-child::before { inset-block-end: 0; }

/* ...and a last point with NO body has nothing to run beside. Without this it draws a four-pixel
   stub under the dot, which reads as a rendering fault rather than as a line. */
/* ...and a last point with NOTHING SHOWING under it draws nothing at all.

   Two cases, and the second arrived with the collapse. A step that never had a body — an ordinary
   tool line — has nothing to run beside. And a step whose body is FOLDED, which is what the
   thinking becomes once it is over, has nothing to run beside either: "Thought for 116s" is one
   line tall, and a rail down the side of one line is a seven-pixel stub under the dot that reads
   as a rendering fault. Opening it brings the line back, because then there is something to run
   beside again. */
.tool-step:last-child:not(:has(> .tool-step-thought, details[open]))::before {
  content: none;
}

/* A filled dot for what is done, a ring for what is happening. Shape rather than colour alone, so
   the distinction survives a colour-blind reader, a greyscale print and forced-colours mode. */
/* ON the line, not beside it — absolutely placed at the first text line rather than centred in
   the row, because a row can now be a paragraph of thinking tall and a dot floating halfway down it
   points at nothing. */
.tool-step-mark {
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 0.5em;
  inline-size: 8px; block-size: 8px; border-radius: 50%;
  border: 2px solid var(--border-strong);
  background: var(--border-strong);
}

.tool-step.is-running { color: var(--muted); }

/* How the step went, once it has (backlog 18.3). Deliberately quieter than the phrase beside it:
   the sentence carries the meaning and the duration is for somebody who chooses to look. Tabular
   figures so a column of times does not jitter as the digits change. */
.tool-step-detail {
  color: var(--faint);
  opacity: .7;
  font-variant-numeric: tabular-nums;
}

/* A step that failed. The word "failed" is in the text as well — colour alone says nothing to a
   reader who cannot distinguish it, and nothing at all in forced-colours mode. */
.tool-step.is-failed { color: var(--danger, #c0362c); }

.tool-step.is-failed .tool-step-mark {
  background: var(--danger, #c0362c);
}

/* THE WORKING, folded away until somebody opens it (backlog 18.5).

   The step is a flex row, so this has to break out of it onto its own line — a disclosure that
   opened INSIDE the row would push the duration off the end and reflow the list every time
   somebody expanded one. */
.tool-step-working {
  flex-basis: 100%;
  margin-block-start: var(--sp-1);
}

.tool-step-working > summary {
  cursor: pointer;
  color: var(--faint);
  font-size: var(--fs-xs);
  /* The browser's default marker is a filled triangle that fights the step's own dot. */
  list-style: none;
}

.tool-step-working > summary::-webkit-details-marker { display: none; }

.tool-step-working > summary::before {
  content: '▸ ';
  display: inline-block;
  transition: transform .12s ease;
}

.tool-step-working[open] > summary::before { content: '▾ '; }

.tool-step-working > summary:hover { color: var(--text); }

.tool-step-code,
.tool-step-output {
  margin: var(--sp-1) 0 0;
  padding: var(--sp-2);
  border-radius: var(--r1);
  background: var(--surface-2);
  font-size: var(--fs-xs);
  line-height: 1.5;

  /* Code does not wrap - a wrapped line of Python is a different program to read. It scrolls in
     its own box instead, so the page itself never scrolls sideways. */
  overflow-x: auto;
  white-space: pre;
  max-block-size: 22rem;
  overflow-y: auto;
}

/* WHAT WENT IN AND WHAT CAME BACK, in a labelled gutter.

   A two-column grid rather than a label floated over the block: the code has to keep its own
   horizontal scroll, and anything positioned on top of it would scroll away with the first long
   line. minmax(0, 1fr) on the second track is what lets it scroll at all - without the zero
   minimum a grid item refuses to shrink below its content and the page scrolls sideways instead. */
.tool-step-io {
  flex-basis: 100%;
  display: grid;
  grid-template-columns: 2.4rem minmax(0, 1fr);
  align-items: start;
  gap: var(--sp-2);
}

/* Aligned with the first line of the block beside it, not centred on the whole of it: a hundred
   lines of Python would put the word IN halfway down the screen from the thing it names. */
.tool-step-io-mark {
  padding-block-start: calc(var(--sp-2) + 0.15em);
  font-size: var(--fs-micro);
  letter-spacing: .08em;
  color: var(--faint);
  text-align: end;
  user-select: none;
}

/* The border that used to tell the two apart. The label does that job now, and keeping both would
   be the same distinction drawn twice. */
.tool-step-io .tool-step-output { border-inline-start: none; }

/* THE THINKING ONCE IT IS OVER — one line, opened only if somebody wants it.

   The summary carries the duration and sits where the step label would, so the row reads the same
   whether the model is still thinking or has finished. Its own panel is capped shorter than the
   live one: this is something being re-read on purpose rather than watched, and a reader who has
   deliberately opened it can scroll. */
.tool-step-thought-done > summary { color: var(--faint); }
.tool-step-thought-done > summary:hover { color: var(--text); }
.tool-step-thought-done .tool-step-thought { max-block-size: 20rem; }

/* WHAT IT THOUGHT, at the first point on the timeline.

   Shaped like the code block below it because it belongs to the same account of the turn, but set
   in the body face and wrapped: this is prose, and a line of reasoning broken by a horizontal
   scrollbar is a sentence nobody finishes. Capped, with its own scroll, so a model that thought at
   length cannot push the answer it produced off the screen. */
.tool-step-thought {
  flex-basis: 100%;
  margin: var(--sp-1) 0 0;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r1);
  background: var(--surface-2);
  color: var(--muted);
  font-size: var(--fs-xs);
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-block-size: 16rem;
  overflow-y: auto;
}

/* What came BACK, distinct from what went in. Reading a traceback and taking it for the program
   that produced it is a real confusion, and a border is cheaper than a label. */
.tool-step-output {
  border-inline-start: 2px solid var(--border-strong);
  color: var(--muted);
}

@media (prefers-reduced-motion: reduce) {
  .tool-step-working > summary::before { transition: none; }
}

.tool-step.is-running .tool-step-mark {
  background: none; border: 2px solid var(--accent);
  inline-size: 8px; block-size: 8px;
  animation: tool-step-pulse 1.4s ease-in-out infinite;
}

@keyframes tool-step-pulse {
  50% { opacity: .35; }
}

/* Somebody who has asked for less movement gets the ring without the pulse — the shape alone still
   says which step is the live one. */
@media (prefers-reduced-motion: reduce) {
  .tool-step.is-running .tool-step-mark { animation: none; }
}

/* ---- One request worked through in stages (backlog 14.3) -------------------------------------

   DELIBERATELY HEAVIER THAN THE TOOL LIST ABOVE IT. That one is an account of what happened; this
   one has a price on it — every stage is a whole paid generation — so it gets a panel rather than
   a hairline, and the cost sits in the heading rather than in a tooltip. */
.run-steps {
  margin: 0 0 var(--sp-3); padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--r2);
  background: var(--surface-2);
}

.run-steps-head {
  margin: 0 0 var(--sp-2);
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-2);
}

.run-steps-title { font-size: var(--fs-xs); font-weight: 600; color: var(--text); }

/* The price. Quieter than the count but on the same line as it, because the two are one fact:
   "three stages" and "each is charged" do not mean anything apart. */
.run-steps-cost { font-size: var(--fs-xs); color: var(--faint); }

.run-list {
  margin: 0; padding: 0; list-style: none;
  display: flex; flex-direction: column; gap: 5px;
}

.run-step {
  display: flex; align-items: baseline; gap: var(--sp-2);
  font-size: var(--fs-xs); color: var(--faint);
}

.run-step-n {
  flex: none; min-inline-size: 1.1em;
  font-variant-numeric: tabular-nums;
  color: var(--faint);
}

.run-step-title { min-inline-size: 0; }

/* SHAPE, NOT COLOUR ALONE — the same rule as the tool list, and the same reasons: a colour-blind
   reader, a greyscale print, and forced-colours mode where every one of these would otherwise be
   the same box. An empty ring is waiting, an accent ring is running, a filled dot is done. */
.run-step-mark {
  flex: none; inline-size: 8px; block-size: 8px; border-radius: 50%;
  align-self: center;
  border: 2px solid var(--border-strong); background: none;
}

.run-step.is-done { color: var(--muted); }
.run-step.is-done .run-step-mark { border-color: var(--border-strong); background: var(--border-strong); }

.run-step.is-running { color: var(--text); }
.run-step.is-running .run-step-n { color: var(--accent); }
.run-step.is-running .run-step-mark {
  border-color: var(--accent);
  animation: run-step-pulse 1.4s ease-in-out infinite;
}

@keyframes run-step-pulse { 50% { opacity: .35; } }

/* The ring without the pulse. The shape alone still says which stage is the live one. */
@media (prefers-reduced-motion: reduce) {
  .run-step.is-running .run-step-mark { animation: none; }
}

/* A stage that did not finish. A cross rather than a red dot: the colour is the second signal
   here, not the first. */
.run-step.is-failed { color: var(--critical); }
.run-step.is-failed .run-step-mark {
  border-radius: 0; border: none;
  inline-size: 8px; block-size: 8px;
  background:
    linear-gradient(to bottom right, transparent 44%, currentColor 44%, currentColor 56%, transparent 56%),
    linear-gradient(to bottom left, transparent 44%, currentColor 44%, currentColor 56%, transparent 56%);
}

.run-step-note { font-size: var(--fs-xs); }

/* ============================================================================================
   GROUPED FORM ROWS (client, 26 Aug — "he was more interested like apple like UI").

   A long form read as one wall of labelled boxes. These draw it the way an iOS settings screen
   does: small quiet headings OUTSIDE the card, one background per group, and hairlines BETWEEN the
   rows rather than a border around each control. Six short forms instead of one long one.

   Reusable on purpose. The agent editor is the first caller; run settings and the account screens
   are the obvious next ones, and nothing here knows anything about agents.
   ========================================================================================== */

.fgrp { display: flex; flex-direction: column; gap: var(--sp-2); }

.fgrp > h3 {
  margin: 0 0 0 14px; font-size: var(--fs-xs); font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--faint);
}

.fcard {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r3); overflow: hidden;
}

.frow {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 13px var(--sp-4); border-block-end: 1px solid var(--border);
}
.frow:last-child { border-block-end: 0; }

/* Stacked rather than side by side, for anything that needs the full width — a textarea, a set of
   chips, a drop zone. */
.frow.is-stacked { display: block; }

.frow-lbl { flex: none; inline-size: 138px; font-size: var(--fs-sm); font-weight: 500; }
.frow.is-stacked .frow-lbl { display: block; inline-size: auto; margin-block-end: 9px; }

/* The label and its explanation, for a row whose control sits at the trailing edge. */
.frow-txt { flex: 1; min-inline-size: 0; }
.frow-txt b { display: block; font-size: var(--fs-sm); font-weight: 500; }
.frow-txt span { display: block; font-size: var(--fs-xs); color: var(--faint); line-height: 1.45; margin-block-start: 2px; }

.frow-help { font-size: var(--fs-xs); color: var(--faint); line-height: 1.5; margin-block-start: 7px; }

/* A field with no box of its own. The card is the box; a border here would draw a box inside a
   box, which is the single thing that stops these reading as an Apple form. */
.ffld {
  flex: 1; min-inline-size: 0; border: 0; background: none; color: var(--text);
  font: inherit; font-size: var(--fs-sm); padding: 0; outline: none;
}
.ffld::placeholder { color: var(--faint); }
.ffld:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: var(--r1); }

textarea.ffld {
  inline-size: 100%; resize: vertical; min-block-size: 64px; line-height: 1.6;
  font-family: inherit;
}

/* ---- a switch that is a real checkbox ------------------------------------------------------
   .switch above is driven by aria and script, which cannot post. This one IS the input, so it
   round-trips through a form with no JavaScript at all. */
.fsw { flex: none; inline-size: 42px; block-size: 25px; position: relative; display: inline-block; }

.fsw input {
  position: absolute; inset: 0; inline-size: 100%; block-size: 100%;
  margin: 0; opacity: 0; cursor: pointer;
}

/* THE PAINTED PARTS TAKE NO POINTER EVENTS. Without this the knob sits over the checkbox and
   swallows the click — invisible to anyone using a label, and broken for a coordinate-driven
   press. The switch is the input; the rest is paint. */
.fsw-track, .fsw-knob { pointer-events: none; }

.fsw-track {
  position: absolute; inset: 0; border-radius: var(--rf);
  background: var(--surface-4); box-shadow: inset 0 0 0 1px var(--border-strong);
  transition: background var(--dur-2) var(--ease-io), box-shadow var(--dur-2) var(--ease-io);
}
.fsw input:checked + .fsw-track { background: var(--accent); box-shadow: none; }
.fsw input:focus-visible + .fsw-track { outline: 2px solid var(--accent); outline-offset: 2px; }

.fsw-knob {
  position: absolute; inset-block-start: 3px; inset-inline-start: 3px;
  inline-size: 19px; block-size: 19px; border-radius: var(--rf); background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
  transition: transform var(--dur-2) var(--ease-io);
}
.fsw input:checked ~ .fsw-knob { transform: translateX(17px); }

/* ---- chips that post ------------------------------------------------------------------------
   A pressable label over a hidden checkbox, so a multi-select posts without script. */
/* ---- a segmented control made of real radio buttons ------------------------------------------
   .seg above is driven by aria and script. This one posts: each option is a label over a radio, so
   the choice round-trips through a form with no JavaScript, and the keyboard gets arrow-key
   navigation between the options for free because they are one radio group. */
.seg-opt { position: relative; display: inline-flex; }

.seg-opt input {
  position: absolute; inset: 0; inline-size: 100%; block-size: 100%;
  margin: 0; opacity: 0; cursor: pointer;
}

.seg-opt span {
  padding: 5px 12px; border-radius: var(--r1); font-size: var(--fs-sm);
  color: var(--muted); font-weight: 500; transition: all var(--dur-1); white-space: nowrap;
}

.seg-opt:has(input:checked) span {
  background: var(--surface); color: var(--text); font-weight: 600; box-shadow: var(--sh-1);
}
.seg-opt:has(input:focus-visible) span { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- a file being dragged onto the composer (BUG-0027) ---------------------------------------
   The whole dock is the drop target — somebody aims at "the message area", not at a three-line
   text box — but the OUTLINE goes on the box, because that is the thing they are trying to hit. */
.dock-box { position: relative; }

.dock-box.is-drop {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* Said in words over the top. An outline alone leaves somebody holding a file over a box wondering
   whether it is the right box. */
.dock-drop {
  position: absolute; inset: 0; z-index: 3; pointer-events: none;
  display: grid; place-items: center; border-radius: inherit;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
}
.dock-drop b { font-size: var(--fs-sm); font-weight: 650; color: var(--accent); }

/* ---- landing on a section you jumped to (BUG-0011) ------------------------------------------

   THE CLIENT: "Nothing is happening when you click Topup button".

   It was happening. Top up is an anchor to the packages further down the same page, and on his
   screen those packages were ALREADY VISIBLE — so the jump moved the page 52 pixels, which is a
   movement nobody notices. A button that does something imperceptible is indistinguishable from a
   button that does nothing, and he was right to report it as broken.

   :target fires exactly when a fragment is navigated to and needs no script. The section says so
   for a moment, and then stops — a permanent highlight would be decoration rather than an answer
   to "did my press do anything". */
[id]:target { scroll-margin-block-start: var(--sp-6); }

.section:target, .panel:target {
  animation: landed 1400ms var(--ease-io);
}

@keyframes landed {
  0%   { box-shadow: 0 0 0 0 var(--accent); }
  12%  { box-shadow: 0 0 0 3px var(--accent); }
  70%  { box-shadow: 0 0 0 3px var(--accent-line); }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* Somebody who has asked for less motion still needs to know the press worked, so the highlight
   stays and only the movement goes. */
@media (prefers-reduced-motion: reduce) {
  .section:target, .panel:target { animation-duration: 1ms; box-shadow: 0 0 0 2px var(--accent-line); }
}

/* Taking a comparison answer away as a file (BUG-0029). Its own row under the answer, the way
   the chat has one — a menu tucked beside the text would be lost in a four-column layout. */
.pane-acts { display: flex; gap: 1px; margin-block-start: var(--sp-3); flex-wrap: wrap; }

.fchips { display: flex; flex-wrap: wrap; gap: 7px; }

.fchip {
  position: relative; display: inline-flex; align-items: center;
  border: 1px solid var(--border-strong); background: var(--surface-2); color: var(--muted);
  border-radius: var(--rf); padding: 6px 13px; font-size: var(--fs-xs); font-weight: 500;
  cursor: pointer; transition: all var(--dur-1);
}
.fchip:hover { border-color: var(--accent); color: var(--text); }

.fchip input { position: absolute; opacity: 0; inline-size: 100%; block-size: 100%; inset: 0; margin: 0; cursor: pointer; }
.fchip:has(input:checked) { background: var(--accent-fill); border-color: var(--accent); color: var(--on-accent); }
.fchip:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- a drop zone ------------------------------------------------------------------------------ */

.fdrop {
  border: 1.5px dashed var(--border-strong); border-radius: var(--r2);
  padding: var(--sp-5) var(--sp-4); text-align: center; color: var(--faint);
  font-size: var(--fs-xs); cursor: pointer;
  transition: border-color var(--dur-1), background var(--dur-1), color var(--dur-1);
}
.fdrop:hover { border-color: var(--accent); color: var(--muted); }
.fdrop:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The state that matters: it must be unmistakable while a file is over it, because the cost of
   getting it wrong is the browser navigating away and losing whatever was typed. */
.fdrop.is-over {
  border-style: solid; border-color: var(--accent);
  background: var(--accent-soft); color: var(--accent);
}

.fdrop b { display: block; font-size: var(--fs-sm); color: var(--text); font-weight: 600; margin-block-end: 3px; }
.fdrop.is-over b { color: var(--accent); }

/* ---- the note a switch reveals ----------------------------------------------------------------- */

.fnote {
  margin-block-start: var(--sp-2); padding: 11px 13px; border-radius: var(--r2);
  background: var(--accent-soft); border: 1px solid var(--accent-line);
  font-size: var(--fs-xs); line-height: 1.55; color: var(--muted);
}
.fnote b { color: var(--text); font-weight: 650; }

/* An app the capability needs is not connected. Said where the person is standing rather than by
   grieving the switch out — a disabled control answers "why not?" with silence, and the client's
   complaint about apps was that nobody could find where to go. */
.fwarn {
  display: flex; gap: 10px; align-items: flex-start;
  margin-block-start: var(--sp-2); padding: 11px 13px; border-radius: var(--r2);
  background: var(--warning-bg); border: 1px solid var(--border-strong);
  font-size: var(--fs-xs); line-height: 1.55; color: var(--text);
}
.fwarn-ico { flex: none; color: var(--warning-fg); }
.fwarn a { color: var(--accent); font-weight: 600; }

/* ============================================================================================
   THE AGENTS SCREEN (client, 25 Aug).

   Two panes: every agent on the left, the one being edited on the right. The client rejected the
   modal it replaces for two reasons, both correct — a popup cannot list fifty agents, and it could
   not scroll to its own last two fields.

   Every class the page uses is defined HERE. The previous screen shipped using five names that were
   never in this file, which is why tools/e2e/css-classes-check.mjs now exists.
   ========================================================================================== */

.ag-split { display: grid; grid-template-columns: 262px minmax(0, 1fr); gap: var(--sp-4); align-items: start; }

.ag-side {
  position: sticky; top: 0;
  display: flex; flex-direction: column; gap: 2px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r3);
  padding: var(--sp-3);
  max-block-size: calc(100vh - 190px); overflow-y: auto;
}

.ag-search { margin-block-end: var(--sp-2); }
.ag-search .input { inline-size: 100%; }

.ag-group {
  margin: var(--sp-3) 0 2px; padding-inline: var(--sp-2);
  font-size: var(--fs-micro); font-weight: 650; letter-spacing: .07em;
  text-transform: uppercase; color: var(--faint);
}
.ag-group:first-child { margin-block-start: 0; }

.ag-row {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 7px var(--sp-2); border-radius: var(--r2);
  color: var(--text); min-inline-size: 0;
}
.ag-row:hover { background: var(--surface-2); }
.ag-row.on { background: var(--accent-soft); }

/* A built-in or a teammate's: shown, not openable. No hover lift, because there is nowhere to go. */
.ag-row.is-flat:hover { background: none; }

.ag-row-txt { min-inline-size: 0; display: grid; gap: 1px; flex: 1; }
.ag-row-txt b { font-size: var(--fs-sm); font-weight: 600; }
.ag-row-txt b, .ag-row-txt small {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ag-row-txt small { font-size: var(--fs-xs); color: var(--faint); }

/* A fixed square whatever is in it, so a row with an emoji and a row without line their names up. */
.ag-ico {
  flex: none; inline-size: 26px; block-size: 26px;
  display: grid; place-items: center; border-radius: var(--r2);
  background: var(--surface-3); font-size: var(--fs-sm);
}
.ag-ico.lg { inline-size: 40px; block-size: 40px; font-size: var(--fs-lg); }
.ag-row.on .ag-ico { background: var(--accent-fill); color: var(--on-accent); }

.ag-main { min-inline-size: 0; display: flex; flex-direction: column; gap: var(--sp-4); }

.ag-title { display: flex; align-items: center; gap: var(--sp-3); }
.ag-title h2 { font-size: var(--fs-xl); }
.ag-title p { margin: 2px 0 0; }

.ag-form { display: flex; flex-direction: column; gap: var(--sp-4); }
.ag-cols { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: var(--sp-4); align-items: start; }
.ag-col { display: flex; flex-direction: column; gap: var(--sp-4); min-inline-size: 0; }

/* The preview follows the reader down a long form: it is the thing they are checking their edits
   against, and scrolling back up to find it defeats the point of it being there. */
.ag-try-col { position: sticky; top: 0; }

/* A REAL GAP. The client's third complaint was two buttons touching, which is what happens when an
   action row is two inline-blocks rather than a flex row with a gap. */
.ag-actions { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }

/* The icon picker — the answer to "how does a user select icon". */
.ag-icons { display: flex; flex-wrap: wrap; gap: 5px; }
.ag-icon-opt {
  inline-size: 34px; block-size: 34px; display: grid; place-items: center;
  font-size: var(--fs-md); border-radius: var(--r2);
  background: var(--surface-2); border: 1px solid transparent;
}
.ag-icon-opt:hover { background: var(--surface-3); }
.ag-icon-opt.on { border-color: var(--accent); background: var(--accent-soft); }
.ag-icon-opt:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ---- the model shortlist, as chips ------------------------------------------------------------
   The client approved a design with chips; thirty-eight checkbox rows was not that design, and it
   made the panel taller than the rest of the form put together. The checkboxes still exist and are
   still what the form posts — they are hidden only once the script that replaces them is running. */
.ag-chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; min-block-size: 30px; }

.ag-chip {
  display: inline-flex; align-items: center; gap: 7px;
  border: 1px solid var(--accent-line); background: var(--accent-soft);
  border-radius: 999px; padding: 4px 6px 4px 11px;
  font-size: var(--fs-xs);
}
.ag-chip-x {
  inline-size: 17px; block-size: 17px; display: grid; place-items: center;
  border-radius: 50%; background: none; color: var(--muted); font-size: var(--fs-sm); line-height: 1;
}
.ag-chip-x:hover { background: var(--surface-3); color: var(--text); }
.ag-chip-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.ag-add { margin-block-start: var(--sp-2); max-inline-size: 260px; }

/* ---- starting points on the empty screen -------------------------------------------------------
   A dashed box with a sentence in it says nothing about what an agent is for. Three worked examples
   say both what it is for and how to begin, and each starts the form already filled in. */
.ag-starts { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: var(--sp-3); }

.ag-start {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  padding: var(--sp-3); border: 1px solid var(--border); border-radius: var(--r2);
  background: var(--surface-2); color: var(--text);
}
.ag-start:hover { border-color: var(--accent); background: var(--surface-3); }
.ag-start:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.ag-start.is-blank { border-style: dashed; background: none; }

.ag-start-txt { display: grid; gap: 2px; min-inline-size: 0; }
.ag-start-txt b { font-size: var(--fs-sm); font-weight: 600; }
.ag-start-txt small { font-size: var(--fs-xs); color: var(--faint); }

/* ---- questions you can press ------------------------------------------------------------------
   The server never received a single Try it request from the client's browser: nothing on the panel
   said "type here first". A question that is one press removes the failure rather than explaining
   it. */
/* How often, and at what time, side by side — it is one sentence and reads as one. */
.ag-when { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 160px); gap: var(--sp-3); align-items: start; }
@media (max-width: 620px) { .ag-when { grid-template-columns: minmax(0, 1fr); } }

/* An agent's own questions on an empty thread. STACKED, not a wrapping row: a starter is a
   whole question, and side by side they wrap into each other and stop reading as separate things
   you can press. Capped so a long one does not run the width of a wide screen. */
.ag-asks {
  display: flex; flex-direction: column; gap: 6px; align-items: center;
  margin-block-start: var(--sp-4);
}
/* A VISIBLE EDGE ON EVERY ONE. `btn-quiet` is transparent until it is hovered, so the second and
   third read as paragraphs of text rather than as things you can press — which is the whole point
   of offering them. */
.ag-asks .btn {
  max-inline-size: 46ch; text-align: start; white-space: normal;
  inline-size: 100%;
  border: 1px solid var(--border); background: var(--surface-2);
  padding: 9px 13px; line-height: 1.45;
}
.ag-asks .btn:hover { border-color: var(--accent); background: var(--surface-3); }

.ag-seeds { display: flex; flex-direction: column; gap: 6px; }
.ag-seeds[hidden] { display: none; }

.ag-seed {
  text-align: start; font: inherit; font-size: var(--fs-xs);
  cursor: pointer; padding: 6px 11px;
}
.ag-seed:hover { border-color: var(--accent); background: var(--surface-3); }
.ag-seed:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ---- Try it, as a conversation --------------------------------------------------------------- */
.ag-try-log {
  display: flex; flex-direction: column; gap: var(--sp-2);
  min-block-size: 90px; max-block-size: 340px; overflow-y: auto;
  padding-inline-end: 2px;
}

.ag-bubble { display: flex; flex-direction: column; gap: 3px; max-inline-size: 92%; }
.ag-bubble-txt {
  border-radius: var(--r2); padding: 8px 11px;
  font-size: var(--fs-sm); white-space: pre-wrap; overflow-wrap: anywhere;
}

/* The question sits to the right, the answer to the left — the shape of every messaging screen
   anybody has used, so nobody has to be told which is which. */
.ag-bubble.is-you { align-self: flex-end; align-items: flex-end; }
.ag-bubble.is-you .ag-bubble-txt { background: var(--accent-soft); border: 1px solid var(--accent-line); }

.ag-bubble.is-it .ag-bubble-txt { background: var(--surface-2); border: 1px solid var(--border); }

.ag-bubble.is-bad .ag-bubble-txt {
  background: var(--critical-bg); border: 1px solid var(--critical); color: var(--critical);
}
.ag-bubble.is-bad { gap: var(--sp-2); align-items: flex-start; }

/* The hint under the box turns critical when it is telling somebody what went wrong. */
#ag-try-hint.is-bad { color: var(--critical); }

.ag-boxes[hidden] { display: none; }

/* One column once the two panes stop fitting. The sidebar unsticks and becomes a short list above
   the form, rather than a 262px column squeezed against it. */
@media (max-width: 1080px) {
  .ag-cols { grid-template-columns: minmax(0, 1fr); }
  .ag-try-col { position: static; }
}

@media (max-width: 860px) {
  .ag-split { grid-template-columns: minmax(0, 1fr); }
  .ag-side { position: static; max-block-size: 320px; }
}

/* ---- changing the plan, and doing a stage again (backlog 17.9) --------------------------------

   PUSHED TO THE END OF THE LINE and quiet until the line is touched. These sit on a list whose job
   is to say what is about to be paid for; controls competing with that for attention would make
   the count harder to read, which is the one thing the list must not do.

   They stay visible on :focus-within, so a keyboard reader never chases a control that appears
   under a pointer they are not using. */
.run-step-edit {
  margin-inline-start: auto;
  align-self: center;
  display: inline-flex; gap: 2px;
  opacity: 0; transition: opacity .12s ease;
}

.run-step:hover .run-step-edit,
.run-step:focus-within .run-step-edit { opacity: 1; }

/* Touch has no hover, so there is nothing to reveal — they are simply always there. */
@media (hover: none) {
  .run-step-edit { opacity: 1; }
}

.run-step-btn {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 20px; block-size: 20px; padding: 0;
  border: 1px solid transparent; border-radius: var(--r1);
  background: none; color: var(--muted); cursor: pointer;
}

.run-step-btn:hover:not(:disabled) { color: var(--text); border-color: var(--border); }
.run-step-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Dimmed rather than hidden at the boundary. A control that vanishes leaves the reader wondering
   what they did; one that is there and dim says the rule. */
.run-step-btn:disabled { opacity: .3; cursor: default; }

.run-step-drop:hover:not(:disabled) { color: var(--critical); border-color: var(--critical); }

@media (prefers-reduced-motion: reduce) {
  .run-step-edit { transition: none; }
}


/* Did it actually do the job (backlog 14.5). Inside the panel, under the stages: it is a statement
   about THAT run, and it is deliberately quiet — a verdict styled like a banner would read as the
   product congratulating itself. */
.run-check {
  margin: var(--sp-2) 0 0; padding-block-start: var(--sp-2);
  border-block-start: 1px solid var(--border);
  display: flex; align-items: baseline; gap: var(--sp-2);
  font-size: var(--fs-xs); color: var(--muted);
}

/* SHAPE FIRST, COLOUR SECOND — the same rule as the stage marks. A tick for done, a hollow
   diamond for something missing: distinguishable in greyscale, in forced colours, and to a reader
   who cannot separate green from amber. */
.run-check-mark {
  flex: none; inline-size: 9px; block-size: 9px; align-self: center;
}

.run-check.is-ok .run-check-mark {
  border-inline-start: 2px solid currentColor; border-block-end: 2px solid currentColor;
  inline-size: 5px; block-size: 9px;
  transform: rotate(45deg) translate(-1px, -1px);
}

.run-check.is-ok { color: var(--ok-fg, var(--muted)); }

.run-check.is-off { color: var(--warning-fg, var(--text)); }

.run-check.is-off .run-check-mark {
  border: 2px solid currentColor; transform: rotate(45deg);
  inline-size: 8px; block-size: 8px;
}

/* Carrying on a run that stopped (backlog 14.4). Inside the panel, under the list, because it is
   about THAT run — a button in the message actions would read as being about the answer. */
.run-carry-on {
  margin-block-start: var(--sp-2); padding-block-start: var(--sp-2);
  border-block-start: 1px solid var(--border);
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
}

/* The sentence that says what will NOT be run again. Quieter than the button and beside it, never
   underneath: it is the answer to the question somebody has while their finger is over the button. */
.run-carry-on-note { font-size: var(--fs-xs); color: var(--faint); }

/* "This did not need stages." One line, said only after the answer is over — see NotSplitNote. */
.run-not-split {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-xs); color: var(--faint);
}

/* ---- Offering a search rather than taking one (backlog 16.6) ---------------------------------

   UNDER the answer and quiet. The answer above it is usually right; a banner over it would tell
   the reader to distrust something before they had read it, which is the product undermining
   itself to sell a search. */
.search-offer {
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  margin-block-start: var(--sp-3); padding-block-start: var(--sp-2);
  border-block-start: 1px dashed var(--border);
  font-size: var(--fs-xs); color: var(--faint);
}

/* ---- Lighter messages (backlog 16.2, option G) -----------------------------------------------

   Every answer used to carry a routing-reason box AND a permanent row of four actions — about 90px
   each of furniture nobody reads twice, on a conversation that gets 386px of height in the first
   place. Nothing is removed here: the reason folds to a chip on the byline, and the actions wait
   until the answer is being looked at. */

/* The reason, folded onto the byline. A real button, so it works on a phone and from a keyboard —
   a claim about how somebody's money was spent must not be mouse-only. */
.why-chip {
  margin-inline-start: 8px; padding: 1px 7px; border-radius: var(--rf);
  border: 1px solid var(--accent-line); background: var(--accent-soft);
  color: var(--accent); font-size: var(--fs-micro); font-weight: 600;
  font-family: inherit; cursor: pointer; white-space: nowrap;
}

.why-chip:hover { background: var(--accent-fill); color: var(--on-accent); border-color: var(--accent); }

/*
   THE ACTION ROW, UNTIL THE ANSWER IS BEING LOOKED AT.

   Three rules, and the second two are why this is not simply "hide it":

   - Only where a pointer can hover. On a phone there is no hover, so the row stays exactly as it
     was — hiding it there would remove Copy and Download outright.
   - focus-within, so tabbing to the row shows it. Without that, everything here is unreachable
     from a keyboard, which is a worse product for the people who most need the buttons.
   - Never on the newest answer. That one is what the reader is actually working with, and having
     to hover the thing you just received to find "Copy" is a puzzle rather than a screen. */
@media (hover: hover) and (pointer: fine) {
  .msg-acts.is-quiet {
    opacity: 0;
    transition: opacity var(--dur-1) var(--ease-io);
  }

  .msg:hover .msg-acts.is-quiet,
  .msg-acts.is-quiet:focus-within,
  .msg:last-of-type .msg-acts.is-quiet {
    opacity: 1;
  }
}

/* Somebody who has asked for less movement gets the row appearing without the fade. */
@media (prefers-reduced-motion: reduce) {
  .msg-acts.is-quiet { transition: none; }
}

/* ---- The model picker's chips and groups (backlog 16.1) --------------------------------------

   THE CHIPS ARE THE FEATURE. At a dozen models a search box is enough; at thirty-eight it only
   helps somebody who already knows what they are looking for, and the person this screen is hardest
   for is the one who does not. */
.mpick-chips {
  display: flex; flex-wrap: wrap; gap: 4px;
  padding: var(--sp-2) var(--sp-3);
  border-block-end: 1px solid var(--border);
}

.mpick-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: var(--rf);
  border: 1px solid var(--border-strong); background: var(--surface-2);
  color: var(--muted); font-size: var(--fs-xs); font-family: inherit;
  cursor: pointer; white-space: nowrap;
}

.mpick-chip:hover { background: var(--surface-3); color: var(--text); }

.mpick-chip.is-on {
  background: var(--accent-fill); border-color: var(--accent); color: var(--on-accent);
}

/* The count is a PREVIEW, not decoration: it says how long the list will be before it is pressed. */
.mpick-chip-n { font-variant-numeric: tabular-nums; opacity: .7; }

.mpick-chip.is-on .mpick-chip-n { opacity: .85; }

/* A heading that holds its place while the rows scroll under it — the whole reason grouping beats
   one long list at forty models. */
.mpick-grouphead {
  position: sticky; inset-block-start: 0; z-index: 1;
  margin: 0; padding: 6px var(--sp-3) 4px;
  background: var(--surface); border-block-end: 1px solid var(--border);
  font-size: var(--fs-xs); letter-spacing: .08em; text-transform: uppercase;
  color: var(--faint);
  display: flex; justify-content: space-between; gap: var(--sp-2);
}

.mpick-group-n { font-variant-numeric: tabular-nums; opacity: .8; }

/* The vendor's mark. One colour per vendor, their own, so a row is identifiable at a glance in a
   list of forty — see VendorMark.tsx for why some are marks and some are letters. */
.vmark {
  inline-size: 18px; block-size: 18px; border-radius: 4px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  color: #fff; margin-inline-end: 6px;
}

.vmark svg { inline-size: 12px; block-size: 12px; display: block; }

.vmark-txt { font-size: 9px; font-weight: 700; letter-spacing: .02em; }

/* ---- The spreadsheet viewer (backlog 8.6) ----------------------------------------------------

   A PAGE WITH NO CHROME. No rail, no header, no footer: this screen is one spreadsheet filling the
   window. Every other viewer in this product is a dialog over the workspace, and this is the only
   one that cannot be — a spreadsheet in a 1,100px column beside a navigation rail is a spreadsheet
   nobody can work in. */
.sheet-page {
  margin: 0; block-size: 100dvh;
  display: flex; flex-direction: column;
  background: var(--bg); color: var(--text);
  overflow: hidden;
}

/* One thin row: back, the file name, and the way to the original. Kept to 44px because every pixel
   here is a row of the spreadsheet somebody cannot see. */
.sheet-bar {
  flex: none;
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border-block-end: 1px solid var(--border);
  background: var(--surface);
}

.sheet-name {
  font-size: var(--fs-md); min-inline-size: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* WHAT THE VIEWER CANNOT SHOW, at the top where it is read before the data rather than after it.
   A spreadsheet that quietly lost its chart is one somebody trusts and should not. */
.sheet-notice {
  flex: none;
  padding: var(--sp-2) var(--sp-4);
  font-size: var(--fs-xs); color: var(--warning-ink, var(--text));
  background: var(--warning-soft, var(--surface-3));
  border-block-end: 1px solid var(--border);
}

.sheet-status {
  margin: 0; padding: var(--sp-6);
  font-size: var(--fs-sm); color: var(--muted); text-align: center;
}

.sheet-status.is-error { color: var(--critical); max-inline-size: 60ch; margin-inline: auto; }

/* THE ENGINE MEASURES ITSELF FROM THIS BOX, so it has to be a real one: a height of auto gives
   Univer a container of zero and a canvas nobody can see. */
.sheet-root { flex: 1 1 auto; min-block-size: 0; position: relative; }

/* The editor, framed (18.18). It fills the space and brings its own everything — that isolation
   is most of why it is an iframe rather than a mount, so there is deliberately nothing here that
   reaches inside it. */
.sheet-frame {
  inline-size: 100%;
  block-size: 100%;
  border: 0;
  display: block;

  /* The editor paints its own ground in its own theme. Without this the frame is transparent for
     the instant before it loads, which reads as the page flashing. */
  background: var(--surface);
}

/* ---- The document viewer (backlog 8.5, re-engined 18.18) --------------------------------------

   THE SAME SHAPE AS THE SPREADSHEET NEXT DOOR, and for the first time. This screen used to lay out
   plain elements itself — .doc-body, .doc-table, .doc-list and the rest, all of which have gone with
   the renderer that produced them. It now frames Quire Docs, which brings its own page, its own
   paper and its own print stylesheet.

   The property that block of CSS existed to protect is NOT lost: the editor renders to HTML, not to
   a canvas, so the text inside the frame is still selectable, still found by Ctrl+F, and still read
   by a screen reader. What changed is who draws the page. */
.doc-page {
  margin: 0; block-size: 100dvh;
  display: flex; flex-direction: column;
  background: var(--bg); color: var(--text);
  overflow: hidden;
}

.doc-status { margin: 0; padding: var(--sp-6); font-size: var(--fs-sm); color: var(--muted); text-align: center; }
.doc-status.is-error { color: var(--critical); max-inline-size: 60ch; margin-inline: auto; }

/* Read-only, or a save that failed — above the document rather than below it, because a reader who
   is about to type needs to know before they start. */
.doc-notice {
  flex: none;
  padding: var(--sp-2) var(--sp-4);
  font-size: var(--fs-xs); color: var(--warning-ink, var(--text));
  background: var(--warning-soft, var(--surface-3));
  border-block-end: 1px solid var(--border);
}

.doc-notice.is-problem { color: var(--critical); }

/* A real box with a real height: the editor measures its paper column and its pagination from this,
   and a height of auto gives it nothing to measure. */
.doc-root { flex: 1 1 auto; min-block-size: 0; position: relative; }

.doc-frame {
  inline-size: 100%;
  block-size: 100%;
  border: 0;
  display: block;

  /* The frame is transparent until it paints, which reads as the page flashing. */
  background: var(--surface);
}

/* PRINTING GOES THROUGH THE FRAME, NOT THROUGH HERE (see the Print button in Doc.cshtml). Ctrl+P on
   this page can only print the chrome, so what is left in this block is the honest outcome of that:
   hide the chrome, and leave the document to the editor's own print stylesheet. */
@media print {
  .no-print { display: none !important; }
  .doc-page { background: #fff; block-size: auto; overflow: visible; }
  .doc-notice { border: 0; background: none; color: #555; }
}

/* ---- The deck viewer (backlog 8.7, re-engined 18.18) ------------------------------------------

   IT USED TO BE ONE CARD PER SLIDE DOWN THE PAGE, and the reasoning was right for what it had: a
   viewer that only knew what each slide SAID is a reading view, and clicking through forty of those
   one at a time would have been the opposite of a preview.

   It now frames Quire Slides, which knows where everything IS — so the slide is a slide again, with
   its own rail, its own stage and its own full-screen presenting. The card rules below have gone
   with the renderer that drew them (.deck-body, .deck-slide, .deck-title, .deck-points, .deck-notes
   and the rest); the editor brings its own. */
.deck-page {
  margin: 0; block-size: 100dvh;
  display: flex; flex-direction: column;
  background: var(--bg); color: var(--text);
  overflow: hidden;
}

.deck-status { margin: 0; padding: var(--sp-6); font-size: var(--fs-sm); color: var(--muted); text-align: center; }
.deck-status.is-error { color: var(--critical); max-inline-size: 60ch; margin-inline: auto; }

/* WHAT THE VIEWER CANNOT SHOW, at the top where it is read BEFORE the slides rather than after.
   Here that is the typefaces: the deck's own fonts cannot be loaded under this product's policy, so
   a reader comparing this against their own copy needs telling why the text looks different. */
.deck-notice {
  flex: none;
  padding: var(--sp-2) var(--sp-4);
  font-size: var(--fs-xs); color: var(--warning-ink, var(--text));
  background: var(--warning-soft, var(--surface-3));
  border-block-end: 1px solid var(--border);
}

.deck-notice.is-problem { color: var(--critical); }

/* A real box with a real height: the editor sizes its stage and its slide rail from this, and a
   height of auto gives it nothing to measure. */
.deck-root { flex: 1 1 auto; min-block-size: 0; position: relative; }

.deck-frame {
  inline-size: 100%;
  block-size: 100%;
  border: 0;
  display: block;

  /* The frame is transparent until it paints, which reads as the page flashing. */
  background: var(--surface);
}

/* PRINTING A DECK GOES THROUGH THE EDITOR, not through here — it lays slides onto paper itself, and
   this page can only print its own chrome. What is left is the honest remainder: hide the chrome. */
@media print {
  .no-print { display: none !important; }
  .deck-page { background: #fff; block-size: auto; overflow: visible; }
  .deck-notice { border: 0; background: none; color: #555; }
}

/* ---- Saving from a viewer (backlog 8.9) ---------------------------------------------------------
   The count of unsaved changes sits in the bar rather than over the grid, because anything floating
   above a spreadsheet covers the one thing the person is looking at. */
.sheet-saved {
  font-size: var(--fs-xs); color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-inline-size: 46ch;
}

/* Unsaved work is stated, not alarmed about — this is an ordinary state, not a fault. */
.sheet-saved.is-dirty { color: var(--text); font-weight: 600; }
.sheet-saved.is-problem { color: var(--critical); }

/* ---- The Sheets header (sheets-kit V2, and Syed's Sheets feature additions, 19 September 2026) ----------------
   The title with a rename pencil and a star, the menu bar under it, then the save state, History and "…". In the
   additions' own tokens and IBM Plex Sans (declared with the Sheets list in this file). Scoped to .sxe-bar, which
   the Document and Slides pages carry too since their feature additions (19 September). */
.sxe-bar {
  --sxe-ink: #1B2230; --sxe-ink-2: #3F4859; --sxe-menubar: #2E3646; --sxe-muted: #5A6474; --sxe-line: #DDE2EA;
  --sxe-control: #C9D0DB; --sxe-hover: #E4E8EF; --sxe-menu-hover: #F0F3F9; --sxe-primary: #2B3FC4;
  --sxe-saved: #1F7A4D; --sxe-warn: #8A6100; --sxe-danger: #B42318; --sxe-star: #B7791F;
  gap: 12px; padding: 0 16px 0 18px; min-block-size: 74px;
  background: #FFFFFF; border-block-end-color: var(--sxe-line); color: var(--sxe-ink);
  font-family: 'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif;
}
.sxe-back {
  flex: none; display: grid; place-items: center; inline-size: 30px; block-size: 30px; border-radius: 7px;
  color: var(--sxe-ink-2);
}
.sxe-back:hover { background: var(--sxe-hover); }
.sxe-back svg, .sxe-ib svg, .sxe-btn svg {
  inline-size: 16px; block-size: 16px;
  fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.sxe-back svg { inline-size: 18px; block-size: 18px; }

.sxe-doc { display: flex; flex-direction: column; gap: 4px; min-inline-size: 0; }
.sxe-titlerow { display: flex; align-items: center; gap: 6px; min-inline-size: 0; }
.sxe-name { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 18px; font-weight: 600; }
.sxe-rename {
  min-inline-size: 14ch; padding: 1px 6px; border: 1px solid var(--sxe-primary); border-radius: 6px; outline: none;
  color: var(--sxe-ink); font: inherit; font-size: 18px; font-weight: 600;
}
.sxe-ib {
  flex: none; display: inline-grid; place-items: center; inline-size: 28px; block-size: 28px; padding: 0;
  border: 0; border-radius: 6px; background: transparent; color: var(--sxe-ink-2); cursor: pointer;
}
.sxe-ib:hover { background: var(--sxe-hover); }
.sxe-ib svg { inline-size: 15px; block-size: 15px; }
.sxe-star.is-on { color: var(--sxe-star); }
.sxe-star.is-on svg { fill: currentColor; }

.sxe-menubar { display: flex; align-items: center; margin-inline-start: -8px; min-inline-size: 0; }
.sxe-mb {
  flex: none; block-size: 26px; padding: 0 8px; border: 0; border-radius: 5px; background: transparent; cursor: pointer;
  color: var(--sxe-menubar); font: inherit; font-size: 13px;
}
.sxe-mb:hover, .sxe-mb[aria-expanded="true"] { background: var(--sxe-hover); }

.sxe-state {
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
  font-size: 12px; font-weight: 500; color: var(--sxe-muted);
}
.sxe-state::before { content: ""; flex: none; }
.sxe-state.is-saved { color: var(--sxe-saved); }
.sxe-state.is-saved::before {
  inline-size: 4px; block-size: 8px; margin: -3px 3px 0;
  border: solid currentColor; border-width: 0 1.8px 1.8px 0; transform: rotate(45deg);
}
.sxe-state.is-dirty { color: var(--sxe-warn); }
.sxe-state.is-dirty::before,
.sxe-state.is-problem::before { inline-size: 7px; block-size: 7px; border-radius: 50%; background: currentColor; }
.sxe-state.is-saving::before {
  inline-size: 10px; block-size: 10px; border-radius: 50%;
  border: 1.6px solid currentColor; border-inline-end-color: transparent; animation: sxe-spin .9s linear infinite;
}
.sxe-state.is-problem { color: var(--sxe-danger); }
@keyframes sxe-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .sxe-state.is-saving::before { animation: none; } }

.sxe-btn {
  display: inline-flex; align-items: center; gap: 8px; block-size: 36px; padding: 0 14px; cursor: pointer; white-space: nowrap;
  border: 1px solid var(--sxe-control); border-radius: 8px; background: #FFFFFF; color: var(--sxe-ink);
  font: inherit; font-size: 13px; font-weight: 600; text-decoration: none;
}
.sxe-btn:hover { background: #F4F6F9; }
.sxe-btn:disabled { opacity: .55; cursor: default; }
.sxe-more { position: relative; flex: none; }
.sxe-sq {
  display: grid; place-items: center; inline-size: 36px; block-size: 36px; padding: 0; cursor: pointer;
  border: 1px solid var(--sxe-control); border-radius: 8px; background: #FFFFFF; color: var(--sxe-ink-2);
}
.sxe-sq:hover, .sxe-sq[aria-expanded="true"] { background: #F4F6F9; }
.sxe-sq svg { inline-size: 16px; block-size: 16px; fill: currentColor; }
.sxe-menu {
  position: absolute; inset-inline-end: 0; inset-block-start: calc(100% + 6px); z-index: 30; inline-size: 210px;
  display: flex; flex-direction: column; padding: 6px;
  background: #FFFFFF; border: 1px solid #D5DAE3; border-radius: 10px; box-shadow: 0 10px 28px rgba(20, 30, 50, .16);
}
.sxe-menu[hidden] { display: none; }
.sxe-menu > button, .sxe-menu > a {
  display: flex; align-items: center; block-size: 34px; padding: 0 12px; border: 0; border-radius: 6px; cursor: pointer;
  background: transparent; color: var(--sxe-ink); font: inherit; font-size: 13px; text-align: start; text-decoration: none;
}
.sxe-menu > button:hover, .sxe-menu > a:hover { background: var(--sxe-menu-hover); }
.sxe-menu > .is-danger { color: var(--sxe-danger); }
.sxe-menu hr { margin: 6px 4px; border: 0; border-block-start: 1px solid #E4E8EE; }

/* After Move to trash: where the sheet went, with Undo, in the space the sheet filled. */
.sheet-status.sxe-gone { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 10px; color: #3F4859; font-family: 'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif; }

/* ---- The same header on the Documents and Slides pages (Syed's Documents and Slides feature additions) ----------
   Their title keeps its extension, muted after the name as the lists show it; Slides has Present beside History;
   and Slides' fonts notice is a band under the header with Download .pptx and a ×. */
.sxe-ext { color: #6B7486; font-weight: 400; }
.doc-status.sxe-gone { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 10px; color: #3F4859; font-family: 'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif; }
.sxe-btn .sxe-play { fill: currentColor; stroke: none; inline-size: 12px; block-size: 12px; }
.deck-fonts {
  flex: none; display: flex; align-items: center; gap: 10px; min-block-size: 36px; padding: 6px 16px 6px 18px;
  background: #F7F3EA; border-block-end: 1px solid #E8DFC9; color: #3F4859;
  font-family: 'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif; font-size: 12.5px;
}
.deck-fonts[hidden] { display: none; }
.deck-fonts > svg { flex: none; inline-size: 15px; block-size: 15px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
.deck-fonts > span { flex: 1; min-inline-size: 0; }
.deck-fonts a { flex: none; color: #2B3FC4; font-weight: 600; text-decoration: none; white-space: nowrap; }
.deck-fonts a:hover { text-decoration: underline; }
.deck-fonts button {
  flex: none; display: grid; place-items: center; inline-size: 26px; block-size: 26px; padding: 0; border: 0; border-radius: 6px;
  background: transparent; color: #3F4859; font-size: 16px; line-height: 1; cursor: pointer;
}
.deck-fonts button:hover { background: #EDE4CF; }
@media print { .deck-fonts { display: none !important; } }

/* A narrower window keeps every control and drops words first: the save state and History become their marks
   (the words stay for a screen reader), then the menu bar scrolls sideways rather than wrapping. */
@media (max-width: 900px) {
  .sxe-state { gap: 0; font-size: 0; }
  #sheet-history .sxe-label, #doc-history .sxe-label, #deck-history .sxe-label, #deck-present .sxe-label {
    position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%);
  }
  #sheet-history, #doc-history, #deck-history, #deck-present { padding: 0 10px; }
}
@media (max-width: 640px) {
  .sxe-bar { gap: 8px; padding: 6px 10px; }
  .sxe-name, .sxe-rename { font-size: 16px; }
  .sxe-menubar { overflow-x: auto; scrollbar-width: none; }
  .sxe-menubar::-webkit-scrollbar { display: none; }
}

.sheet-dialog {
  /* --r4, not --radius-lg. That token has never existed in this stylesheet — the scale is
     --r1 through --r4 plus --rf — so the declaration was invalid and dropped, and this dialog has
     had square corners since it shipped. Found 2026-09-10 while auditing the tokens a new rule was
     about to use; nothing reported it, because an invalid border-radius fails silently and a square
     dialog looks like a decision somebody took. */
  border: 1px solid var(--border); border-radius: var(--r4);
  padding: var(--sp-5); max-inline-size: 46rem; inline-size: calc(100vw - var(--sp-6));
  background: var(--surface); color: var(--text);
}

.sheet-dialog::backdrop { background: rgb(0 0 0 / 35%); }

.sheet-dialog h2 { margin: 0 0 var(--sp-2); font-size: var(--fs-lg); }

/* The two choices are the whole dialogue, so they get room. A radio somebody has to aim at is a
   radio somebody accepts the default of. */
/* THE SAME CHOICE ROW, WHEREVER IT IS DRAWN.
 *
 * These rules were written against .sheet-dialog, which is the file viewers' overlay. The agent
 * editor lives in the palette overlay instead — the one the persona picker uses — so its radio
 * rows lost their layout entirely and the label ran into its own description on one line.
 *
 * Selected together rather than duplicated: a choice row is one control with one appearance, and
 * two copies of these five rules would drift the first time either overlay was touched. */
.sheet-dialog .choice,
.palette-box .choice {
  display: flex; gap: var(--sp-3); align-items: flex-start;
  padding: var(--sp-3); margin-block-start: var(--sp-2);
  border: 1px solid var(--border); border-radius: var(--radius);
  cursor: pointer;
}

.sheet-dialog .choice:hover,
.palette-box .choice:hover { background: var(--surface-2); }
.sheet-dialog .choice:has(input:checked),
.palette-box .choice:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
.sheet-dialog .choice b,
.palette-box .choice b { display: block; font-size: var(--fs-sm); }
.sheet-dialog .choice small,
.palette-box .choice small { display: block; color: var(--muted); margin-block-start: 2px; }

.sheet-dialog .dialog-actions,
.palette-box .dialog-actions {
  display: flex; justify-content: flex-end; gap: var(--sp-2);
  margin-block-start: var(--sp-4);
}

.version-row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-2) 0; border-block-end: 1px solid var(--border);
  font-size: var(--fs-sm);
}

.version-row:last-child { border-block-end: none; }

/* The document viewer's save state, mirroring the spreadsheet's (backlog 8.10). */
.doc-saved {
  font-size: var(--fs-xs); color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-inline-size: 52ch;
}

.doc-saved.is-dirty { color: var(--text); font-weight: 600; }
.doc-saved.is-problem { color: var(--critical); }

/* =================================================================================================
   NAMES THE MARKUP WAS ALREADY USING (client, 27 Aug)

   Forty-four class names appeared in pages and islands with no rule behind them. A class name is a
   contract: getting one wrong fails SILENTLY — the element renders, unstyled, and looks like a
   layout somebody never finished rather than a name somebody guessed.

   The ones that were reaching for an existing class were corrected in the markup instead; these are
   the ones that named something real which nobody had written. Each is deliberately small — the job
   is to make the markup true, not to redesign screens nobody has complained about.
   ============================================================================================== */

/* A page that is its own column, on the account and portal screens that are not inside the
   workspace shell. Matches .pad, which the rest of the product uses for the same job. */
.wrap, .notif-page { inline-size: min(1100px, 100%); margin-inline: auto; padding: var(--sp-5); }

/* A row whose columns are different heights and should line up at the TOP rather than centre —
   two stacked lists beside each other, where centring makes the shorter one float. */
.row-top { align-items: flex-start; }

/* Text carrying the critical colour without the tag chrome, for a sentence inside a paragraph. */
.is-critical { color: var(--critical); }

/* The two halves of a .stat. The container already lays them out; these are what they look like,
   and they were styled through `.stat > b` for the ones that used a <b>. */
.stat-label { font-size: var(--fs-micro); font-weight: 650; letter-spacing: .08em; text-transform: uppercase; color: var(--faint); }
.stat-value { font-size: var(--fs-2xl); font-weight: 650; letter-spacing: -.022em; line-height: 1.15; }

/* A block INSIDE a panel: a nested group, usually a <details>. Quieter than the panel around it,
   because two identical cards nested read as a mistake. */
.panel-sub { border: 1px solid var(--border); border-radius: var(--r2); padding: var(--sp-3) var(--sp-4); background: var(--surface-2); }
.panel-sub + .panel-sub { margin-block-start: var(--sp-2); }
.panel-sub > summary { cursor: pointer; }

/* An expandable editor on the admin screens — the same shape as .panel-sub and named for what it
   holds rather than where it sits. */
.editbox { border: 1px solid var(--border); border-radius: var(--r2); padding: var(--sp-3) var(--sp-4); background: var(--surface-2); }
.editbox > summary { cursor: pointer; display: flex; align-items: center; gap: var(--sp-2); }
.editbox[open] > summary { margin-block-end: var(--sp-3); }

/* A paragraph that has to be READ rather than skimmed past — a warning about what will not happen.
   `.warn` beside it carries the colour; this is the shape. */
.callout { border-inline-start: 2px solid var(--border-strong); padding: var(--sp-2) var(--sp-4); margin-block: var(--sp-3); background: var(--surface-2); border-radius: 0 var(--r2) var(--r2) 0; }
.callout.warn { border-inline-start-color: var(--warning-fg); background: var(--warning-bg); color: var(--warning-fg); }

/* A rule between sections of a long form. */
.sep { border: 0; border-block-start: 1px solid var(--border); margin-block: var(--sp-5); }

/* A set of mutually exclusive options laid out as cards rather than as a select. */
.choices { display: flex; flex-wrap: wrap; gap: var(--sp-3); }

/* The buttons that finish a form. Pushed apart from the fields above them, so Save never sits
   tight against the last input. */
.form-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); margin-block-start: var(--sp-5); }

/* A BUTTON that has to read as a link — "Mark all read" in the notification bell, which is an
   action and must be a <button> for the keyboard, but would look absurd as one. */
.lnk { background: none; border: 0; padding: 0; font: inherit; color: var(--accent); cursor: pointer; }
.lnk:hover { text-decoration: underline; }
.lnk[hidden] { display: none; }

/* Who said it, on a shared conversation. The question is quiet and the answer is the page — the
   opposite emphasis to the composer, because a shared link is READ and never replied to. */
.sc-you .sc-who { color: var(--faint); }
.sc-you { padding-inline-start: var(--sp-4); border-inline-start: 2px solid var(--border); }
.sc-ai .sc-who { color: var(--accent); }
.sc-cmp { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }

/* The controls on a row in a picker list, revealed beside the row rather than under it. */
.pe-row-actions { display: inline-flex; align-items: center; gap: 2px; flex-shrink: 0; }

/* The tool switches on the agent form, once "only these" has been chosen. */
.pe-tools { display: flex; flex-direction: column; gap: var(--sp-2); margin-block-start: var(--sp-3); padding-inline-start: var(--sp-5); }

/* One vendor's models in the picker. The heading is already styled; this is the group around it. */
.mpick-group { display: flex; flex-direction: column; gap: 1px; margin-block-end: var(--sp-3); }

/* A run that has stopped and is ASKING, rather than one carrying on by itself. The base
   .run-carry-on is the layout; this is the colour that makes it a question. */
.run-awaiting { border-color: var(--warning-fg); background: var(--warning-bg); }

/* Which site a web source came from, beside the date. .src-when already styles its neighbour. */
.src-site { font-weight: 600; color: var(--muted); }

/* Starter prompts grouped under a category label. */
/* THE HEADING SITS WITH ITS OWN BUTTONS (client, 5 Sep).

   .lbl sets no alignment, so it inherited the centring of the empty state around it, while the
   buttons below it are flex items packed to the start. Every category heading therefore floated
   over a block it did not line up with - and because the columns are content-width, each one
   floated by a different amount. Centring only reads as centred when the thing under it is too. */
.starter-group { display: flex; flex-direction: column; gap: 6px; margin-block-end: var(--sp-4); text-align: start; }
/* ONE TO A LINE, because wrapping made a column that lined up with nothing (client, 5 Sep).

   These were laid out as wrapping chips. The buttons are all different widths, so each category
   wrapped at a different point and produced an accidental second column - "Shorten this" and
   "Explain it simply" stacked into a block that matched no other block on the screen. Aligning
   the headings fixed the headings and left that, because the raggedness was never about the
   headings.

   A vertical list has nothing to wrap and nothing to misalign. Two or three items to a category
   makes it compact rather than long, and every link now starts on the same axis as its heading. */
.starter-row { display: flex; flex-direction: column; align-items: start; gap: 2px; }

/* =================================================================================================
   TASK PLANNER (client, 25 Aug — BUG-0033)

   "Add a Task Planner on right in chat view, like the image mock up attached. (Fourth section)"

   His annotation circles the Run settings rail and points at what was the fourth section, then
   circles the empty space beneath it — so this is a section INSIDE the rail that already exists,
   not a fourth panel. Direction A of Docs/mockups/task-planner.html, chosen because every figure
   in it already exists: the run, its stages and the approval gate are all built.

   Lifted verbatim from the mockup so the two cannot drift — his standing rule from the App Hub
   round is that the built thing is identical to the drawing.
   ============================================================================================== */

/* THE ONE THING WAITING ON A PERSON, above the list rather than inside it. A run that has stopped
   to ask is not on a step — it is between them — and drawing it as a step would put a question in
   a column of statements. */
.plan-need {
  border: 1px solid var(--warning-fg); background: var(--warning-bg);
  border-radius: var(--r2); padding: var(--sp-3); margin-block-end: var(--sp-3);
}
.plan-need b { display: block; font-size: var(--fs-xs); color: var(--warning-fg); }
.plan-need span { display: block; font-size: var(--fs-sm); margin-block-start: 2px; }

.plan-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }

.plan-step { display: flex; align-items: flex-start; gap: 8px; font-size: var(--fs-sm); color: var(--muted); }

/* A ring, filled when done. Not a tick glyph: at 12px a tick is a smudge, and the three states
   have to be told apart at a glance from across a desk. */
.plan-step > i {
  inline-size: 12px; block-size: 12px; border-radius: 50%; flex-shrink: 0;
  border: 1.5px solid var(--border-strong); margin-block-start: 3px;
}
.plan-step.is-done { color: var(--text); }
.plan-step.is-done > i { background: var(--ok-fg); border-color: var(--ok-fg); }
.plan-step.is-now { color: var(--text); font-weight: 600; }
.plan-step.is-now > i { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.plan-step.is-failed { color: var(--critical); }
.plan-step.is-failed > i { background: var(--critical); border-color: var(--critical); }

.plan-foot { margin: var(--sp-3) 0 0; font-size: var(--fs-xs); color: var(--faint); }

/* ---- What Blue Vision remembers (§17.5, extended to ordinary chat) ---------------------------

   A list of facts with a way to remove each one. Its own rules rather than the notification
   screen's .row, which is scoped to .notif-prefs and did nothing here — the first version rendered
   as a paragraph with a button jammed into the middle of it. */
.memory-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

/* A rule BETWEEN entries, not around each. These are items in one list, not four cards. */
.memory-list > li + li { border-block-start: 1px solid var(--border); }

.memory-list > li {
  display: flex;
  align-items: start;
  justify-content: space-between;
  gap: var(--sp-4);
  padding-block: var(--sp-3);
}

/* The fact itself gets the width; the button takes what it needs and no more. */
.memory-list > li > div { min-inline-size: 0; }
.memory-list > li form { flex: none; }

/* Learned-on, under the fact and quieter than it — the fact is what the model acts on and the
   date is only ever read when somebody is working out where it came from. */
.memory-list p {
  margin: var(--sp-1) 0 0;
  font-size: var(--fs-xs);
  color: var(--faint);
}

/* The panel bar here holds a heading and a destructive button; without this they sit shoulder to
   shoulder as though the heading were a label for it.

   SCOPED TO THE PANEL THAT HOLDS THE LIST, not to .account-page. Written the wider way first, which
   would have re-laid out the panel bar on every account screen in the product — billing, profile,
   notifications, spending — to fix one page nobody had opened yet. A change to a dozen screens is
   not a smaller change than a class name. */
.panel:has(> .panel-body > .memory-list) > .panel-bar,
.panel:has(> .panel-body > .hint) > .panel-bar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4);
}

/* ---- What was done for you (backlog 18.18) ----------------------------------------------------

   A list of actions with the vendor's verdict beside each. Its own rules rather than the memory
   list's: this one carries a state on the right that has to stay put while the description beside
   it wraps to two lines. */
.receipt-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.receipt-list > li + li { border-block-start: 1px solid var(--border); }

.receipt-list > li {
  display: flex;
  align-items: start;
  justify-content: space-between;
  gap: var(--sp-4);
  padding-block: var(--sp-3);
}

.receipt-list > li > div { min-inline-size: 0; }

/* Who or what it touched, beside the action rather than under it — "Sent an email" and
   "to finance@acme.com" are one sentence and reading them as two costs a line. */
.receipt-what {
  margin-inline-start: var(--sp-2);
  color: var(--muted);
  font-size: var(--fs-sm);
  overflow-wrap: anywhere;
}

.receipt-list p {
  margin: var(--sp-1) 0 0;
  font-size: var(--fs-xs);
  color: var(--faint);
}

/* The reason, on the two states where there is one. Slightly stronger than the timestamp above it,
   because "we could not reach Microsoft" is the whole content of that row. */
.receipt-note { color: var(--muted) !important; }

/* THE VERDICT. Shape and word, never colour alone: a reader who cannot distinguish these still has
   the label, which is why the text says "Not found" rather than showing a red dot. */
.receipt-state {
  flex: none;
  font-size: var(--fs-xs);
  padding: .15rem .5rem;
  border-radius: var(--r1);
  border: 1px solid var(--border);
  white-space: nowrap;
}

.receipt-state.is-ok   { color: var(--success, #2f6f4f); border-color: currentColor; }
.receipt-state.is-bad  { color: var(--danger, #c0362c); border-color: currentColor; }
.receipt-state.is-idle { color: var(--faint); }

@media (max-width: 560px) {
  .receipt-list > li { flex-direction: column; gap: var(--sp-2); }
  .receipt-state { align-self: start; }
}

/* The evidence line under a step that acted (backlog 18.18).

   ON ITS OWN LINE, via flex-basis, because the step row wraps: without it the proof runs on after
   the duration and "1.8s ✓Booked a Zoom meeting" reads as one continuous phrase.

   Not folded away like the working above it, and deliberately: the working is the customer's own
   code and output, while this is one sentence saying an email went to somebody — the thing they most
   want to see without pressing anything. */
.tool-step-proof {
  flex-basis: 100%;
  margin: var(--sp-1) 0 0;
  font-size: var(--fs-xs);
  color: var(--muted);
  overflow-wrap: anywhere;
}

/* A tick, in the success colour, carrying no meaning on its own — the sentence beside it says what
   happened, so a reader who cannot distinguish the colour loses nothing. */
.tool-step-proof-mark {
  color: var(--success, #2f6f4f);
  margin-inline-end: var(--sp-1);
  font-weight: 600;
}

/* ============================================================================
   ROOMS — several people and an assistant in one thread (Rooms V2, 19 September 2026).

   Rebuilt against room.tsx as it stands at 12b5e96. The screen is three columns: the room rail,
   the room itself (a conversation OR its task board), and a details panel that can be put away.

   TWO THINGS THE HANDOFF ASKED FOR THAT THIS DELIBERATELY DOES NOT DO.

   Its --primary is a violet, #4B44D6, used for every filled control. NETFIVE's accent belongs to
   the ACCOUNT — the client chose red on 19 September and any account may change it again — so
   every accent here is var(--accent), and anything filled with it carries var(--on-accent), the
   pair that is guaranteed to read in both themes. A room that hard-coded the handoff's violet
   would be the one screen in the product that ignores the appearance picker.

   Its green, amber and red are likewise mapped onto the product's own status tokens (--ok,
   --warning, --critical and their pairs) rather than re-declared. The six room tile colours are
   the one exception, below: nothing in NETFIVE means "this room, not that one".

   THE 900px BREAKPOINT IS THE SAME ONE the workspace uses. The panel folds to an overlay first,
   at 1100px, because it is the column a person can do without; the rail and the room become two
   screens only when there is genuinely no room for both.
   ============================================================================ */

/* Six colours for six people, and no more. A seventh person shares with the
   first, which is correct: the colour is a memory aid for telling apart the
   handful of people talking right now, not an identifier. Generating a hue per
   member is how a room ends up with two indistinguishable pale blues.

   Pairs, not single values: the initials sit ON the tint, so each one needs a
   text colour that passes against its own ground rather than against paper. */
:root {
  --tint-0-bg: #e3ecfa; --tint-0-fg: #1d4f8f;
  --tint-1-bg: #fae4dc; --tint-1-fg: #8f3d1f;
  --tint-2-bg: #dcf0e8; --tint-2-fg: #14634a;
  --tint-3-bg: #f7ecd4; --tint-3-fg: #6f4c0a;
  --tint-4-bg: #f6e3ef; --tint-4-fg: #7b2f5c;
  --tint-5-bg: #e6e6f5; --tint-5-fg: #3d3d80;

  /* THE ONE PLACE ROOMS CARRIES COLOURS OF ITS OWN, and the reason is that the product has no
     token for what these say. A room's tile is not a status, not a selection and not the accent:
     it is an arbitrary mark somebody picked so that six rooms in a rail are six different shapes
     in the corner of the eye. Borrowing --accent for it would make every room look selected, and
     borrowing --ok or --critical would make a room look like it was in a state.

     The light values are the handoff's --room-1…--room-6 unchanged. Each carries white at 5:1 or
     better. The dark set is lifted just far enough to separate from a near-black page while still
     carrying white at 4.5:1 or better — which is why the letter colour is one token for both
     themes rather than a pair per tile. */
  --room-1: #4b44d6; --room-2: #c0283b; --room-3: #7c3aed;
  --room-4: #1d6fc2; --room-5: #1e7b43; --room-6: #a15c07;
  --on-room: #ffffff;
}

[data-theme="dark"] {
  --tint-0-bg: #1b2c46; --tint-0-fg: #9dc2f0;
  --tint-1-bg: #3a2118; --tint-1-fg: #eaa484;
  --tint-2-bg: #12312a; --tint-2-fg: #6fd6b4;
  --tint-3-bg: #362a12; --tint-3-fg: #dcb765;
  --tint-4-bg: #34182b; --tint-4-fg: #e29ecd;
  --tint-5-bg: #24244a; --tint-5-fg: #a8a8e6;

  --room-1: #6259e0; --room-2: #d03a4b; --room-3: #8b51ee;
  --room-4: #2673c4; --room-5: #1f8146; --room-6: #a66209;
}

/* ---- the three columns -------------------------------------------------- */

/* The island's mount. A flex row whose one child is the grid below — this is what gives the
   island the screen's full height without the mount itself being the grid, which would nest one
   grid inside another and collapse the room to nothing. */
.rooms-mount { flex: 1; display: flex; min-block-size: 0; min-inline-size: 0; }

/* THE PANEL IS A COLUMN ONLY WHEN IT IS THERE.

   `Panel` is mounted in two different places by room.tsx: beside the board it is a child of
   .rooms, and beside the conversation it is a child of .rooms-thread. Both are handled, and both
   with :has rather than with a class the script would have to remember to set — a layout that
   depends on a flag is a layout that goes wrong the first time somebody forgets the flag.

   The middle column is minmax(0, 1fr) rather than 1fr because a long word in a message otherwise
   sets the track's minimum width and pushes the panel off the side. */
.rooms {
  /* flex:1 inside .rooms-mount, exactly as .ws is inside .screen — the shell never scrolls and
     each screen owns its own scrolling, so a block-size of 100% here would overflow rather than
     fill. */
  flex: 1;
  min-inline-size: 0;
  min-block-size: 0;
  display: grid;
  grid-template-columns: 264px minmax(0, 1fr);
  background: var(--bg);

  /* The panel lies over this box once the window is too narrow to give it a column. */
  position: relative;
}

.rooms:has(> .rooms-panel) { grid-template-columns: 264px minmax(0, 1fr) 320px; }

/* ---- the rail ----------------------------------------------------------- */

.rooms-rail {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-block-size: 0;
  padding: var(--sp-4) var(--sp-3);
  overflow-y: auto;
  background: var(--surface-2);
  border-inline-end: 1px solid var(--border);
}

/* The mark stays the brand's red under every accent, the same rule the desk's own wordmark
   follows — it is the logo, not a piece of chrome somebody chose the colour of. */
.rooms-rail-brand {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  block-size: 36px;
  padding-inline: var(--sp-2);
  font-size: var(--fs-sm);
  font-weight: 800;
  letter-spacing: .08em;
  color: var(--text);
}

.rooms-rail-mark { font-size: var(--fs-2xl); font-weight: 900; line-height: 1; color: var(--a-ruby); }

.rooms-find {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-block: var(--sp-2);
  block-size: 38px;
  padding-inline: 10px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--surface);
  color: var(--muted);
  font-size: var(--fs-sm);
  text-align: start;
}

.rooms-find:hover { border-color: var(--border-strong); color: var(--text); }
.rooms-find-ic { flex: none; font-size: var(--fs-lg); line-height: 1; }
.rooms-find-text { flex: 1; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.rooms-find kbd {
  flex: none;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--fs-micro);
  padding: 1px 6px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--surface-2);
}

.rooms-rail-links { display: flex; flex-direction: column; gap: 2px; }

.rooms-rail-links a {
  display: flex;
  align-items: center;
  block-size: 36px;
  padding-inline: 10px;
  border-radius: 9px;
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: 600;
}

.rooms-rail-links a:hover { background: var(--surface-3); }

.rooms-rail-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-block-start: var(--sp-3);
  block-size: 30px;
  padding-inline: 10px;
}

.rooms-rail-head h2 {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
}

.rooms-rail-add {
  inline-size: 28px;
  block-size: 28px;
  border-radius: 7px;
  color: var(--muted);
  font-size: var(--fs-lg);
  line-height: 1;
}

.rooms-rail-add:hover { background: var(--surface-3); color: var(--text); }

.rooms-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.rooms-item {
  display: flex;
  align-items: center;
  gap: 10px;
  inline-size: 100%;
  block-size: 36px;
  padding-inline: 10px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  text-align: start;
  cursor: pointer;
}

.rooms-item:hover { background: var(--surface-3); }
.rooms-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* THE CURRENT ROOM IS FILLED, NOT TINTED. A tint is what hover already is, and the rail is read
   at a glance from the far side of a desk. --accent-fill with --on-accent because this is a solid
   accent surface with words on it, and that pair is the one guaranteed to stay legible whichever
   accent the account is on and whichever theme it is in. */
.rooms-item.on { background: var(--accent-fill); color: var(--on-accent); font-weight: 700; }

/* The badge would otherwise be accent-on-accent — invisible — on the row it matters most on. */
.rooms-item.on .rooms-unread { background: var(--on-accent); color: var(--accent-fill); }

/* ---- a long name, readable on hover (18.23) ----------------------------------------------------

   A truncated title says a room exists and not WHICH one: "Plan limits 17889613…" and "Plan limits
   17889635…" are the same string on screen. Hovering slides the text far enough to read the end of
   it, and lets it go when the pointer leaves.

   HOW FAR IT SLIDES, AND WHY NOT A MARQUEE. `100cqw - 100%` is the container's width minus the
   text's own — the exact overflow. A name barely too long moves barely at all; one twice too long
   moves twice as far; one that FITS produces a positive number, which min() clamps to zero so it
   never moves at all. A fixed-distance marquee gets every one of those three cases wrong.

   THREE THINGS IT MUST NOT DO, all handled by the query below:

     - Not for somebody who asked for less motion. Gated on prefers-reduced-motion; the ellipsis
       simply stays.
     - Not on a touch screen. There is no hover on a phone — the browser fires one on tap and leaves
       it stuck, so a tapped room would scroll its own name for ever. Gated on a device that
       genuinely hovers with a fine pointer.
     - Not fighting the ellipsis. text-overflow only applies while text is clipped, so it is dropped
       for the moment the text is sliding and comes straight back. */
.rooms-item-name {
  flex: 1;
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;

  /* What 100cqw below is measured against. */
  container-type: inline-size;
}

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  /* The whole row is the trigger, not the text — a pointer anywhere on it should reveal the title. */
  .rooms-item:hover .rooms-item-name,
  .rooms-item:focus-visible .rooms-item-name {
    text-overflow: clip;
  }

  .rooms-item:hover .rooms-item-name > span,
  .rooms-item:focus-visible .rooms-item-name > span {
    display: inline-block;
    white-space: nowrap;
    animation: name-slide 7s linear 0.45s infinite;
  }
}

/* The pause before it starts is deliberate: a pointer crossing the list on its way somewhere else
   should not set every name it passes into motion. */
@keyframes name-slide {
  0%, 10%   { transform: translateX(0); }
  45%, 60%  { transform: translateX(min(0px, calc(100cqw - 100%))); }
  95%, 100% { transform: translateX(0); }
}

.rooms-rail-new {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-block-start: var(--sp-2);
  block-size: 36px;
  padding-inline: 10px;
  border-radius: 9px;
  color: var(--accent);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: 700;
  text-align: start;
}

.rooms-rail-new:hover { background: var(--accent-soft); }

/* Count first, colour second: somebody who cannot distinguish the accent still reads the number,
   and the aria-label says what it counts. A plain unread count is grey — it says "there is
   something here", which is not urgent enough to spend a colour on. */
.rooms-unread {
  flex: none;
  min-inline-size: 20px;
  block-size: 20px;
  padding-inline: 6px;
  border-radius: var(--rf);
  background: var(--surface-4);
  color: var(--text);
  font-size: var(--fs-micro);
  font-weight: 800;
  line-height: 20px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* SOMEBODY SAID YOUR NAME, which is the one thing in this list worth pulling an eye across the
   screen. Same size and place as the plain count it replaces, so the list does not reflow as
   messages arrive. */
.rooms-unread.is-mention { background: var(--accent-fill); color: var(--on-accent); }

/* ---- the room's own tile ------------------------------------------------- */

/* The same mark in the rail, in the header and in the switcher, so a room is recognised before it
   is read. */
.rooms-tile {
  flex: none;
  display: inline-grid;
  place-items: center;
  inline-size: 20px;
  block-size: 20px;
  border-radius: 5px;
  background: var(--room-1);
  color: var(--on-room);
  font-size: var(--fs-micro);
  font-weight: 800;
}

.rooms-tile.is-lg { inline-size: 38px; block-size: 38px; border-radius: 10px; font-size: var(--fs-lg); }

/* The six colours themselves are declared with the swatch that picks them, beside the create
   dialog below — one place for the pair, so a colour can never exist in the picker and not on the
   tile. */

/* ---- the room: header, rules, thread, composer -------------------------- */

/* FOUR ROWS AND, WHEN THE PANEL IS OPEN, TWO COLUMNS. The four in-flow children are pinned to
   column 1 explicitly: auto-placement fills a two-column grid row by row, so without this the
   rule chips would land beside the header rather than under it. */
.rooms-thread {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  min-inline-size: 0;
  min-block-size: 0;
  background: var(--surface);
}

.rooms-thread > .rooms-head,
.rooms-thread > .rooms-rules,
.rooms-thread > .rooms-messages,
.rooms-thread > .rooms-foot { grid-column: 1; }

.rooms-thread:has(> .rooms-panel) { grid-template-columns: minmax(0, 1fr) 320px; }
.rooms-thread > .rooms-panel { grid-column: 2; grid-row: 1 / -1; }

/* Nothing picked: one centred block, and no rows to reserve. */
.rooms-thread.empty { grid-template-rows: minmax(0, 1fr); place-items: center; }

.rooms-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5);
  background: var(--surface);
}

.rooms-head-main { flex: 1; min-inline-size: 0; }

.rooms-head-main h1 {
  margin: 0;
  font-size: var(--fs-xl);
  font-weight: 800;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ONE SENTENCE OUT OF FOUR FACTS. The markup lists goal, progress, seats and who is here as bare
   spans with no punctuation between them, because which of them exist depends on the room — so the
   separator is drawn here, on every fact after the first, and a room with only two of them reads
   as correctly as a room with all four. */
.rooms-head-facts {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px var(--sp-2);
  margin: 2px 0 0;
  font-size: var(--fs-xs);
  color: var(--muted);
}

.rooms-head-facts > * + *::before { content: "\00B7"; margin-inline-end: var(--sp-2); color: var(--faint); }

/* The goal is the answer to "what is this room for", so it is the one fact set in the room's own
   ink rather than in the grey the rest of the line uses. */
.rooms-goal { display: inline-flex; align-items: baseline; gap: 5px; color: var(--text); font-weight: 700; }
.rooms-goal-ic { color: var(--accent); }
.rooms-progress-text { font-variant-numeric: tabular-nums; }

.rooms-call { flex: none; }

.rooms-icon-btn {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 34px;
  block-size: 34px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--surface);
  color: var(--muted);
  font-size: var(--fs-lg);
  line-height: 1;
}

.rooms-icon-btn:hover { background: var(--surface-3); color: var(--text); border-color: var(--border-strong); }
.rooms-icon-btn[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent); }

/* Only on the narrow layout, where the rail is a separate screen. Wide, the rail is visible and a
   Back button would point at what is already there. */
.rooms-back { display: none; }

/* ---- people ------------------------------------------------------------- */

.rooms-people { display: flex; margin: 0; padding: 0; list-style: none; }

.rooms-face {
  flex: none;
  display: grid;
  place-items: center;
  inline-size: 30px;
  block-size: 30px;
  border: 2px solid var(--surface);
  border-radius: var(--rf);
  background: var(--surface-3);
  color: var(--muted);
  font-size: var(--fs-micro);
  font-weight: 800;
  letter-spacing: .02em;
}

.rooms-face.sm { inline-size: 22px; block-size: 22px; border-width: 0; font-size: 9.5px; }
.rooms-msg > .rooms-face { inline-size: 34px; block-size: 34px; border-width: 0; font-size: var(--fs-sm); }

/* OVERLAPPING IS FOR A CROWD (18.29).

   Every face used to overlap the one before it by 7px, however few there were. Two people in a room
   therefore cost one of them a letter — and the ringed face is raised above its neighbours so its
   ring is never half-covered, which meant the person who is HERE clipped the initials of the person
   who is not. "BA" read as "3A".

   So the pile stands apart until there are four of it, which is the point where the 7px it saves is
   worth more than the letter it costs. */
.rooms-people .rooms-face + .rooms-face { margin-inline-start: 3px; }
.rooms-people:has(> li:nth-child(4)) .rooms-face + .rooms-face { margin-inline-start: -7px; }
.rooms-people .rooms-face:first-child { margin-inline-start: 0; }

.rooms-face.tint-0 { background: var(--tint-0-bg); color: var(--tint-0-fg); }
.rooms-face.tint-1 { background: var(--tint-1-bg); color: var(--tint-1-fg); }
.rooms-face.tint-2 { background: var(--tint-2-bg); color: var(--tint-2-fg); }
.rooms-face.tint-3 { background: var(--tint-3-bg); color: var(--tint-3-fg); }
.rooms-face.tint-4 { background: var(--tint-4-bg); color: var(--tint-4-fg); }
.rooms-face.tint-5 { background: var(--tint-5-bg); color: var(--tint-5-fg); }

/* The assistant is a rounded square, not a circle — it is the one voice in the room that is not a
   person, and the shape says so before the letters are read. Filled with the accent, so it takes
   the readable pair. */
.rooms-face.ai { border-radius: var(--r3); background: var(--accent-fill); color: var(--on-accent); }

/* Here now. A ring rather than the usual corner dot: at 30px a dot is four pixels of colour
   carrying the whole meaning, and the title attribute says it in words for anybody the colour does
   not reach.

   THE RING IS THE BORDER, NOT A SHADOW OUTSIDE IT. It was `box-shadow: 0 0 0 2px`, which paints
   beyond the element — and these avatars overlap each other by 7px, so the ring landed ON TOP of
   the neighbour to its inline-start and read as a green bite taken out of the face beside it. The
   second shadow, in the panel's own colour, is the gap that makes the ring look deliberate. */
.rooms-face.here {
  border-width: 2px;
  border-style: solid;
  border-color: var(--ok);
  position: relative;
  z-index: 1;
  box-shadow: 0 0 0 2px var(--surface);
}

.rooms-face.more { background: var(--surface-3); color: var(--muted); font-weight: 700; }

/* ---- the three room rules (2.2, F6) -------------------------------------
   These were settings four clicks away, and they decide the three things everybody in a room most
   needs to know. The owner gets a menu; everybody else gets the value and who set it — a member
   offered a dropdown that then refuses them is worse off than a member shown a fact. */

.rooms-rules {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  padding: 0 var(--sp-5) var(--sp-3);
  background: var(--surface);
  border-block-end: 1px solid var(--border);
}

.rooms-rules-label { font-size: var(--fs-xs); font-weight: 700; color: var(--muted); }

.rooms-rule { position: relative; display: inline-flex; }

.rooms-rule-btn,
.rooms-rule.is-read-only {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  block-size: 30px;
  padding-inline: 10px;
  border: 1px solid var(--border);
  border-radius: var(--rf);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-sm);
}

.rooms-rule-btn:hover { border-color: var(--border-strong); }
.rooms-rule-btn[aria-expanded="true"] { border-color: var(--accent); background: var(--accent-soft); }
.rooms-rule-label { color: var(--muted); }
.rooms-rule-btn b, .rooms-rule.is-read-only b { font-weight: 700; }
.rooms-rule-caret { color: var(--muted); font-size: var(--fs-xs); }

/* A member's chip carries who to ask instead of a control that would refuse them. */
.rooms-rule.is-read-only { background: var(--surface-2); }
.rooms-rule-by { color: var(--faint); font-size: var(--fs-xs); }

.rooms-rule-menu {
  position: absolute;
  inset-block-start: calc(100% + 6px);
  inset-inline-start: 0;
  z-index: 25;
  inline-size: 330px;
  max-inline-size: 90vw;
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0;
  padding: var(--sp-3);
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r4);
  box-shadow: var(--sh-3);
}

.rooms-rule-menu button {
  inline-size: 100%;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  text-align: start;
}

.rooms-rule-menu button:hover { background: var(--surface-3); }
.rooms-rule-menu .is-chosen { border-color: var(--accent); background: var(--accent-soft); }
.rooms-rule-choice { font-size: var(--fs-md); font-weight: 700; }

/* Every choice carries one line saying what it does, because "When mentioned / Every message /
   Never" are three phrases whose consequences — cost, mostly — are not on their faces. */
.rooms-rule-why { font-size: var(--fs-xs); color: var(--muted); line-height: 1.4; }

/* ---- messages ----------------------------------------------------------- */

.rooms-messages {
  min-block-size: 0;
  overflow-y: auto;
  overflow-anchor: none;
  padding-block: var(--sp-2);
  display: flex;
  flex-direction: column;
  background: var(--surface);
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  .rooms-messages { scroll-behavior: auto; }
}

/* A NEW ROOM READS FROM THE BOTTOM, like every other conversation. Three messages pinned to the top
   of a tall screen, with the composer a long way below them, reads as a page that has failed to
   load the rest. An auto margin on the first child rather than justify-content: flex-end, which
   clips the top of the thread once it does overflow. */
.rooms-messages > :first-child { margin-block-start: auto; }

/* The messages column is already a flex column, so the empty state only needs somewhere to sit. */
.rooms-messages.is-empty { justify-content: center; }

.rooms-older {
  display: grid;
  place-items: center;
  padding-block: var(--sp-2);
  color: var(--muted);
  font-size: var(--fs-xs);
}

/* "Today", "Yesterday" or the date, with a rule either side. The lines are drawn rather than put in
   the markup, because they are decoration and a screen reader should hear the day and nothing. */
.rooms-dayline {
  flex-basis: 100%;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: 0;
  padding: var(--sp-2) var(--sp-5);
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--muted);
}

.rooms-dayline::before,
.rooms-dayline::after { content: ""; flex: 1; block-size: 1px; background: var(--border); }

/* ONE ROW PER MESSAGE, NOT A BUBBLE EITHER SIDE (Rooms V2).

   A room is read the way a document is — top to bottom, scanning for who said what — and a
   left/right bubble layout makes that scan impossible the moment there are more than two people in
   it. So every message is the same shape: a face, a name, and the words. Your own messages are the
   one exception the markup makes, and it is deliberate: they carry no avatar, so they are indented
   to the same text column as everybody else's. */
.rooms-msg {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-5);
}

.rooms-msg:hover { background: var(--surface-2); }
.rooms-msg.mine, .rooms-msg.run-on:not(.mine) { padding-inline-start: calc(var(--sp-5) + 34px + var(--sp-3)); }

/* A run of messages from one person is one block with one name on it. The gap closing is what makes
   it read as continued speech rather than as a list. */
.rooms-msg.run-on { padding-block-start: 0; }

/* A RAIL AND A WASH, because this is the one state that should pull somebody's eye across a screen:
   the server decided that this message names YOU. It has to survive being next to any of the other
   states, which is why it is on the row rather than on the words. */
.rooms-msg.is-for-me { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }

/* Arrived at from a task, a pin or a decision. It fades rather than staying marked, because the
   mark answers "which one" and that question stops being asked the moment it is answered. */
.rooms-msg.is-jumped { animation: rooms-jumped 2.4s var(--ease) 1; }

@keyframes rooms-jumped {
  0%, 35% { background: var(--accent-soft); }
  100%    { background: transparent; }
}

@media (prefers-reduced-motion: reduce) {
  .rooms-msg.is-jumped { animation: none; background: var(--accent-soft); }
}

/* THE NAME AND THE MODEL CHIP SHARE A LINE; EVERYTHING ELSE TAKES ITS OWN.

   Flex-wrap rather than a column, so "Assistant" and "Lowest cost · [model]" read as one label on
   the answer below them — which is what they are. The catch-all below gives every other child a
   full line without this rule having to know the name of any component that might be dropped in
   here, such as the waiting line. */
.rooms-bubble {
  flex: 1;
  min-inline-size: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px var(--sp-2);
}

.rooms-bubble > * { flex-basis: 100%; min-inline-size: 0; }
.rooms-bubble > .rooms-who,
.rooms-bubble > .rooms-chip { flex-basis: auto; }

.rooms-who { font-size: var(--fs-md); font-weight: 700; color: var(--text); }

.rooms-text {
  margin: 0;
  font-size: var(--fs-md);
  line-height: 1.5;

  /* What people type contains newlines and they mean something. Collapsing them turns a three-line
     list into one long sentence. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* WHY THE ASSISTANT LOOKS DIFFERENT AT ALL. In a room, an answer is the one message somebody paid
   for, produced by a model that may not be the room's default, and which any member may ask to be
   compared against another. None of that is true of "yes, Wednesday works".

   The box is on the words and not on the whole block, so the name, the model chip and the line
   saying who was charged stay outside it — exactly where the handoff draws them. */
.rooms-msg.ai .rooms-text {
  padding: var(--sp-3) 14px;
  border: 1px solid var(--border);
  border-radius: var(--r4);
  background: var(--surface);
}

.rooms-deleted { font-style: italic; color: var(--muted); }
.rooms-edited { font-size: var(--fs-xs); font-style: italic; color: var(--muted); }

/* An @ anywhere in a message. Cosmetic — it says "this addresses somebody". Whether it addresses
   the reader is a different fact, decided by the server, and drawn on the row above. */
.rooms-at {
  padding: 0 3px;
  border-radius: 4px;
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 700;
}

/* Still writing. An inline block on the last line rather than a spinner beside the message, so it
   sits where the next word will appear. */
.rooms-caret {
  display: inline-block;
  inline-size: 7px;
  block-size: 1em;
  vertical-align: text-bottom;
  margin-inline-start: 2px;
  background: var(--accent);
  animation: rooms-blink 1s steps(2, start) infinite;
}

@keyframes rooms-blink { to { visibility: hidden; } }

@media (prefers-reduced-motion: reduce) {
  .rooms-caret { animation: none; opacity: .55; }
}

.rooms-warn { font-size: var(--fs-xs); font-weight: 600; color: var(--warning-fg); }

/* The model's name sits ABOVE the answer, not under it. It is the label on what follows — which
   model you are about to read — and a label after the thing it labels is a footnote. */
.rooms-answer-model { align-self: center; }

/* "Asked by James · charged to James", on every finished answer, to everybody in the room. A room
   spends somebody's credits every time the assistant speaks, and before this the answers were
   anonymous and free-looking. */
.rooms-answer-meta { margin: 0; font-size: var(--fs-xs); color: var(--muted); }

/* A decision is a card and a notice is a line, and neither is somebody talking: no avatar, no name,
   no hover menu and nothing to reply to. Indented to the text column so it reads as something the
   room said rather than as a second kind of message. */
.rooms-decision-card {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin: 6px var(--sp-5) 6px calc(var(--sp-5) + 34px + var(--sp-3));
  padding: var(--sp-3) 14px;
  border: 1px solid color-mix(in srgb, var(--ok) 40%, transparent);
  border-radius: var(--r4);
  background: var(--ok-bg);
}

/* The tick is lettered in the status colour's own ground rather than in white: on the light theme
   white on green is 3.3:1, and on the dark theme the green is bright enough that white all but
   disappears. The pair inverts with the theme and is legible in both. */
.rooms-decision-ic {
  flex: none;
  display: grid;
  place-items: center;
  inline-size: 30px;
  block-size: 30px;
  border-radius: var(--rf);
  background: var(--ok);
  color: var(--ok-bg);
  font-size: var(--fs-sm);
  font-weight: 700;
}

.rooms-decision-head {
  margin: 0 0 3px;
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ok-fg);
}

.rooms-decision-text { margin: 0; font-size: var(--fs-md); line-height: 1.5; }

.rooms-notice {
  margin: 6px var(--sp-5);
  padding: var(--sp-2) var(--sp-3);
  border-radius: 9px;
  background: var(--surface-2);
  color: var(--muted);
  font-size: var(--fs-sm);
}

/* ---- what can be done with one message (2.2) ----------------------------
   React, make a task, record a decision, pin. It is in the markup always and shown on hover or
   focus, rather than mounted on hover — a menu that only exists while the pointer is over it cannot
   be reached by the keyboard at all, and that is most of what these buttons are for. */

.rooms-acts {
  position: absolute;
  inset-block-start: -10px;
  inset-inline-end: var(--sp-5);
  z-index: 2;
  display: flex;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--surface);
  box-shadow: var(--sh-2);
  opacity: 0;
}

.rooms-msg:hover .rooms-acts,
.rooms-msg:focus-within .rooms-acts { opacity: 1; }

/* No hover to reveal them with, so they are simply there — faintly, so they do not compete with
   the words. */
@media (hover: none) { .rooms-acts { opacity: .75; } }

.rooms-act {
  display: inline-grid;
  place-items: center;
  inline-size: 28px;
  block-size: 28px;
  border-radius: 7px;
  color: var(--muted);
  font-size: var(--fs-sm);
  line-height: 1;
}

.rooms-act:hover { background: var(--surface-3); color: var(--text); }
.rooms-act[aria-pressed="true"] { color: var(--accent); }

/* ---- files on a message -------------------------------------------------- */

.rooms-files { display: grid; gap: 4px; margin: 0; padding: 0; list-style: none; }

.rooms-file {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  max-inline-size: 100%;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-sm);
}

.rooms-file:hover { border-color: var(--accent); }
.rooms-file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- the empty room (18.29) ---------------------------------------------
   "Nothing said here yet." was four words in the top-left of a 400px void, at the one moment a
   person has nothing to copy from and the most to learn. The starters are not decoration: each one
   is a thing this room can actually do, and pressing one puts it in the box rather than sending it,
   so nobody is charged for reading an example. */

.rooms-empty {
  margin: auto;
  padding: 32px 20px;
  max-inline-size: 40rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}

.rooms-empty h2 { margin: 0; font-size: var(--fs-lg); font-weight: 700; }
.rooms-empty p { margin: 0 0 var(--sp-3); color: var(--muted); font-size: var(--fs-sm); }
.rooms-empty ul { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 0; list-style: none; }

.rooms-empty button {
  padding: 4px 11px;
  border: 1px solid var(--border);
  border-radius: var(--rf);
  background: var(--surface-2);
  color: var(--muted);
  font-family: inherit;
  font-size: var(--fs-xs);
}

.rooms-empty button:hover { color: var(--text); border-color: var(--accent); }
.rooms-empty .btn { border-radius: var(--r1); }

/* ---- the composer bar ---------------------------------------------------- */

/* ONE BAR, THREE ROWS: anything the room could not do, the box itself, and the cost line with the
   shortcuts beside it. The cost line and the shortcuts share a row because they are the same
   sentence read two ways — what the next answer costs, and the three ways to ask for one. */
.rooms-foot {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5) var(--sp-4);
  background: var(--surface);
  border-block-start: 1px solid var(--border);
}

.rooms-foot > .rooms-composer,
.rooms-foot > .rooms-confirm,
.rooms-foot > .rooms-failed { flex-basis: 100%; }

/* WHO IS TYPING — FLOATED, NOT RESERVED.

   This held a min-block-size so the composer would not jump when somebody started typing. The
   reasoning was right and the cost was wrong: every room, all day, carried 19px of empty space
   above its message box for a line that is almost never there. Lifted out of the flow instead, over
   the bottom of the message list, where every chat application puts it — no gap when nobody is
   typing AND no jump when somebody starts. */
.rooms-typing {
  position: absolute;
  inset-block-end: 100%;
  inset-inline-start: var(--sp-5);
  margin: 0 0 2px;
  padding: 1px 8px;
  border-radius: var(--r1);
  background: var(--surface);
  color: var(--muted);
  font-size: var(--fs-xs);
  font-style: italic;
  pointer-events: none;
}

.rooms-typing:empty { display: none; }

/* NOBODY IS CHARGED WITHOUT SEEING IT FIRST (1.4, 2.7, F5). Above the composer, before sending,
   with the question still in the box: the owner was paying for this room and their budget has gone,
   so the next answer falls to whoever asks for it. Amber rather than red — this is a cost, not a
   failure. */
.rooms-confirm {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3);
  border: 1px solid var(--warning);
  border-radius: var(--r2);
  background: var(--warning-bg);
}

.rooms-confirm p { flex: 1 1 240px; margin: 0; color: var(--warning-fg); font-size: var(--fs-sm); font-weight: 550; }
.rooms-confirm-buttons { display: flex; gap: var(--sp-2); margin-inline-start: auto; }

/* The box and the controls that belong to it are one object with one border, so the row of icons
   underneath reads as part of the message being written rather than as a toolbar over the page. */
.rooms-composer {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  padding: 6px 8px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r4);
  background: var(--surface);
}

.rooms-composer:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-line); }

/* ORDER, NOT MARKUP. Everything below already sits above the textarea in the DOM, in the order
   React needs it; the rows on screen are a different order, and moving the markup to get them would
   make the file harder to read for the sake of one stylesheet. */
.rooms-composer > .rooms-picked { order: -3; flex-basis: 100%; }
.rooms-composer > .rooms-model,
.rooms-composer > .rooms-failed { order: -2; flex-basis: 100%; }

.rooms-composer textarea {
  order: -1;
  flex-basis: 100%;
  padding: 9px 6px;
  border: 0;
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-md);
  line-height: 1.5;
  resize: none;
  max-block-size: 160px;
  overflow-y: auto;
}

.rooms-composer textarea:focus { outline: none; }

/* The only control on this row that is not the person's own: everything to its inline-start is free
   and belongs to whoever is typing, and Send is the one that spends. */
.rooms-composer > .btn-primary { margin-inline-start: auto; }

.rooms-composer > .rooms-model {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  padding-block-end: var(--sp-2);
  border-block-end: 1px solid var(--border);
}

/* ---- what the next answer costs ------------------------------------------
   Not a warning that appears when something is expensive. A line that only shows up on the dear
   path teaches people that no line means free, and nothing in this room is free. */

.rooms-cost {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--muted);
}

.rooms-cost.is-dearer { color: var(--warning-fg); font-weight: 600; }

.rooms-shortcuts { display: flex; flex-wrap: wrap; gap: 6px; margin-inline-start: auto; }

.rooms-shortcuts button {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 8px;
  border: 1px solid var(--border);
  border-radius: 7px;
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-xs);
}

.rooms-shortcuts button:hover { background: var(--surface-3); border-color: var(--border-strong); }
.rooms-shortcuts code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 700; }

/* ---- the model picker, and what it costs (1.3, 18.24) --------------------
   There is no "Auto — best model for the task": it was the default, it sounded like a favour, and
   it hid the price. The default is the cheapest model NETFIVE has, with the real model's name
   underneath it and a $ beside every alternative. */

.rooms-model { position: relative; }

.rooms-model-pick {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  max-inline-size: 260px;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: var(--r2);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-sm);
}

.rooms-model-pick:hover { background: var(--surface-3); }
.rooms-model-pick:disabled { opacity: .5; cursor: default; }
.rooms-model-pick:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.rooms-model-name { font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rooms-model-hint { color: var(--muted); font-size: var(--fs-xs); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* WHOSE PLAN THIS LIST CAME FROM, on its own line above everything. A member in an owner-paid room
   sees fewer models than their own account has; without this line that reads as the product losing
   their models rather than the room spending somebody else's money. It floats, so it needs a ground
   of its own — grey italics on top of somebody else's message is unreadable. */
.rooms-model-whose {
  position: absolute;
  inset-block-end: calc(100% + 4px);
  inset-inline-start: 0;
  inline-size: max-content;
  max-inline-size: 60ch;
  padding: 1px 8px;
  border-radius: var(--r1);
  background: var(--surface);
  color: var(--muted);
  font-size: var(--fs-xs);
  font-style: normal;
}

/* ---- comparing (1.3) ----------------------------------------------------
   A switch and a list of models to tick, not a number. The old control was "answers: 1 / 2 / 3" and
   the server picked which models, so nobody choosing 3 knew what they had bought three of. */

.rooms-compare { display: inline-flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2); }

.rooms-compare-switch {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  block-size: 30px;
  padding-inline: 10px;
  border: 1px solid var(--border);
  border-radius: var(--r2);
  background: var(--surface);
  font-size: var(--fs-sm);
  font-weight: 700;
  cursor: pointer;
}

.rooms-compare-switch input { inline-size: 15px; block-size: 15px; margin: 0; accent-color: var(--accent); }
.rooms-compare-models { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.rooms-compare-hint { font-size: var(--fs-xs); color: var(--muted); font-style: normal; }

/* Fewer than two is not a comparison, and Send is refused there — so the hint has to say so in the
   colour the product uses for "this will not work yet". */
.rooms-compare-hint.is-short { color: var(--critical); font-weight: 600; }

.rooms-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 9px;
  border: 1px solid var(--border);
  border-radius: var(--rf);
  background: var(--surface-2);
  color: var(--muted);
  font-family: inherit;
  font-size: var(--fs-xs);
  white-space: nowrap;
}

/* The model is the one chip worth colouring: it is the answer to "what will this cost" and the only
   one a member can change per message. */
.rooms-chip.is-model { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent); }

.rooms-chip.is-pickable { background: var(--surface); color: var(--text); cursor: pointer; }
.rooms-chip.is-pickable:hover { border-color: var(--border-strong); }

/* Ticked means "this one is being paid for", so it is a solid accent surface with a name on it and
   takes the readable pair rather than a tint. */
.rooms-chip.is-pickable.is-on { background: var(--accent-fill); border-color: var(--accent); color: var(--on-accent); font-weight: 700; }

/* ---- the personal controls (18.26) --------------------------------------
   Attach, Link, Prompts and Dictate are YOURS wherever you are. None of them is gated on who pays,
   because none of them costs anything: they are ways of getting words and files INTO the box. */

.rooms-attach {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 34px;
  block-size: 34px;
  border-radius: var(--r2);
  color: var(--muted);
  cursor: pointer;
}

/* THE ICON NEEDS A SIZE OF ITS OWN HERE. `.gi` is only given one inside .icon-btn and .stepper —
   this is a <label>, because a file input cannot be opened by a button without script. An SVG with
   no size falls back to 300x150 and blows the bar apart, which looks like a broken build rather
   than a missing rule. */
.rooms-attach > .gi { inline-size: 17px; block-size: 17px; }

/* A <button> needs the border and background reset that a <label> never had. */
button.rooms-attach { border: 0; background: transparent; font-family: inherit; }
button.rooms-attach:disabled { opacity: .5; cursor: default; }
.rooms-attach:hover { background: var(--surface-3); color: var(--text); }
.rooms-attach:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }

/* THE LINK BOX REPLACES ITS OWN BUTTON rather than appearing beside it. A permanent address field
   in a chat composer is a field people type messages into — and next to a message box, two text
   inputs on one row is a guessing game. */
.rooms-linkbox {
  flex: 1 1 220px;
  min-inline-size: 0;
  padding: 7px 10px;
  border: 1px solid var(--accent);
  border-radius: var(--r2);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-sm);
}

.rooms-linkbox:focus { outline: none; box-shadow: 0 0 0 3px var(--accent-line); }

.rooms-prompts { position: relative; display: inline-flex; }

/* Chips for what is about to be sent. Full width above the box, so a long file name wraps rather
   than squeezing the textarea. */
.rooms-picked { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 var(--sp-2); padding: 0; list-style: none; }

.rooms-picked li {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-inline-size: 100%;
  padding: 3px 6px 3px 10px;
  border: 1px solid var(--border);
  border-radius: var(--rf);
  background: var(--surface-2);
  font-size: var(--fs-sm);
}

.rooms-picked-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rooms-picked-drop { padding: 0 2px; border: 0; background: none; color: var(--muted); font-size: 15px; line-height: 1; cursor: pointer; }
.rooms-picked-drop:hover { color: var(--critical); }

/* ---- the @ and / menu (18.20) -------------------------------------------

   FLOATED, NOT A FLEX CHILD. The composer is a row — a list dropped into it would sit beside the
   textarea and squash it. Absolute keeps the row exactly as it was, which matters because this list
   appears and disappears on almost every keystroke and a composer that resized each time would be
   unusable. ABOVE the box rather than below: below is where the on-screen keyboard is on a phone. */
.rooms-menu {
  position: absolute;
  inset-inline: 0;
  inset-block-end: calc(100% + 6px);
  z-index: 20;
  margin: 0;
  padding: 4px;
  list-style: none;
  max-block-size: 260px;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r2);
  box-shadow: var(--sh-3);
}

.rooms-menu-row {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  padding: 7px 10px;
  border-radius: var(--r1);
  cursor: pointer;
  font-size: var(--fs-sm);
}

/* Highlighted by the KEYBOARD, not by hover alone — the textarea keeps focus throughout, so the
   browser's own focus ring never appears on these rows and this is the only thing telling somebody
   which name Enter is about to take. */
.rooms-menu-row.is-active { background: var(--accent-soft); }

.rooms-menu-label { font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rooms-menu-hint { margin-inline-start: auto; color: var(--muted); font-size: var(--fs-xs); white-space: nowrap; }

/* OFFERED BUT REFUSED, and it says which. A command that is simply missing teaches somebody they
   misremembered it; one that is greyed with a reason teaches them what to change. */
.rooms-menu-row.is-off { cursor: default; opacity: .62; }
.rooms-menu-row.is-off .rooms-menu-label { text-decoration: line-through; text-decoration-thickness: 1px; }
.rooms-menu-row.is-off .rooms-menu-hint { font-style: italic; }

@media (prefers-reduced-motion: no-preference) {
  .rooms-menu { animation: rooms-menu-in 90ms ease-out; }
}

@keyframes rooms-menu-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}

/* The picker and the prompt list reuse the @ menu, anchored to their own button rather than to the
   composer — a list the width of the whole bar for four model names reads as a mistake. */
.rooms-menu.is-models {
  inset-inline: auto;
  inset-inline-start: 0;
  min-inline-size: 260px;
  max-inline-size: 360px;
}

.rooms-menu.is-prompts {
  inset-inline: auto;
  inset-inline-end: 0;
  min-inline-size: 240px;
  max-inline-size: 380px;
}

/* ---- the right panel (2.2) ----------------------------------------------
   Seven sections, in the order somebody catching up reads them: where are we, what is left, what
   have we settled, who is here, what the assistant does and costs, what was kept and what was
   shared. */

.rooms-panel {
  min-inline-size: 0;
  min-block-size: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-inline-start: 1px solid var(--border);
}

.rooms-panel-sec {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-5);
  border-block-end: 1px solid var(--border);
}

.rooms-panel-sec:last-child { border-block-end: 0; }

.rooms-panel-sec h3 {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: 800;
}

/* Whatever a heading carries besides its own word — a count, a due date, a way through to the board
   — belongs on the far side of the line, where the eye is not made to hunt for it. */
.rooms-panel-sec h3 > .rooms-panel-note,
.rooms-panel-sec h3 > .rooms-panel-link { margin-inline-start: auto; }

.rooms-panel-note { color: var(--muted); font-size: var(--fs-xs); font-weight: 500; }
.rooms-panel-empty { margin: 0; color: var(--faint); font-size: var(--fs-sm); }

.rooms-panel-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--accent);
  font-family: inherit;
  font-size: var(--fs-xs);
  font-weight: 700;
  cursor: pointer;
}

.rooms-panel-link:hover { text-decoration: underline; text-underline-offset: 2px; }

.rooms-panel-add {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding-block: 4px;
  color: var(--accent);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: 700;
  cursor: pointer;
}

.rooms-panel-add:hover { text-decoration: underline; text-underline-offset: 2px; }

.rooms-panel-goal { display: flex; align-items: flex-start; gap: var(--sp-2); margin: 0; font-size: var(--fs-md); font-weight: 700; }
.rooms-panel-progress-line { display: flex; justify-content: space-between; margin: 0; font-size: var(--fs-xs); color: var(--muted); font-variant-numeric: tabular-nums; }

/* A real <progress>, so a screen reader is told the number rather than shown a coloured rectangle
   it cannot describe — which is also why it has to be restyled in three vendor pieces. */
.rooms-progress {
  inline-size: 100%;
  block-size: 6px;
  border: 0;
  border-radius: var(--rf);
  appearance: none;
  -webkit-appearance: none;
  background: var(--surface-3);
  color: var(--ok);
  overflow: hidden;
}

.rooms-progress::-webkit-progress-bar { background: var(--surface-3); }
.rooms-progress::-webkit-progress-value { background: var(--ok); }
.rooms-progress::-moz-progress-bar { background: var(--ok); }

.rooms-tasks { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; }

.rooms-tasks > li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  column-gap: var(--sp-2);
  padding-block: 5px;
}

.rooms-tasks > li > label { grid-column: 1; display: flex; align-items: flex-start; gap: 10px; cursor: pointer; }
.rooms-tasks input[type="checkbox"] { flex: none; inline-size: 17px; block-size: 17px; margin: 1px 0 0; accent-color: var(--ok); }
.rooms-task-title { font-size: var(--fs-md); font-weight: 600; }

.rooms-task-sub {
  grid-column: 1;
  display: flex;
  flex-wrap: wrap;
  gap: 4px var(--sp-2);
  margin-inline-start: 27px;
  color: var(--muted);
  font-size: var(--fs-xs);
}

.rooms-tasks > li > .rooms-face { grid-column: 2; grid-row: 1 / -1; }
.rooms-tasks > li.is-done .rooms-task-title { color: var(--muted); text-decoration: line-through; }

.rooms-decisions { display: flex; flex-direction: column; gap: 6px; margin: 0; padding: 0; list-style: none; }

.rooms-decisions li {
  padding: var(--sp-2) 10px;
  border-radius: 9px;
  background: var(--ok-bg);
  font-size: var(--fs-sm);
  line-height: 1.4;
}

.rooms-decision-when { font-weight: 700; color: var(--ok-fg); }
.rooms-decision-when::after { content: " \00B7 "; color: var(--muted); font-weight: 400; }

.rooms-members { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; list-style: none; }
.rooms-members li { display: flex; align-items: center; gap: 10px; min-block-size: 36px; }
.rooms-member-main { flex: 1; min-inline-size: 0; display: flex; flex-direction: column; }
.rooms-member-name { font-size: var(--fs-md); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rooms-member-role { font-size: var(--fs-xs); color: var(--muted); }

/* Reading a room right now, which is a different fact from being in it — the green is the same one
   the ring on their face uses, so the two say the same thing in the same colour. */
.rooms-member-here { font-size: var(--fs-xs); font-weight: 600; color: var(--ok-fg); }

.rooms-panel-facts { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 6px var(--sp-3); margin: 0; font-size: var(--fs-sm); }
.rooms-panel-facts dt { color: var(--muted); }
.rooms-panel-facts dd { margin: 0; font-weight: 600; }

.rooms-pins { display: flex; flex-direction: column; gap: 6px; margin: 0; padding: 0; list-style: none; }

.rooms-pins button {
  inline-size: 100%;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--sp-2) 10px;
  border-radius: 9px;
  background: var(--surface-2);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-sm);
  text-align: start;
  cursor: pointer;
}

.rooms-pins button:hover { background: var(--surface-3); }
.rooms-pin-who { font-weight: 700; }

/* Two lines of it, because a pin is a way back to a message and not the message itself. */
.rooms-pin-text {
  color: var(--muted);
  line-height: 1.4;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.rooms-panel-files { display: flex; flex-direction: column; gap: var(--sp-2); margin: 0; padding: 0; list-style: none; }

/* The same link as in the thread, stacked instead of in a row: here the name and who shared it are
   two lines in a 320px column rather than one line across a message. */
.rooms-panel-files .rooms-file { flex-direction: column; align-items: flex-start; gap: 0; inline-size: 100%; }

/* ---- the task board (2.9) -----------------------------------------------
   The same room with the conversation swapped for its work. It replaces the thread rather than
   sitting beside it: a board squeezed into a sidebar cannot hold three columns, and a conversation
   squeezed beside a board stops being readable. */

.rooms-board { display: flex; flex-direction: column; }

/* In place of the rule chips, because a board is not where rules are changed. */
.rooms-board-switch { flex-basis: 100%; display: flex; gap: 6px; }

.rooms-board-switch button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  block-size: 32px;
  padding-inline: 12px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
}

.rooms-board-switch button:hover { background: var(--surface-3); }
.rooms-board-switch .is-on { background: var(--link-soft); border-color: var(--link-line); color: var(--link); font-weight: 700; }

.rooms-board-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5);
  border-block-end: 1px solid var(--border);
}

.rooms-board-filters { display: flex; gap: 6px; }

/* ---- A room's memory (NETFIVE-AI-Handoff §2.3, 21 September 2026) -------------------------------
 *
 * The badge colours are the handoff's memoryBadge tokens exactly. They are NOT the account's accent,
 * and deliberately: kind and status are read at a glance down a long list, and on the ruby accent a
 * red "Decision" would sit beside a red Send and read as a warning. Every badge carries its word too,
 * so none of this is colour alone (Part 5).
 */
.rooms-memory-bar { flex-wrap: wrap; row-gap: 8px; }
.rooms-memory-toggle { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; cursor: pointer; }
.rooms-memory-search {
  margin-inline-start: auto; inline-size: 260px; max-inline-size: 100%;
  padding: 7px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); font: inherit; font-size: 13px;
}

.rooms-memory-body { padding: 16px 20px 28px; overflow-y: auto; }
.rooms-memory-month + .rooms-memory-month { margin-block-start: 18px; }

/* Indented to the card column (§2.3), so the month reads as a heading over the cards, not the dates. */
.rooms-memory-monthlbl {
  margin: 0 0 8px; padding-inline-start: 76px;
  font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
}

.rooms-memory-list { display: grid; gap: 10px; margin: 0; padding: 0; list-style: none; }
.rooms-memory-row { display: grid; grid-template-columns: 64px minmax(0, 1fr); gap: 12px; align-items: start; }

/* Fixed and unwrapped, so every statement starts at the same x down the whole page. */
.rooms-memory-date { font-size: 12px; color: var(--muted); white-space: nowrap; padding-block-start: 12px; }

.rooms-memory-card {
  display: grid; gap: 6px; min-inline-size: 0;
  padding: 12px 14px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface);
}
.rooms-memory-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 11px; }
.rooms-memory-owner { margin-inline-start: auto; font-size: 12px; color: var(--muted); }
.rooms-memory-text { margin: 0; font-size: 14px; font-weight: 700; line-height: 1.4; overflow-wrap: anywhere; }
.rooms-memory-extra { margin: 0; font-size: 12px; color: var(--muted); }
.rooms-memory-replacement { color: var(--text); font-weight: 600; }

.rooms-memory-source {
  justify-self: start; padding: 2px 8px; border-radius: 999px; font-size: 12px;
  background: var(--surface-3); color: var(--text);
}
.rooms-memory-source:hover { background: var(--surface-4); }

.rooms-memory-kind, .rooms-memory-status { padding: 2px 8px; border-radius: 999px; font-weight: 700; }
.rooms-memory-kind.is-decision   { background: #eef2ff; color: #3730a3; }
.rooms-memory-kind.is-commitment { background: #ede9fe; color: #6d28d9; }
.rooms-memory-kind.is-reason     { background: #f1f2f5; color: #3d4450; }
.rooms-memory-status.is-confirmed  { background: #dcfce7; color: #166534; }
.rooms-memory-status.is-open       { background: #ede9fe; color: #6d28d9; }
.rooms-memory-status.is-superseded { background: #f1f2f5; color: #5b6472; }

/* A replaced item stays readable - the history is the point - but steps back (§2.3: 0.6). */
.rooms-memory-row.is-replaced { opacity: .6; }

.rooms-memory-empty { margin: 24px 0; color: var(--muted); font-size: 13px; }

/* Kept, per the handoff: "it is a product promise". Quiet, but always there. */
.rooms-memory-promise { margin: 24px 0 0; padding-inline-start: 76px; font-size: 12px; color: var(--muted); }

@media (max-width: 640px) {
  .rooms-memory-row { grid-template-columns: minmax(0, 1fr); gap: 4px; }
  .rooms-memory-date { padding-block-start: 0; }
  .rooms-memory-monthlbl, .rooms-memory-promise { padding-inline-start: 0; }
  .rooms-memory-search { margin-inline-start: 0; inline-size: 100%; }
}

:root[data-theme="dark"] .rooms-memory-kind.is-decision   { background: #1e1b4b; color: #c7d2fe; }
:root[data-theme="dark"] .rooms-memory-kind.is-commitment { background: #2e1065; color: #ddd6fe; }
:root[data-theme="dark"] .rooms-memory-kind.is-reason     { background: #23262d; color: #cbd5e1; }
:root[data-theme="dark"] .rooms-memory-status.is-confirmed  { background: #052e16; color: #86efac; }
:root[data-theme="dark"] .rooms-memory-status.is-open       { background: #2e1065; color: #ddd6fe; }
:root[data-theme="dark"] .rooms-memory-status.is-superseded { background: #23262d; color: #94a3b8; }

.rooms-board-filters button {
  block-size: 30px;
  padding-inline: 12px;
  border: 1px solid var(--border);
  border-radius: var(--rf);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: 700;
}

.rooms-board-filters button:hover { border-color: var(--border-strong); }

/* INK, NOT THE ACCENT, and that is on purpose: the Conversation / Tasks switch beside it is already
   accent-tinted, and two accent-coloured groups on one bar makes them look like one control. The
   chosen filter inverts the page's own ink and ground, which reads in both themes. */
.rooms-board-filters .is-on { background: var(--text); border-color: var(--text); color: var(--bg); }

.rooms-board-add { flex: 1 1 320px; display: flex; gap: var(--sp-2); }

.rooms-board-add input {
  flex: 1;
  min-inline-size: 0;
  padding: 7px 12px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-sm);
}

.rooms-board-add input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-line); }

/* NOTHING IT PROPOSES BECOMES A TASK UNTIL SOMEBODY PRESSES ADD, so a suggestion has to look unlike
   a card: dashed, and tagged with where it came from. A model writing a to-do list into a room
   unasked is how a task list fills with things nobody agreed to do. */
.rooms-suggested { display: flex; flex-direction: column; gap: var(--sp-2); margin: 0; padding: var(--sp-3) var(--sp-5) 0; list-style: none; }

.rooms-suggested li {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  padding: var(--sp-3);
  border: 1px dashed var(--accent-line);
  border-radius: var(--r3);
  background: var(--surface);
}

.rooms-suggested-tag {
  padding: 1px 7px;
  border-radius: var(--rf);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: var(--fs-micro);
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
}

.rooms-suggested-text { flex: 1 1 200px; min-inline-size: 0; font-size: var(--fs-sm); }

.rooms-columns {
  flex: 1;
  min-block-size: 0;
  overflow-y: auto;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: start;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
}

.rooms-column {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-3);
  border-radius: var(--r4);
  background: var(--surface-2);
}

.rooms-column h2 { display: flex; justify-content: space-between; align-items: baseline; padding: 2px 4px; font-size: var(--fs-md); font-weight: 800; }
.rooms-column > ul { display: flex; flex-direction: column; gap: var(--sp-2); margin: 0; padding: 0; list-style: none; }

.rooms-column li {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r3);
  background: var(--surface);
}

.rooms-card-title { margin: 0; font-size: var(--fs-md); font-weight: 700; line-height: 1.35; }
.rooms-column li.is-done .rooms-card-title { color: var(--muted); text-decoration: line-through; }
.rooms-card-sub { display: flex; flex-wrap: wrap; align-items: center; gap: 4px var(--sp-2); margin: 0; font-size: var(--fs-xs); color: var(--muted); }
.rooms-due { font-weight: 600; }

/* Due today, tomorrow or already past. Amber, not red: a date that has come round is a thing to do
   today, not a failure. */
.rooms-due.is-soon { color: var(--warning-fg); }

/* ARROWS, NOT DRAGGING. Dragging is the expected gesture and it is also the one a keyboard cannot
   make, a screen reader cannot describe and a touchscreen gets wrong half the time.

   The face is last in the markup and first on the card: the auto margin after it is what pushes the
   two arrows to the far edge, and a card with nobody on it still has them there. */
.rooms-card-move { display: flex; align-items: center; justify-content: flex-end; gap: 6px; }
.rooms-card-move > .rooms-face { order: -1; margin-inline-end: auto; }

.rooms-card-move > button {
  display: inline-grid;
  place-items: center;
  inline-size: 28px;
  block-size: 28px;
  border: 1px solid var(--border);
  border-radius: 7px;
  background: var(--surface);
  color: var(--muted);
  font-size: var(--fs-sm);
  line-height: 1;
}

.rooms-card-move > button:hover { background: var(--surface-3); color: var(--text); border-color: var(--border-strong); }

/* ---- the room switcher palette ------------------------------------------
   Centred over everything, keyboard-first, and it closes on Escape. The list it replaced was 35
   rows of equal weight; this is one row at a time and the one you want is usually the first. */

.rooms-palette-backdrop {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 12vh 16px 16px;
  background: rgb(0 0 0 / .45);
}

.rooms-palette {
  inline-size: min(100%, 35rem);
  max-block-size: 60vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r4);
  box-shadow: var(--sh-3);
}

.rooms-palette input {
  padding: 13px 15px;
  border: 0;
  border-block-end: 1px solid var(--border);
  border-radius: 0;
  background: transparent;
  color: var(--text);
  font-size: var(--fs-lg);
}

.rooms-palette input:focus { outline: none; }
.rooms-palette ul { margin: 0; padding: 6px; list-style: none; overflow-y: auto; }

.rooms-palette li {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 8px 10px;
  border-radius: 9px;
  color: var(--muted);
  font-size: var(--fs-sm);
  cursor: pointer;
}

.rooms-palette li.is-active { background: var(--surface-3); color: var(--text); }
.rooms-palette li .name { flex: 1; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rooms-palette li.is-current .name { color: var(--text); font-weight: 700; }
.rooms-palette-empty { padding: 18px 15px; color: var(--muted); font-size: var(--fs-sm); }

/* Making a room is the row that is not a result, so it is the one row in the list carrying the
   accent — it is an action sitting among matches, and reading as another match is how somebody
   opens a room called "+ New room". */
.rooms-palette li.is-new .name { color: var(--accent); font-weight: 700; }

/* THREE LISTS BEHIND ONE BOX (2.3): rooms, people and what was said. Tabs rather than three
   separate shortcuts, because the question is always "find the thing" and only afterwards "which
   kind of thing was it". */
.rooms-palette-tabs { display: flex; gap: 6px; padding: 10px 14px 4px; }

.rooms-palette-tabs button {
  block-size: 28px;
  padding-inline: 12px;
  border: 1px solid var(--border);
  border-radius: var(--rf);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: 700;
}

.rooms-palette-tabs button:hover { border-color: var(--border-strong); }
.rooms-palette-tabs .is-on { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent); }

.rooms-palette-note { margin-inline-start: auto; color: var(--faint); font-size: var(--fs-xs); white-space: nowrap; }

/* The keys, named, along the bottom. A switcher whose shortcuts have to be guessed is a switcher
   people use with the mouse, which is the one thing it is not for. */
.rooms-palette-keys {
  margin: 0;
  padding: var(--sp-2) 15px 10px;
  border-block-start: 1px solid var(--border);
  color: var(--faint);
  font-size: var(--fs-xs);
}

/* ---- the dialogs --------------------------------------------------------- */

.rooms-modal { align-items: center; padding-block: var(--sp-4); }

/* .palette-x is positioned against its panel, so the panel has to be one. */
.rooms-dialog {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-5);
  max-block-size: 88vh;
  overflow-y: auto;
}

.rooms-dialog h2 { margin: 0; padding-inline-end: 26px; font-size: var(--fs-lg); }

.rooms-field { display: flex; flex-direction: column; gap: 5px; }
.rooms-field > span { font-size: var(--fs-sm); font-weight: 600; }

.rooms-field input,
.rooms-field select {
  padding: 8px 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r1);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-md);
}

.rooms-field input:focus,
.rooms-field select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-line); }

/* A SETTING THAT IS A CHOICE, NOT A VALUE (18.24). Every other field here is a label above a
   control; this one is a box beside its own explanation, because the explanation is the part that
   matters — "members may use my models" is a sentence about somebody else's spending, and the
   consequence has to be readable before the box is ticked, not after. */
.rooms-check { flex-direction: row; align-items: flex-start; gap: var(--sp-3); }

.rooms-check > input {
  /* Aligned to the first line of the label rather than centred against the whole block, which on a
     two-line explanation floats it into the middle of the sentence. */
  flex: none;
  margin-block-start: 2px;
  inline-size: 16px;
  block-size: 16px;
  accent-color: var(--accent);
}

.rooms-check > span { display: flex; flex-direction: column; gap: 2px; font-weight: 400; }
.rooms-check > span > b { font-size: var(--fs-sm); font-weight: 600; }

.rooms-hint { font-size: var(--fs-xs); color: var(--muted); font-style: normal; line-height: 1.45; }
/* "said in red and then forgotten about" was the intention and was never true: nothing cleared it,
   so it sat above the composer for the rest of the visit. It carries its own dismiss now, and the
   flex is what keeps that cross on the same line as the sentence rather than under it. */
.rooms-failed {
  margin: 0;
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  color: var(--critical);
  font-size: var(--fs-sm);
  font-weight: 550;
}
.rooms-failed > span { min-inline-size: 0; }
.rooms-failed-x {
  margin-inline-start: auto;
  flex-shrink: 0;
  padding: 0 4px;
  border: 0;
  background: none;
  color: inherit;
  font-size: var(--fs-md);
  line-height: 1;
  cursor: pointer;
}
.rooms-failed-x:hover { opacity: .7; }

/* A REFUSAL WITH SOMETHING TO DO ABOUT IT (bug B6).

   Apart from .rooms-failed because it is a different kind of message: that one is "something went
   wrong", said in red and then forgotten about. This is the server's own sentence — this room is
   full, this plan does not include rooms — and the reader can act on it, so it is a block with the
   way out inside it rather than a line of red text above a form that still looks usable. */
.rooms-refused {
  display: grid;
  gap: var(--sp-2);
  justify-items: start;
  margin: 0;
  padding: var(--sp-3);
  border: 1px solid color-mix(in srgb, var(--critical) 35%, transparent);
  border-radius: 8px;
  background: var(--critical-bg);
}

.rooms-refused p { margin: 0; color: var(--critical); font-size: var(--fs-sm); font-weight: 550; line-height: 1.45; }

/* THE DIALOG'S OWN BUTTONS, SET APART FROM THE LIST ABOVE THEM. With only --sp-1 of space this sat
   the same distance below "Leave" as the member rows sat from each other — and because every one of
   them is right-aligned, "OWNER", "Leave" and "Done" read as one column of three related things
   rather than a list and then a footer. */
.rooms-dialog-buttons {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-2);
  margin-block-start: var(--sp-4);
  padding-block-start: var(--sp-3);
  border-block-start: 1px solid var(--border);
}

/* ---- who is in a room, on the settings panel ----------------------------- */

.rooms-people-list { border-block-start: 1px solid var(--border); padding-block-start: var(--sp-3); }
.rooms-people-list h3 { margin: 0 0 var(--sp-2); font-size: var(--fs-sm); }
.rooms-people-list ul { display: flex; flex-direction: column; gap: 6px; margin: 0; padding: 0; list-style: none; }
.rooms-people-list li { display: flex; align-items: center; gap: var(--sp-2); }
.rooms-person-name { flex: 1; min-inline-size: 0; font-size: var(--fs-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* A word, not a colour. "Owner" is a fact about permissions and a reader who cannot distinguish a
   tint would otherwise have no way to know who can change the room. */
.rooms-person-role {
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
}

.rooms-note { margin: 0; padding: var(--sp-4); color: var(--muted); font-size: var(--fs-sm); line-height: 1.55; }

/* ---- making a room, inviting to it, and handing it over (2.4, 2.5, 2.10) ----

   These three dialogs arrived after the screen itself and they are wider than the product's
   ordinary .palette-box, which is why is-wide exists: naming a room, choosing its colour, setting a
   goal and a date and adding four people is not a column of five fields, and squeezing it into one
   makes a form nobody reads to the bottom of. */

.rooms-dialog.is-wide { inline-size: min(760px, 100%); }

/* The tile as it will look, beside the name as it is typed. Both are the room's identity in the
   rail, and choosing them blind is how somebody ends up with six rooms the same shade. */
.rooms-name-row { display: flex; align-items: center; gap: var(--sp-3); }
.rooms-name-row > input { flex: 1; min-inline-size: 0; }

.rooms-swatches { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-block-start: 6px; }

.rooms-swatch {
  inline-size: 24px;
  block-size: 24px;
  padding: 0;
  border: 2px solid transparent;
  border-radius: 7px;
  background: var(--room-1);
  cursor: pointer;
}

/* The chosen swatch is marked by a ring OUTSIDE it, in the page's own ink, rather than by a border
   inside it — a border eats two pixels of the colour the person is judging, which on a 24px square
   is most of the reason to look at it. */
.rooms-swatch.is-on { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--text); }
.rooms-swatch:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.rooms-tile.room-1, .rooms-swatch.room-1 { background: var(--room-1); }
.rooms-tile.room-2, .rooms-swatch.room-2 { background: var(--room-2); }
.rooms-tile.room-3, .rooms-swatch.room-3 { background: var(--room-3); }
.rooms-tile.room-4, .rooms-swatch.room-4 { background: var(--room-4); }
.rooms-tile.room-5, .rooms-swatch.room-5 { background: var(--room-5); }
.rooms-tile.room-6, .rooms-swatch.room-6 { background: var(--room-6); }

/* Two short fields that belong together — a goal and the date it is wanted by — read as one
   question on one line and as two unrelated ones stacked. They fold to a column where there is not
   room for both, because half of a date field is not a date field. */
.rooms-two-up { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); }

@media (max-width: 620px) {
  .rooms-two-up { grid-template-columns: minmax(0, 1fr); }
}

/* A field's label with something on the far end of it: how many seats are left, or the button that
   opens the rules. */
.rooms-field-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.rooms-field-head > span:first-child { font-size: var(--fs-sm); font-weight: 600; }

/* HOW MANY SEATS ARE LEFT, SAID BEFORE SOMEBODY TYPES A NAME. Grey while there is room, amber when
   there is nearly none and red at the limit — three states because the last two want different
   things done about them: finish what you are doing, and buy a bigger plan. */
.rooms-seats { font-size: var(--fs-xs); font-style: normal; color: var(--muted); }
.rooms-seats.is-near { color: var(--warning-fg); font-weight: 600; }
.rooms-seats.is-full { color: var(--critical); font-weight: 600; }

.rooms-seats-bar {
  inline-size: 100%;
  block-size: 6px;
  border: 0;
  border-radius: var(--rf);
  appearance: none;
  -webkit-appearance: none;
  background: var(--surface-3);
  color: var(--accent);
  overflow: hidden;
}

.rooms-seats-bar::-webkit-progress-bar { background: var(--surface-3); }
.rooms-seats-bar::-webkit-progress-value { background: var(--accent); }
.rooms-seats-bar::-moz-progress-bar { background: var(--accent); }

/* ---- picking people by name ---------------------------------------------
   Whoever has been chosen is a chip inside the box, and the box is what is typed into: one control
   rather than a field and a list beside it, so adding the fourth person is the same gesture as
   adding the first. */

.rooms-people-pick { display: flex; flex-direction: column; gap: 6px; }

.rooms-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 0;
  padding: 6px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r1);
  background: var(--surface);
  list-style: none;
}

.rooms-chips:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-line); }

.rooms-chip.is-person { padding: 2px 4px 2px 2px; background: var(--surface-2); color: var(--text); font-size: var(--fs-sm); }
.rooms-chip.is-person button { padding: 0 4px; color: var(--muted); font-size: 14px; line-height: 1; }
.rooms-chip.is-person button:hover { color: var(--critical); }

.rooms-chips-input { flex: 1 1 160px; min-inline-size: 0; display: flex; }
.rooms-chips-input input { flex: 1; min-inline-size: 0; border: 0; background: transparent; color: var(--text); font-family: inherit; font-size: var(--fs-md); padding: 4px 6px; }
.rooms-chips-input input:focus { outline: none; }

.rooms-found {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 4px;
  border: 1px solid var(--border);
  border-radius: var(--r1);
  background: var(--surface-2);
  max-block-size: 220px;
  overflow-y: auto;
  list-style: none;
}

.rooms-found li { display: flex; align-items: center; gap: var(--sp-2); padding: 4px 6px; border-radius: var(--r1); }
.rooms-found li:hover { background: var(--surface-3); }
.rooms-found-name { flex: 1; min-inline-size: 0; font-size: var(--fs-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- the rules, while a room is being made ------------------------------
   A sentence first and the radio buttons only if somebody asks for them. Most rooms want the
   defaults, and four fieldsets on the create form is how a two-field job turns into a page people
   abandon. */

.rooms-rules-box { display: flex; flex-direction: column; gap: 6px; padding: var(--sp-3); border: 1px solid var(--border); border-radius: var(--r2); background: var(--surface-2); }
.rooms-rules-sentence { margin: 0; font-size: var(--fs-sm); color: var(--muted); line-height: 1.5; }

.rooms-rules-choices { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--sp-3); margin-block-start: 4px; }
.rooms-rules-choices fieldset { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; border: 0; }
.rooms-rules-choices legend { padding: 0 0 4px; font-size: var(--fs-xs); font-weight: 700; color: var(--muted); }
.rooms-rules-choices label { display: flex; align-items: center; gap: 6px; font-size: var(--fs-sm); cursor: pointer; }
.rooms-rules-choices input[type="radio"] { flex: none; inline-size: 15px; block-size: 15px; margin: 0; accent-color: var(--accent); }

/* Nested, so the spending limit inside "who pays" keeps the stacked label-over-field shape the rest
   of the form uses rather than inheriting the radio row's. */
.rooms-rules-choices .rooms-field { margin-inline-start: 21px; }

/* ---- handing the room over (2.10) --------------------------------------- */

.rooms-choose { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; list-style: none; }

.rooms-choose label {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 7px 10px;
  border: 1px solid var(--border);
  border-radius: var(--r1);
  font-size: var(--fs-sm);
  cursor: pointer;
}

.rooms-choose label:hover { background: var(--surface-3); }
.rooms-choose label:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
.rooms-choose input[type="radio"] { flex: none; inline-size: 15px; block-size: 15px; margin: 0; accent-color: var(--accent); }

/* Amber, not red: handing a room over is not a mistake, and what this says is a consequence of it —
   the answers this room pays for stop being paid for by you. */
.rooms-warnbox {
  margin: 0;
  padding: var(--sp-3);
  border: 1px solid var(--warning);
  border-radius: var(--r2);
  background: var(--warning-bg);
  color: var(--warning-fg);
  font-size: var(--fs-sm);
  line-height: 1.45;
}

/* The one irreversible thing in Room settings, set apart from the fields above it the same way the
   dialog's buttons are — so nobody reaches it by running out of form. */
.rooms-leave { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2); padding-block-start: var(--sp-3); border-block-start: 1px solid var(--border); }
.rooms-leave h3 { margin: 0; font-size: var(--fs-sm); }

/* WHAT IS HIDDEN, SAID IN THE ROOM RATHER THAN DISCOVERED (2.6). A member who joined a month-old
   room saw four messages and had no way to tell whether the room was new or broken. It sits in the
   thread where the older messages would have been, which is the only place it answers the question
   somebody is actually asking. */
.rooms-hidden-note {
  margin: var(--sp-2) var(--sp-5);
  padding: var(--sp-3) 14px;
  border: 1px solid var(--border);
  border-radius: var(--r3);
  background: var(--surface-2);
}

.rooms-hidden-note h2 { margin: 0 0 3px; font-size: var(--fs-md); font-weight: 700; }
.rooms-hidden-note p { margin: 0; font-size: var(--fs-sm); color: var(--muted); line-height: 1.5; }

/* ---- narrow --------------------------------------------------------------

   THE SAME SHAPE CHAT USES, one step at a time. Chat collapses its three columns to one and lets
   Details lie over the conversation; this does the same, with the difference that the panel goes
   first and the rail second — a room is readable without its details and is not readable at 300px.

   MEDIA, NOT CONTAINER, and that IS the difference from .ch-app, so it is worth saying why. Rooms
   opens as a whole page and, in a desk window, inside an iframe — so the viewport already is the
   window. A container query would mean putting `container-type: inline-size` on .rooms-mount, and
   inline-size containment makes that box the containing block for fixed-position descendants:
   every dialog in this screen is .palette, which is `position: fixed; inset: 0`, and all four of
   them would silently stop covering the page. */

@media (max-width: 1100px) {
  /* The details panel stops being a column and lies over the room instead. It is still openable and
     still closes from the same button, which is the part that matters: a control that stays on
     screen and quietly stops working is worse than one that is gone. */
  .rooms:has(> .rooms-panel) { grid-template-columns: 264px minmax(0, 1fr); }
  .rooms-thread:has(> .rooms-panel) { grid-template-columns: minmax(0, 1fr); }

  /* Under the panel and over everything else, so a press anywhere but the panel closes it (Syed's
     QA, 24 September 2026). Without it the panel covered the composer and every press on Send
     landed on the panel and did nothing at all - no movement, no message, no reason. Transparent
     rather than dimmed: this is a drawer in a window, not a modal over a page, and dimming the room
     would say the room had stopped working when only the panel is in front of it. */
  .rooms-panel-scrim {
    position: absolute;
    inset: 0;
    z-index: 29;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: default;
  }

  .rooms > .rooms-panel,
  .rooms-thread > .rooms-panel {
    position: absolute;

    /* Back to auto placement: a grid line that no longer exists collapses an absolutely positioned
       child's containing block to the padding edge, and the panel would be drawn 0px wide. */
    grid-column: auto;
    grid-row: auto;

    inset-block: 0;
    inset-inline-end: 0;
    z-index: 30;
    inline-size: min(320px, 92%);
    box-shadow: -14px 0 32px -14px rgb(0 0 0 / .4);
  }
}

@media (max-width: 900px) {
  /* One column, two screens. The rail IS the screen until a room is picked; picking one replaces
     it, and the Back button in the room header is the way out — the same shape as every messaging
     app on a phone. */
  .rooms,
  .rooms:has(> .rooms-panel) { grid-template-columns: minmax(0, 1fr); }

  .rooms-rail { border-inline-end: 0; }
  .rooms-thread { display: none; }

  .rooms.viewing .rooms-rail { display: none; }
  .rooms.viewing .rooms-thread { display: grid; }
  .rooms.viewing .rooms-board { display: flex; }

  .rooms-back { display: inline-flex; }

  /* The avatar row is the first thing to go: it is a nicety, and the room's name and the composer
     are not. Everybody in it is in the panel's member list anyway. */
  .rooms-people { display: none; }

  .rooms-head,
  .rooms-rules,
  .rooms-board-bar,
  .rooms-foot,
  .rooms-columns { padding-inline: var(--sp-3); }

  .rooms-msg,
  .rooms-notice,
  .rooms-dayline { padding-inline: var(--sp-3); }

  .rooms-hidden-note { margin-inline: var(--sp-3); }

  .rooms-msg.mine,
  .rooms-msg.run-on:not(.mine) { padding-inline-start: calc(var(--sp-3) + 34px + var(--sp-3)); }

  .rooms-decision-card { margin-inline: var(--sp-3) var(--sp-3); }
  .rooms-acts { inset-inline-end: var(--sp-3); }
  .rooms-typing { inset-inline-start: var(--sp-3); }

  /* Three columns of 100px are not three columns. */
  .rooms-columns { grid-template-columns: minmax(0, 1fr); }
}

/* =============================================================================
   THE DESKTOP (step 1 of the portal plan)

   ATRIUM OS'S COLOURS (Syed, 15 September): teal-grey grounds, cool surfaces and
   ink, light and dark, taken from Atrium's atrium.css. They are scoped to .dk, so
   the desktop wears them while the product's own screens opened in its windows
   keep NETFIVE's look until each is redesigned.

   THE ACCENT IS NOT ATRIUM'S MAGENTA. It stays the accent each person picked
   (D-51), so the accent picker still works here (Syed: accent colour only).

   Atrium's web fonts are not loaded, because this product loads nothing from
   another site; the desktop uses NETFIVE's own font stack.

   The sizes are the handover mockup's: a 44px top bar, a 98px dock, 34px title
   bars and a 40px taskbar.
   ============================================================================ */

/* No chrome: the desktop itself, and any page opened inside one of its windows.
   _Layout sets data-chrome from ViewData["Shell"] and from Sec-Fetch-Dest. */
[data-chrome="none"] .appnav,
[data-chrome="none"] .topbar { display: none; }
[data-chrome="none"] .app { grid-template-columns: minmax(0, 1fr); }

.dk-mount { flex: 1; min-block-size: 0; display: flex; flex-direction: column; }

/* THE SHARED STONE PALETTE AND TYPE (Syed, 19 September: netfive-layout-fix's tokens.css and the shell previews —
   "The white and black theme both should be implemented as it was given in the new built"). Stone, not Atrium's
   mint: his note traced the green in the header rows, status bars and drop areas of Notes, Tasks, Files and the desk
   to one shared token, and this is that token. Notes, Tasks, Files and the Calculator all draw from these names, so
   they change with it. Every value is his, except --dk-line-strong: the edge of a field stays 3:1 against what is
   behind it (Syed, 15 September), where his hairline would be 1.3:1.
   Instrument Sans matches the NETFIVE wordmark; IBM Plex Mono is for numerals and keys. Both are served from this
   site (wwwroot/fonts, each with its OFL licence) — the product still loads nothing from another. */
/* Plus Jakarta Sans, which the home handoff asks for (v3, 20 September 2026), in the same two subsets and served
   from this site with its OFL licence beside it. Instrument Sans stays for everything outside the desk. */
@font-face {
  font-family: 'Plus Jakarta Sans'; font-style: normal; font-weight: 400 800; font-display: swap;
  src: url('../fonts/plus-jakarta-sans-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Plus Jakarta Sans'; font-style: normal; font-weight: 400 800; font-display: swap;
  src: url('../fonts/plus-jakarta-sans-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Instrument Sans'; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url('../fonts/instrument-sans-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Instrument Sans'; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url('../fonts/instrument-sans-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('../fonts/ibm-plex-mono-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('../fonts/ibm-plex-mono-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('../fonts/ibm-plex-mono-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('../fonts/ibm-plex-mono-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Source Serif 4 is for long-form reading only — the body of a note — so only a note's page ever downloads it. */
@font-face {
  font-family: 'Source Serif 4'; font-style: normal; font-weight: 400 600; font-display: swap;
  src: url('../fonts/source-serif-4-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Source Serif 4'; font-style: normal; font-weight: 400 600; font-display: swap;
  src: url('../fonts/source-serif-4-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

.dk {
  --dk-ground: #F5F5F4;
  /* Every scroller on the desk draws from HERE, and this is the only place it can be done.
     :root declares `scrollbar-color: var(--surface-4) transparent`; var() is substituted AT :root,
     so what inherits down is the resolved SITE colour and no --surface-4 remap inside the desk can
     reach it. The same fault was found three times on 22 September 2026 - the left rail, the Notes
     list and preview, and the Sheets list - each time as a thumb the same colour as the panel
     behind it. Fixing it per scroller found it again; one declaration here covers every scroller
     the desk has, including the ones nobody has looked at yet.

     --dk-ink-3 measured against the surfaces these bars actually sit on: 6.93:1 and 6.48:1 in dark,
     4.59:1 and 4.80:1 in light, all clear of the 3:1 a control needs to be seen.

     A scroller that wants its own ::-webkit-scrollbar rules must set `scrollbar-color: auto` to
     cancel this, as .dk-dock-apps does - Chromium ignores those pseudo-elements when a standard
     scrollbar property holds a real value, but not when it holds its initial one. */
  scrollbar-color: var(--dk-ink-3) transparent;
  --dk-ground-2: #F5F5F4;
  --dk-ground-3: #F0EFED;
  --dk-surface: #FFFFFF;
  --dk-surface-2: #FAFAF9;
  --dk-surface-3: #F0EFED;
  /* --dk-line is his hairline around cards, windows, the bar and the rails; --dk-line-soft a divider inside one;
     --dk-line-strong his firmer border, for a tile or a dashed drop area. --dk-field is the edge of a box somebody
     types into, kept at 3:1 against what is behind it, which is how somebody sees where to type. */
  --dk-line: #E7E5E4;
  --dk-line-strong: #D6D3D1;
  --dk-line-soft: #F0EFED;
  --dk-field: #8A827C;
  --dk-ink: #1C1917;
  --dk-ink-2: #57534E;
  --dk-ink-3: #78716C;
  --dk-good: #15803D;
  --dk-warn: #B45309;
  --dk-crit: #B91C1C;
  /* The two halves of the credit bar, named here rather than written into the rule, so the palette
     holds every colour the desk draws and this pair can be changed in one place (Syed, 22 September
     2026: the bar's colours were not in the main UI). --dk-usage-economy is --dk-good's own value.

     NOT CHANGED TO A PALETTE-NATIVE PAIR LIKE GREEN AND AMBER, which would look more at home and
     read worse: green against amber is the classic confusion for deuteranopia, and green against
     indigo is not. Syed checked the priority hues for deuteranopia and protanopia himself, so the
     readability of a pair is a property this file is already keeping. Different hues are his to
     choose; these two lines are where they go. */
  --dk-usage-economy: #15803D;
  --dk-usage-advanced: #3730A3;
  --dk-shadow-1: 0 1px 2px rgba(28, 25, 23, .12);
  --dk-shadow-2: 0 14px 36px rgba(28, 25, 23, .16), 0 2px 6px rgba(28, 25, 23, .08);
  --dk-bar: #FFFFFF;
  --dk-rail: #FAFAF9;
  --dk-r: 10px;
  --dk-r-s: 7px;
  /* The desk's own font since the home redesign (v3, 20 September 2026); the fallbacks are the handoff's. */
  --font: 'Plus Jakarta Sans', ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, "Cascadia Mono", Consolas, monospace;

  position: relative;
  flex: 1;
  min-block-size: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--dk-ground);
  color: var(--dk-ink);
  font-family: var(--font);
  font-size: var(--fs-md);
  color-scheme: light;
}

/* THE DARK STONE (Syed, 19 September: tokens.css's dark block and the shell's dark preview). It replaces the
   near-black of 17 September: warm stone, #141210 under everything, #1C1917 for the bar and the rails, #231F1D for a
   card one step up from them, #332F2C its hairline. The ink is #FAFAF9, dropping to #D6D3D1 for a second line and
   #A8A29E for a hint; a field's edge keeps 3:1. */
[data-theme="dark"] .dk {
  --dk-ground: #141210;
  --dk-ground-2: #141210;
  --dk-ground-3: #1C1917;
  --dk-surface: #231F1D;
  --dk-surface-2: #1C1917;
  --dk-surface-3: #332F2C;
  --dk-line: #332F2C;
  --dk-line-strong: #44403C;
  --dk-line-soft: #2A2624;
  --dk-field: #78716C;
  --dk-ink: #FAFAF9;
  --dk-ink-2: #D6D3D1;
  --dk-ink-3: #A8A29E;
  --dk-good: #4ADE80;
  --dk-warn: #FBBF24;
  --dk-crit: #FCA5A5;
  /* Lifted for a dark card rather than swapped for a different pair: the meaning is carried by the
     colour, so it has to survive the theme. */
  --dk-usage-economy: #22C55E;
  --dk-usage-advanced: #818CF8;
  --dk-shadow-1: 0 1px 2px rgba(0, 0, 0, .4);
  --dk-shadow-2: 0 14px 36px rgba(0, 0, 0, .5), 0 2px 6px rgba(0, 0, 0, .4);
  --dk-bar: #1C1917;
  --dk-rail: #1C1917;
  color-scheme: dark;
}

/* Zero specificity, so every button class below that sets its own colour wins. As a plain `.dk button`
   rule it outranked `.dk-start` and `.dk-btn-primary`, and put ink-coloured text on the accent. */
:where(.dk button) { font: inherit; color: inherit; }
.dk-busy { user-select: none; }
/* A frame under the pointer would swallow the drag. */
.dk-busy iframe { pointer-events: none; }

.dk-center { align-items: center; justify-content: center; }
.dk-spacer { flex: 1; }
.dk-grow { flex: 1; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dk-block { display: flex; inline-size: 100%; justify-content: center; }
.dk-hint { color: var(--dk-ink-3); font-size: var(--fs-xs); }
.dk-empty { color: var(--dk-ink-3); font-size: var(--fs-sm); text-align: center; padding: var(--sp-5) var(--sp-3); margin: 0; }
.dk-error { color: var(--dk-crit); font-size: var(--fs-sm); margin: 0; }
.dk-visually-hidden { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.dk-icon { inline-size: 16px; block-size: 16px; flex-shrink: 0; }
.dk-kbd {
  font-family: var(--mono); font-size: var(--fs-micro); color: var(--dk-ink-3);
  border: 1px solid var(--dk-line); border-block-end-width: 2px; border-radius: 4px; padding: 0 4px;
}

/* ---- controls ------------------------------------------------------------ */

.dk-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 6px 12px; border-radius: var(--dk-r-s); border: 1px solid var(--dk-line);
  background: var(--dk-surface-2); color: var(--dk-ink); font-size: var(--fs-sm); font-weight: 600;
  cursor: pointer; text-decoration: none; white-space: nowrap;
}
.dk-btn:hover { background: var(--dk-surface-3); }
.dk-btn:disabled { opacity: .5; cursor: default; }
.dk-btn-primary { background: var(--accent-fill); border-color: var(--accent); color: var(--on-accent); }
.dk-btn-primary:hover { background: var(--accent-fill-hover); }
.dk-btn-quiet { background: transparent; border-color: transparent; color: var(--dk-ink-2); }
.dk-btn-quiet:hover { background: color-mix(in srgb, var(--dk-ink) 7%, transparent); }
.dk-btn-quiet[aria-pressed="true"] { color: var(--accent); }
.dk-btn-danger { color: var(--dk-crit); }

.dk-link {
  background: none; border: 0; padding: 0; color: var(--link); font-size: var(--fs-sm);
  cursor: pointer; text-align: start;
}
.dk-link:hover { color: var(--link-hover); text-decoration: underline; }

.dk-input {
  padding: 7px 10px; border-radius: var(--dk-r-s); border: 1px solid var(--dk-field);
  background: var(--dk-surface); color: var(--dk-ink); font: inherit; font-size: var(--fs-md); min-inline-size: 0;
}
.dk-input:focus-visible,
.dk-btn:focus-visible,
.dk button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.dk-form { display: grid; gap: var(--sp-2); }
.dk-form-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.dk-panel { background: var(--dk-surface-2); border: 1px solid var(--dk-line-soft); border-radius: var(--dk-r); padding: var(--sp-3); }
.dk-fail { display: grid; gap: var(--sp-3); justify-items: center; }
.dk-note-box {
  background: color-mix(in srgb, var(--accent) 8%, var(--dk-surface)); border: 1px solid color-mix(in srgb, var(--accent) 25%, var(--dk-line));
  border-radius: var(--dk-r); padding: 10px 12px; font-size: var(--fs-sm); color: var(--dk-ink-2);
}

.dk-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.dk-list-item {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px;
  background: var(--dk-surface-2); border: 1px solid var(--dk-line-soft); border-radius: 8px;
}

/* ---- glyphs: an app's initials on its colour ------------------------------- */

.dk-glyph {
  display: inline-grid; place-items: center; flex-shrink: 0; font-weight: 700; letter-spacing: .02em;
  background-image: linear-gradient(150deg, rgba(255, 255, 255, .28), rgba(0, 0, 0, .16));
  box-shadow: var(--dk-shadow-1);
}
.dk-glyph-sm { inline-size: 18px; block-size: 18px; border-radius: 5px; font-size: 8.5px; box-shadow: none; }
.dk-glyph-md { inline-size: 26px; block-size: 26px; border-radius: var(--dk-r-s); font-size: var(--fs-micro); }
.dk-glyph-lg { inline-size: 40px; block-size: 40px; border-radius: 11px; font-size: 13px; }
.dk-glyph-xl { inline-size: 46px; block-size: 46px; border-radius: 13px; font-size: var(--fs-lg); }
.dk-glyph-quiet { background: var(--dk-surface-3); background-image: none; color: var(--dk-ink-2); box-shadow: none; font-size: 16px; }

/* ---- top bar ------------------------------------------------------------- */

/* THE TOP BAR OF THE APPROVED MOCKUP (Syed, 18 September): 56px, the mark and NETFIVE, the desk and what is in
   front, then Today, a search box, the light/dark switch, the bell and the avatar with the person's name. The
   credits and the clock that sat in it went to the account menu and the Today column. */
.dk-bar {
  /* 64px and 24px of side padding, as the handoff draws the bar (20 September 2026). */
  position: relative; z-index: 60; flex-shrink: 0; block-size: 64px;
  display: flex; align-items: center; gap: 24px; padding: 0 24px;
  background: var(--dk-bar);
  border-block-end: 1px solid var(--dk-line);
}
.dk-bar-group { position: relative; display: flex; }
.dk-brand { display: flex; align-items: center; gap: 10px; color: var(--dk-ink); text-decoration: none; }
/* THE APPROVED MARK (Logo.dc.html, 17 September): the letter N cut as one continuous stroke, in
   the brand's own red. It reads `--a-ruby` rather than `--accent` deliberately — a logo that
   changes colour with somebody's accent preference is no longer the logo. The bare stroke, not the
   filled squircle: a solid red tile here would shout beside a row of app icons. */
.dk-brand-mark { inline-size: 28px; block-size: 28px; flex-shrink: 0; display: block; color: var(--a-ruby); }
.dk-brand-word { font-size: 17px; font-weight: 700; letter-spacing: .02em; }
.dk-bar-btn {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 9px; border: 0; border-radius: 8px;
  background: transparent; color: var(--dk-ink-2); font-size: var(--fs-sm); cursor: pointer; white-space: nowrap;
}
.dk-bar-btn:hover { background: color-mix(in srgb, var(--dk-ink) 7%, transparent); color: var(--dk-ink); }
.dk-bar-btn[aria-pressed="true"],
.dk-bar-btn[aria-expanded="true"] { background: var(--dk-surface); color: var(--dk-ink); box-shadow: var(--dk-shadow-1); }

/* THE BAR IN THREE ZONES (Syed's shell design, 19 September). The two sides share what the search leaves, so the
   search sits in the middle of the bar; the right side never gives up its controls, so on a narrow screen the search
   moves over rather than anything being covered. */
.dk-bar-zoned { gap: 12px; padding: 0 14px 0 18px; }
.dk-bar-start, .dk-bar-end { flex: 1 1 0; display: flex; align-items: center; }
.dk-bar-start { gap: 6px; min-inline-size: 0; }
.dk-bar-end { justify-content: flex-end; gap: 4px; }
.dk-bar-rule { inline-size: 1px; block-size: 18px; margin: 0 6px; flex-shrink: 0; background: var(--dk-line); }

/* Crumbs: the desk, grey, with a screen drawn in the desk's colour; a chevron; and what is in front, in the ink. */
.dk-crumbs { display: flex; align-items: center; gap: 4px; min-inline-size: 0; }
.dk-desk-btn { block-size: 30px; gap: 6px; padding: 0 8px; color: var(--dk-ink-2); font-size: 13px; }
.dk-desk-ico { display: grid; color: var(--dk-ink-3); }
.dk-desk-ico .dk-icon { inline-size: 15px; block-size: 15px; }
.dk-crumb-sep { display: grid; color: var(--dk-ink-3); opacity: .6; }
.dk-crumb-sep .dk-icon { inline-size: 13px; block-size: 13px; }

/* Today, the search box, the switch and the bell share one shape: 34px high, a 9px corner, the edge a field has. */
.dk-bar-pill, .dk-find, .dk-bell {
  block-size: 34px; border-radius: 9px; border: 1px solid var(--dk-line); background: var(--dk-surface-2);
}
.dk-bar-pill { padding: 0 18px; color: var(--dk-ink-2); font-size: 13px; font-weight: 600; background: transparent; }
.dk-bar-pill[aria-pressed="true"] { background: var(--dk-surface-2); color: var(--dk-ink); box-shadow: none; }
.dk-find { inline-size: 300px; min-inline-size: 0; flex-shrink: 1; gap: 9px; padding: 0 12px; color: var(--dk-ink-3); font-size: 13px; }
.dk-find:hover { background: var(--dk-surface-3); color: var(--dk-ink-2); }
.dk-find .dk-icon { inline-size: 15px; block-size: 15px; }
.dk-find-text { flex: 1; min-inline-size: 0; text-align: start; overflow: hidden; text-overflow: ellipsis; }
.dk-find .dk-kbd {
  font-family: inherit; font-size: 11px; color: var(--dk-ink-3); border-width: 1px; border-radius: 5px; padding: 1px 6px;
  border-color: color-mix(in srgb, var(--dk-line-strong) 75%, var(--dk-ink-3));
}
.dk-bell { inline-size: 34px; padding: 0; justify-content: center; color: color-mix(in srgb, var(--dk-ink) 60%, var(--dk-ink-2)); }
.dk-bell .dk-icon { inline-size: 17px; block-size: 17px; }
.dk-bell[aria-expanded="true"] { background: var(--dk-surface-3); box-shadow: none; }

/* ---- light or dark, in one click (Syed, 17 September) ------------------------------------------
   One ghost button since the shell design (19 September): the moon on a light screen, the sun on a
   dark one. The filled pair it replaced competed with the real primary actions. The rest of the
   choice — System, and the accents — is in the account menu behind the avatar. */
.dk-look-toggle { inline-size: 34px; block-size: 34px; padding: 0; justify-content: center; flex-shrink: 0; color: var(--dk-ink-2); }
.dk-look-toggle .dk-icon { inline-size: 17px; block-size: 17px; }
.dk-dot { inline-size: 8px; block-size: 8px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
.dk-now {
  padding: 0 4px; font-size: 13px; font-weight: 600; color: var(--dk-ink); max-inline-size: 28ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The design's search: wider, in the middle, and white like a field; Ctrl and K as two caps. */
.dk-bar-zoned .dk-find { inline-size: auto; flex: 0 1 460px; min-inline-size: 200px; padding: 0 10px; gap: 8px; background: var(--dk-surface); }
.dk-find-keys { display: flex; gap: 3px; flex-shrink: 0; }

/* Today, as a control over a date: a calendar, the word and a chevron, as the design draws it. */
.dk-today-btn { gap: 6px; padding: 0 10px; font-size: 12.5px; font-weight: 500; color: var(--dk-ink); background: var(--dk-surface); }
.dk-today-btn .dk-icon { inline-size: 14px; block-size: 14px; color: var(--dk-ink-2); }
.dk-today-chev { display: grid; }
.dk-today-chev .dk-icon { inline-size: 12px; block-size: 12px; color: var(--dk-ink-3); }

/* The credits chip: the balance in short, and the share of this period's allowance left as a meter under the word.
   It opens Usage. */
.dk-credit-chip {
  block-size: 34px; gap: 7px; padding: 0 10px; flex-shrink: 0; border: 1px solid var(--dk-line); border-radius: 9px;
  background: var(--dk-surface); color: var(--dk-ink);
}
.dk-credit-chip > .dk-icon { inline-size: 14px; block-size: 14px; color: var(--accent); }
.dk-credit-chip b { font-size: 12.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
.dk-credit-side { display: flex; flex-direction: column; gap: 2px; }
.dk-credit-word { font-size: 10px; line-height: 1; color: var(--dk-ink-3); }
.dk-credit-meter { inline-size: 44px; block-size: 3px; border-radius: 999px; overflow: hidden; background: var(--dk-line); }
.dk-credit-meter > span { display: block; block-size: 100%; border-radius: 999px; background: var(--accent); }

/* The bell as a ghost beside the switch, on the phone's bar too, its count a small red pill on its corner. */
.dk-bar .dk-bell { border-color: transparent; background: transparent; }
.dk-bar .dk-bell:hover { background: color-mix(in srgb, var(--dk-ink) 7%, transparent); }
.dk-bar .dk-bell[aria-expanded="true"] { background: var(--dk-surface-3); }
.dk-bar .dk-badge {
  inset-block-start: 2px; inset-inline-end: -1px; min-inline-size: 15px; block-size: 15px; font-size: 9.5px;
  box-shadow: 0 0 0 1.5px var(--dk-surface);
}
.dk-bell { position: relative; }
/* The brand's red with white on it, as both previews draw the count: 4.6:1, where the dark theme's lighter red
   would have put white at under 3:1. */
.dk-badge {
  position: absolute; inset-block-start: -5px; inset-inline-end: -5px; min-inline-size: 16px; block-size: 16px;
  padding: 0 4px; border-radius: 8px; background: #E11D33; color: #FFFFFF;
  font-size: 10px; font-weight: 700; display: grid; place-items: center; line-height: 1;
}

/* The avatar: a circle with the initial, the person's name and a chevron, one button that opens the account menu.
   A tint of the accent with the initial in it (Syed, 19 September: the shell previews' #E0E7FF and #3730A3 in light,
   #2E2A4A and #C7D2FE in dark, which is indigo; another accent tints the same way). */
.dk { --dk-face: color-mix(in srgb, var(--accent) 17%, #FFFFFF); --dk-face-ink: color-mix(in srgb, var(--accent) 78%, #000000); }
[data-theme="dark"] .dk { --dk-face: color-mix(in srgb, var(--accent) 24%, var(--dk-surface)); --dk-face-ink: color-mix(in srgb, var(--accent) 45%, #FFFFFF); }
.dk-avatar {
  display: inline-flex; align-items: center; gap: 9px; padding: 2px 4px 2px 2px; border: 0; border-radius: 999px;
  background: transparent; color: var(--dk-ink); font-size: 14px; font-weight: 600; cursor: pointer; white-space: nowrap;
}
.dk-avatar:hover, .dk-avatar[aria-expanded="true"] { background: color-mix(in srgb, var(--dk-ink) 7%, transparent); }
.dk-avatar-face {
  inline-size: 32px; block-size: 32px; border-radius: 50%; display: grid; place-items: center; flex-shrink: 0;
  background: var(--dk-face); color: var(--dk-face-ink); font-size: 14px; font-weight: 700;
}
.dk-avatar-name { max-inline-size: 16ch; overflow: hidden; text-overflow: ellipsis; }
.dk-avatar > .dk-icon { inline-size: 14px; block-size: 14px; color: var(--dk-ink-3); }

/* The balance, in the phone's account menu. */
.dk-pop-credits { padding: 0 10px 6px; font-family: var(--mono); font-size: var(--fs-xs); color: var(--dk-ink-2); }

/* A narrower screen keeps every control and gives up words first: the name, then the search box's width. */
@media (max-width: 1180px) { .dk-find { inline-size: 220px; } .dk-bar-zoned .dk-find { flex-basis: 300px; } }
@media (max-width: 1080px) { .dk-avatar-name { display: none; } .dk-bar { gap: 12px; } }
@media (max-width: 900px) {
  .dk-find, .dk-bar-zoned .dk-find { inline-size: 34px; flex: 0 0 34px; min-inline-size: 0; padding: 0; justify-content: center; }
}

/* ---- menus ----------------------------------------------------------------- */

/* A MENU IS NEVER TALLER THAN THE ROOM IT HAS (Syed, 21 September 2026: "There is no visible scroll
   bar available. A scroll bar should be provided so the content can be scrolled using the mouse
   wheel.").

   The Ask card's model menu lists every model the account may choose, and an account with sixty of
   them got a sixty-item menu: no cap, no overflow, so it simply ran off the bottom of the window.
   Nothing scrolled, because nothing was overflowing - the menu was the full height of its content
   and the window was what cut it off. A wheel over it scrolled the page behind instead.

   The cap was already here, on .dk-pop-wide, and the plain menu never got it. It belongs on the base
   class: every .dk-pop grows with its data, and the next long one should not have to rediscover this.

   min() rather than a flat 440px, because 440 is still too tall for a menu opening low in a short
   window. scrollbar-gutter reserves the bar's width so the items do not shift sideways the moment a
   menu becomes scrollable - and `thin` keeps it visible rather than an overlay bar that fades out,
   which is the bar Syed could not see. A menu that fits shows no bar at all.

   scrollbar-color is set HERE rather than inherited. The page sets it at :root in terms of --surface-4,
   which is the site's palette; the desk has its own --dk-* palette, so a menu on the dark stone would
   have drawn its thumb from the wrong set. --dk-ink-3 measures 6.5:1 against the dark menu surface and
   4.8:1 against the light one, both well clear of the 3:1 a control needs to be seen at all. */
.dk-pop {
  position: absolute; inset-block-start: calc(100% + 6px); inset-inline-start: 0; z-index: 70;
  min-inline-size: 240px; padding: 6px; display: grid; gap: 2px;
  background: var(--dk-surface); color: var(--dk-ink); border: 1px solid var(--dk-line);
  border-radius: 12px; box-shadow: var(--dk-shadow-2);
  max-block-size: min(440px, 60vh); overflow-y: auto;
  scrollbar-gutter: stable; scrollbar-width: thin;
  scrollbar-color: var(--dk-ink-3) transparent;
}
.dk-pop-end { inset-inline-start: auto; inset-inline-end: 0; }
/* Keeps its own width; the height cap and the scrolling now come from .dk-pop above. */
.dk-pop-wide { inline-size: 320px; }
.dk-pop-head { display: flex; align-items: center; gap: 8px; padding: 4px 8px 8px; }
.dk-pop-lbl { padding: 6px 10px 2px; font-size: var(--fs-micro); font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--dk-ink-3); }
.dk-pop-item {
  display: flex; align-items: center; gap: 8px; inline-size: 100%; padding: 7px 10px; border: 0; border-radius: 8px;
  background: transparent; color: var(--dk-ink); text-align: start; font-size: var(--fs-sm); text-decoration: none; cursor: pointer;
}
.dk-pop-item:hover { background: var(--dk-surface-3); }
.dk-pop-danger { color: var(--dk-crit); }
/* The current choice, shown quietly beside an item that opens a sub-menu - "Language   English ›" - so what
   is set can be read without opening it. */
.dk-pop-now { color: var(--dk-ink-3); font-size: var(--fs-xs); white-space: nowrap; }
.dk-pop-sep { block-size: 1px; background: var(--dk-line-soft); margin: 4px 0; }

.dk-bell-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.dk-bell-item {
  display: grid; gap: 2px; inline-size: 100%; padding: 8px 10px; border: 0; border-radius: 8px;
  background: transparent; text-align: start; cursor: pointer; font-size: var(--fs-sm);
}
.dk-bell-item:hover { background: var(--dk-surface-3); }
.dk-bell-item.is-unread { background: color-mix(in srgb, var(--accent) 9%, var(--dk-surface)); }
.dk-bell-item span { color: var(--dk-ink-2); font-size: var(--fs-xs); }
.dk-bell-item time { color: var(--dk-ink-3); font-size: var(--fs-micro); }

/* The right-click menu: fixed to the viewport, placed by desktop.ts so it never runs off the screen. */
.dk-ctx { position: fixed; min-inline-size: 200px; z-index: 150; }
.dk-pop-item:disabled { opacity: .45; cursor: default; background: transparent; }

/* ---- the body: dock and desk --------------------------------------------------- */

.dk-banner {
  flex-shrink: 0; block-size: 30px; display: flex; align-items: center; gap: 8px; padding: 0 12px;
  font-size: var(--fs-sm); background: color-mix(in srgb, var(--dk-warn) 14%, var(--dk-surface)); color: var(--dk-ink);
  border-block-end: 1px solid var(--dk-line-soft);
}
.dk-pulse { inline-size: 7px; block-size: 7px; border-radius: 50%; background: var(--dk-warn); animation: dk-pulse 1.4s ease-in-out infinite; }

.dk-body { flex: 1; min-block-size: 0; display: flex; }

/* THE LEFT RAIL (Syed, 18 September: "This is what I had asked you to implement"). It replaced the 98px dock and
   keeps its class names, because it does every job the dock did — open an app, show which are open, pin — and
   seven browser checks find apps through them. The sizes are the approved mockup's: 150px across, INTELLIGENCE
   with Ask AI and Compare as larger tinted squares, PINNED with each app's icon in a faint square of its own
   colour and its name beneath, More apps counted, and Search and a filled All apps at the foot. The spacing is
   what fits all of that on a 900px-high screen without the rail scrolling. */
.dk-dock {
  /* 96px, as the handoff draws it (20 September 2026). It was 150px while the rail carried Ask AI and Compare as
     large squares; those are the home screen's two cards now, so the rail is only apps. */
  inline-size: 96px; flex-shrink: 0; display: flex; flex-direction: column;
  padding: 12px 0 12px; overflow: hidden;
  background: var(--dk-rail);
  border-inline-end: 1px solid var(--dk-line); position: relative; z-index: 30;
}
/* The apps scroll when the screen is shorter than the mockup's; Search and All apps are a foot that stays. */
.dk-dock-apps {
  flex: 1; min-block-size: 0; display: flex; flex-direction: column; gap: 2px;
  padding: 0 8px; overflow-y: auto; overflow-x: hidden;
}

/* THE RAIL'S SCROLLBAR IS DRAWN RATHER THAN OVERLAID (Syed, 22 September 2026: "left panel is not
   showing scroll bar until we hover mouse on it").

   IT WAS NEVER HIDDEN. It was the wrong colour, and only in the dark theme.

   The rail's scrollbar always took 10px of layout width, so it was always drawn. What made it
   invisible is that `scrollbar-color` is set once at :root from `--surface-4` - the SITE palette -
   and inherits into the desk, which has a palette of its own. In the light theme that is a cream
   bar on a near-white rail: low contrast but findable. In the dark theme it is a dark bar on a
   near-black rail, and there is nothing to see until the pointer arrives and Chromium brightens
   the thumb itself. Hence "not showing until we hover".

   So the fix is the desk's own ink at 28%, which lands light-on-dark and dark-on-light, and the
   pseudo-elements do it because `scrollbar-color` alone cannot round a thumb or set its width.
   Same shape as the Calendar's hour grid, which carried the same complaint about a different panel.

   TWO THINGS THAT MADE THIS HARD TO SEE, both worth knowing before touching a scrollbar again.
   Headless Chromium draws no scrollbars at all and reports zero width for every one of them,
   including a deliberately 20px bar - so the first diagnosis here, "it is an overlay scrollbar",
   was an artefact of the instrument and simply wrong. And the light theme looks fine, so a check
   that did not switch themes would have found nothing to fix. Measure this one headed, in dark.

   NO scrollbar-gutter, deliberately. The note at the top of this file records that reserving the
   width permanently is what made one complaint arrive four times, ending with the client looking at
   reserved empty space. This takes its 6px only while there is something to scroll. */
.dk-dock-apps {
  scrollbar-width: auto;
  scrollbar-color: auto;
}
.dk-dock-apps::-webkit-scrollbar { inline-size: 6px; }
.dk-dock-apps::-webkit-scrollbar-track { background: transparent; }
.dk-dock-apps::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--dk-ink) 28%, transparent); border-radius: 3px;
}
.dk-dock-apps::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--dk-ink) 45%, transparent);
}
.dk-dock-foot { flex-shrink: 0; display: flex; flex-direction: column; gap: 2px; padding: 6px 8px 0; }
.dk-dock-apps > *, .dk-dock-foot > * { flex-shrink: 0; }
/* While any of the list is out of sight below, its bottom edge fades and an arrow sits on the fade (Syed, 18
   September): an OPEN NOW section had pushed "More apps" under the foot with nothing to say the rail scrolls. A mask
   rather than a gradient over the top, because the rail's own background is translucent. The list is gone by 24px
   above its edge, so the arrow sits on a clear strip rather than over whichever icon is passing beneath it. */
/* And room BELOW the last item for the arrow to sit in, so nothing of the list is ever underneath it
   (Syed, 21 September 2026: "MORE is overlapping on dashboard"). A section heading caught in the fade showed
   through the arrow's translucent chip and read as the same word printed twice. */
.dk-dock-apps-more { mask-image: linear-gradient(to bottom, #000 calc(100% - 64px), transparent calc(100% - 24px)); padding-block-end: 22px; }
.dk-dock-below { position: relative; flex-shrink: 0; block-size: 0; }
.dk-dock-below > span {
  position: absolute; inset-block-end: 3px; inset-inline-start: 50%; translate: -50% 0;
  display: grid; place-items: center; inline-size: 28px; block-size: 18px; border-radius: 9px; cursor: pointer;
  color: var(--dk-ink-2); background: var(--dk-surface); border: 1px solid var(--dk-line);
}
.dk-dock-below > span:hover { color: var(--dk-ink); background: var(--dk-surface-3); }
.dk-dock-below .dk-icon { inline-size: 16px; block-size: 16px; }
.dk-dock-lbl {
  font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--dk-ink-3);
  padding: 0 4px 4px;
}
/* Each section but the first opens with a hairline, as the handoff has it. */
.dk-dock-btn + .dk-dock-lbl {
  margin-block-start: 6px; padding-block-start: 8px; border-block-start: 1px solid var(--dk-line-soft, var(--dk-line));
}
.dk-dock-btn {
  position: relative; display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 4px 0; border: 0; border-radius: 12px; background: transparent; cursor: pointer;
  color: color-mix(in srgb, var(--dk-ink) 55%, var(--dk-ink-2));
}
.dk-dock-btn:hover { background: color-mix(in srgb, var(--dk-ink) 6%, transparent); color: var(--dk-ink); }
.dk-dock-btn .dk-glyph { transition: transform var(--dur-2) var(--ease); }
.dk-dock-btn:hover .dk-glyph { transform: scale(1.06); }
.dk-dock-btn[aria-current="true"] { background: var(--dk-surface); color: var(--dk-ink); box-shadow: var(--dk-shadow-1); }
/* Open in a window: a bar at the rail's edge — the accent while the window is showing, a quieter one while it is
   minimised, so which apps are open reads from the rail as it did from the dock. */
.dk-dock-btn.is-open::before {
  content: ""; position: absolute; inset-inline-start: -8px; inset-block-start: 50%; translate: 0 -50%;
  inline-size: 3px; block-size: 18px; border-radius: 2px; background: var(--dk-ink-3);
}
.dk-dock-btn[aria-current="true"]::before { background: var(--accent); block-size: 24px; }
.dk-dock-name {
  font-size: 10.5px; font-weight: 600; line-height: 1.22; text-align: center; max-inline-size: 76px;
  overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}

/* Ask AI and Compare: larger squares, a stronger tint of their own colour, the name in the ink. */
.dk-dock-hero { gap: 5px; padding: 10px 0; }
.dk-dock-hero .dk-glyph { inline-size: 40px; block-size: 40px; border-radius: 12px; }
.dk-dock-hero .dk-dock-name { font-size: 12px; font-weight: 600; color: var(--dk-ink); max-inline-size: 76px; }
[data-theme="dark"] .dk-dock-hero .dk-glyph-sym { background: color-mix(in srgb, var(--dk-app) 28%, #0D0E11); }
/* Ask AI shows as the chosen one, as the mockup draws it. */
.dk-dock-lead { background: color-mix(in srgb, var(--dk-surface-2) 50%, var(--dk-surface-3)); }

/* Every rail app: a 28px tile with the name beneath (the handoff, 20 September 2026). */
.dk-dock-pin .dk-glyph { inline-size: 28px; block-size: 28px; border-radius: 8px; }
.dk-dock-pin .dk-glyph-sym .dk-sym { inline-size: 20px; block-size: 20px; }
[data-theme="dark"] .dk-dock-pin .dk-glyph-sym { background: color-mix(in srgb, var(--dk-app) 16%, #0D0E11); }
.dk-dock-pin .dk-glyph-quiet .dk-icon { inline-size: 18px; block-size: 18px; }

/* Tighter while windows are open — the taskbar takes 40px from the rail — and on a screen shorter than the mockup's
   900px, so that every pin still shows above the foot. The same squares and names, with less air between them. */
.dk-dock-compact .dk-dock-hero { padding: 6px 0; }
.dk-dock-compact .dk-dock-hero .dk-glyph { inline-size: 36px; block-size: 36px; }
.dk-dock-compact .dk-dock-pin { padding: 2px 0; }
.dk-dock-compact .dk-dock-pin .dk-glyph { inline-size: 26px; block-size: 26px; border-radius: 8px; }
.dk-dock-compact .dk-dock-btn + .dk-dock-lbl { padding-block-start: 8px; }
@media (max-height: 880px) {
  .dk-dock-hero { padding: 6px 0; }
  .dk-dock-hero .dk-glyph { inline-size: 36px; block-size: 36px; }
  .dk-dock-pin { padding: 2px 0; }
  .dk-dock-pin .dk-glyph { inline-size: 26px; block-size: 26px; border-radius: 8px; }
  .dk-dock-btn + .dk-dock-lbl { padding-block-start: 8px; }
}

/* Home, Search and Settings: utilities rather than apps, so a white tile with a hairline and a dark mark, as the
   handoff draws the foot (20 September 2026). */
.dk-glyph-home, .dk-dock-foot .dk-glyph-quiet {
  background: var(--dk-surface); background-image: none; border: 1px solid var(--dk-line); color: var(--dk-ink);
  box-shadow: none;
}
[data-theme="dark"] .dk-glyph-home, [data-theme="dark"] .dk-dock-foot .dk-glyph-quiet {
  background: var(--dk-surface-2); border-color: var(--dk-line-strong);
}
.dk-dock-home[aria-current="page"] { background: var(--dk-surface); box-shadow: var(--dk-shadow-1); }
.dk-dock-home[aria-current="page"] .dk-glyph-home { background: var(--dk-ink); border-color: var(--dk-ink); color: var(--dk-surface); }
.dk-dock-home[aria-current="page"] .dk-dock-name { font-weight: 700; color: var(--dk-ink); }
.dk-dock-home { text-decoration: none; }

/* All apps: filled in the accent, at the foot of the rail (Syed, 19 September: the shell previews' indigo). */
.dk { --dk-all: var(--accent-fill); }
.dk-dock-all {
  display: flex; align-items: center; justify-content: center; gap: 8px; block-size: 38px; margin-block-start: 6px;
  border: 0; border-radius: 10px; background: var(--dk-all); color: var(--on-accent); font-size: 13px; font-weight: 600; cursor: pointer;
}
.dk-dock-all:hover { background: var(--accent-fill-hover); }
.dk-dock-all .dk-icon { inline-size: 15px; block-size: 15px; }

.dk-desk { position: relative; flex: 1; min-inline-size: 0; overflow: hidden; isolation: isolate; }

/* The desk is painted flat in both themes (Syed, 19 September: the shell previews). Atrium's "blueprint" wallpaper —
   a glow of the accent and of Atrium's green over a grid — was the largest patch of the green his note asked to
   remove, and the dark theme's faint grid went with it. */
.dk-wall { position: absolute; inset: 0; z-index: 0; pointer-events: none; background: var(--dk-ground); }

/* ---- the home screen, behind the windows ------------------------------------- */

/* THE HOME SCREEN OF THE APPROVED MOCKUP (Syed, 18 September): 34px by 32px of margin, 22px between its parts —
   the greeting, Ask AI and Compare, the folder row, then Your apps. The Today column is beside it, not over it. */
/* 28px above and 48px at the sides, as the handoff measures the main column (20 September 2026). */
.dk-home { position: absolute; inset: 0; z-index: 1; overflow: auto; padding: 28px 48px 40px; }
@media (min-width: 1081px) { .dk-home-beside { inset-inline-end: 340px; } }
/* The home screen sits in the middle of whatever room it has (Syed, 19 September 2026: "can you center align
   everything"). On a wide screen it used to hug the left edge with the gap all on one side; the width it is allowed
   is unchanged, so on a narrow screen nothing moves. */
.dk-home-in { max-inline-size: 1180px; margin-inline: auto; display: flex; flex-direction: column; gap: 22px; }
.dk-greeting { margin: 0 0 6px; font-size: 34px; font-weight: 700; line-height: 1.25; letter-spacing: -.01em; }
/* The person's own name, in the accent, as the preview draws it (Syed, 17 September). The greeting
   is one formatted phrase, so the name is a span inside it rather than a second sentence. */
/* The name is in the ink, not the accent: the handoff's greeting is one colour (20 September 2026). */
.dk-greeting-name { color: inherit; }
.dk-sub { margin: 0; color: var(--dk-ink-2); font-size: 14.5px; }

/* Ask AI, a box to type into, and Compare beside it at 430px. They stack when there is no room for both. */
/* Ask and Compare in the handoff's 1.75 : 1 (20 September 2026). A grid rather than two flexible boxes: with flex,
   Compare's own words set a floor and the row came out at 1.66 : 1. Below 1024 they stack, as the handoff says. */
.dk-heroes { display: grid; grid-template-columns: 1.75fr 1fr; gap: 16px; align-items: stretch; }
.dk-heroes > * { min-inline-size: 0; }
@media (max-width: 1023px) { .dk-heroes { grid-template-columns: 1fr; } }
.dk-hero-ico {
  inline-size: 34px; block-size: 34px; border-radius: 10px; display: grid; place-items: center; flex-shrink: 0;
  background: var(--accent-soft); color: var(--accent);
}
.dk-hero-ico .dk-icon { inline-size: 18px; block-size: 18px; }
.dk-hero-ico-lg { inline-size: 44px; block-size: 44px; border-radius: 12px; }
.dk-hero-ico-lg .dk-icon { inline-size: 22px; block-size: 22px; }

/* The Ask card (NETFIVE-Home-Handoff-v3, 20 September 2026): the card and Compare beside it in 1.75 : 1, a header
   with its subtitle, a box whose textarea grows to six rows, chips for whatever is attached, and a toolbar. */
.dk-ask {
  position: relative; flex: 1.75 1 0; min-inline-size: 0; padding: 14px 16px;
  border-radius: 16px; border: 1px solid var(--dk-line); background: var(--dk-surface);
}
/* Dragging files over the card: a dashed edge and a word, as the handoff draws it. */
.dk-ask.is-over { border: 2px dashed var(--accent); padding: 13px 15px; }
.dk-ask-drop {
  position: absolute; inset-block-end: 10px; inset-inline: 16px; margin: 0; text-align: center;
  font-size: 12.5px; font-weight: 700; color: var(--accent);
}
.dk-ask-head { display: flex; align-items: center; gap: 10px; margin-block-end: 12px; }
.dk-ask-titles { min-inline-size: 0; }
.dk-ask-head h2 { margin: 0; font-size: 14px; font-weight: 800; letter-spacing: normal; }
.dk-ask-sub { margin: 2px 0 0; font-size: 12px; color: var(--dk-ink-2); }
.dk-ask-box {
  display: flex; flex-direction: column; gap: 8px; padding: 9px 12px;
  border-radius: 12px; border: 1px solid var(--dk-field);
  background: color-mix(in srgb, var(--dk-surface-2) 50%, var(--dk-surface-3));
}
/* The field draws no outline of its own, so the box it sits in shows where the typing is. */
.dk-ask-box:focus-within { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.dk-ask-input {
  inline-size: 100%; min-inline-size: 0; border: 0; padding: 0; background: transparent; color: var(--dk-ink);
  font: inherit; font-size: 14px; line-height: 1.45; outline: none; resize: none; overflow-y: auto;
}
.dk-ask-input::placeholder { color: var(--dk-ink-3); opacity: 1; }

/* What is attached, between the typing and the toolbar: a name each, and a cross to take it off. */
/* Compare: the card is one link, its head above and the models it can ask beneath (handoff §4.4). */
.dk-compare { flex-direction: column; align-items: stretch; gap: 10px; }
.dk-compare-head { display: flex; align-items: center; gap: 14px; min-inline-size: 0; }
.dk-compare-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.dk-compare-chip {
  block-size: 26px; display: inline-flex; align-items: center; padding: 0 9px; border-radius: 8px;
  border: 1px solid var(--dk-line); background: var(--dk-surface-2); font-size: 12px; font-weight: 600; color: var(--dk-ink-2);
}
.dk-compare-more { border-style: dashed; }

/* Grey blocks while a card waits for its rows (handoff §5). They breathe rather than spin, and stop breathing
   for anybody who asked their system for less motion. */
.dk-skeleton { display: grid; gap: 8px; }
.dk-skeleton-row { display: block; block-size: 14px; border-radius: 7px; background: var(--dk-surface-3); animation: dk-pulse 1.4s ease-in-out infinite; }
.dk-skeleton-row:nth-child(2) { inline-size: 84%; animation-delay: .15s; }
.dk-skeleton-row:nth-child(3) { inline-size: 68%; animation-delay: .3s; }
.dk-continue .dk-skeleton { padding: 10px 12px; border: 1px solid var(--dk-line); border-radius: 12px; }
.dk-continue .dk-skeleton-row { block-size: 26px; }
@keyframes dk-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }
@media (prefers-reduced-motion: reduce) { .dk-skeleton-row { animation: none; } }

/* ---- NETFIVE noticed (NETFIVE-AI-Handoff-v5 §1.1b, 21 September 2026) --------------------------
 *
 * Three cards between "Continue where you left off" and "Your apps". Each is one button: the whole
 * card opens the room, and the line at the foot says so rather than being a second target inside a
 * target — a link nested in a clickable card is two things to press that do the same thing.
 *
 * The section draws nothing at all when there is nothing to say (see NoticedSection), so there is no
 * empty state to style here and none should be added: on most desks this has never existed.
 */
/* "Search in", under the Ask box (Syed's mockup §1.1a, 23 September 2026). A label, the places, and
   the promise. The chips wrap rather than scroll: a row that scrolls sideways hides the last place
   on a narrow window, and the last place is as real as the first. */
.dk-searchin {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px;
  margin-block-start: 10px; min-inline-size: 0;
}
.dk-searchin-lbl { font-size: var(--fs-micro); color: var(--dk-ink-3); }
.dk-searchin-chip {
  padding: 3px 11px; border: 1px solid var(--dk-line); border-radius: 999px;
  background: var(--dk-surface); color: var(--dk-ink);
  font: inherit; font-size: var(--fs-micro); cursor: pointer; white-space: nowrap;
}
.dk-searchin-chip:hover { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
/* The promise, quiet and on its own line when the chips have used the width. */
.dk-searchin-only { flex-basis: 100%; font-size: var(--fs-micro); color: var(--dk-ink-3); }

.dk-noticed { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
/* The handoff's 22px red-tint chip. Fixed to the accent's soft pair rather than a literal, so it
   follows an account that has chosen something other than red. */
.dk-noticed-mark {
  inline-size: 22px; block-size: 22px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 7px; background: var(--accent-soft); color: var(--accent);
}
.dk-noticed-mark svg { inline-size: 14px; block-size: 14px; }
.dk-noticed-card {
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
  min-inline-size: 0; padding: 12px;
  border: 1px solid var(--dk-line); border-radius: 12px;
  background: var(--dk-surface); color: var(--dk-ink);
  font: inherit; text-align: start; cursor: pointer;
}
/* The card stopped being a button on 22 September 2026, when "Suggest owners" went inside it: a
   control inside a control is invalid, and a browser resolves it by making the inner press the outer
   one - so the suggestion would have opened the room. The box keeps the look; .dk-noticed-open is the
   press that opens, and it carries the hover so the card still lifts under the pointer. */
.dk-noticed-card:hover { border-color: var(--dk-line-strong); }
.dk-noticed-open {
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
  inline-size: 100%; min-inline-size: 0; padding: 0; border: 0;
  background: none; color: inherit; font: inherit; text-align: start; cursor: pointer;
}
.dk-noticed-card:has(.dk-noticed-open:hover) { background: var(--dk-surface-2); }
.dk-noticed-src { font-size: var(--fs-micro); color: var(--dk-ink-3); }
.dk-noticed-title { font-size: 13px; font-weight: 700; line-height: 1.35; }
.dk-noticed-ctx { font-size: var(--fs-micro); color: var(--dk-ink-2); }
/* The action, as text. Indigo like every other link in the product, and it is a label on the card's
   own behaviour rather than a control — the card is the control. */
.dk-noticed-act { margin-block-start: 2px; font-size: var(--fs-micro); font-weight: 600; color: var(--link); }
.dk-noticed-card:hover .dk-noticed-act { color: var(--link-hover); text-decoration: underline; }
.dk-noticed-act { padding: 0; border: 0; background: none; cursor: pointer; }
.dk-noticed-act[disabled] { opacity: .55; cursor: default; }

/* "Suggest owners", and what it opens. Two actions side by side, then the work with the people
   beside it - so the answer appears where the question was asked rather than in another panel. */
.dk-noticed-foot { display: flex; align-items: center; gap: 12px; margin-block-start: 2px; }
.dk-noticed-owners { display: grid; justify-items: start; gap: 6px; margin-block-start: 4px; inline-size: 100%; min-inline-size: 0; }
/* A grid stretches its children, and a stretched button centres its label — which put "Open the
   room" in the middle of the card, reading as a dialog's button rather than as the card's own link. */
.dk-noticed-owners > .dk-noticed-task { justify-self: stretch; }
.dk-noticed-task {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px;
  min-inline-size: 0; padding-block-start: 6px; border-block-start: 1px solid var(--dk-line-soft);
}
.dk-noticed-taskname { font-size: 12px; font-weight: 600; inline-size: 100%; }
/* One person, and why them. A chip rather than a link: it does something, and it is not navigation. */
.dk-noticed-give {
  padding: 3px 9px; border: 1px solid var(--dk-line); border-radius: 999px;
  background: var(--dk-surface-2); color: var(--dk-ink); font: inherit; font-size: var(--fs-micro);
  cursor: pointer; max-inline-size: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dk-noticed-give:hover { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }

@media (max-width: 900px) { .dk-noticed { grid-template-columns: minmax(0, 1fr); } }

/* Continue where you left off (the handoff §4.5, 20 September 2026): four cards across, each the whole link. */
.dk-continue { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
.dk-cont {
  display: flex; align-items: center; gap: 10px; min-inline-size: 0; padding: 10px 12px;
  border: 1px solid var(--dk-line); border-radius: 12px; background: var(--dk-surface); color: var(--dk-ink);
  font: inherit; text-align: start; cursor: pointer;
}
.dk-cont:hover { border-color: var(--dk-line-strong); }
.dk-cont-text { min-inline-size: 0; display: grid; gap: 1px; }
.dk-cont-text b { font-size: 13px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dk-cont-text span { font-size: 12px; color: var(--dk-ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 1279px) { .dk-continue { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Messages in the day panel (the handoff, 20 September 2026): a face, the name, and the last thing said. */
.dk-msg-row { align-items: flex-start; gap: 10px; }
.dk-msg-face {
  display: grid; place-items: center; flex-shrink: 0; inline-size: 32px; block-size: 32px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent); font-size: 13px; font-weight: 700;
}
/* A group wears a rounded square, a person a circle: the same distinction Chat itself draws, so the two screens
   agree before a name is read (Chat V2, 20 September 2026). */
.dk-msg-face.is-group { border-radius: 9px; background: var(--dk-surface-3); color: var(--dk-ink-2); }
.dk-msg-text { display: grid; gap: 1px; min-inline-size: 0; text-align: start; }
.dk-msg-text b { font-size: 13px; font-weight: 700; color: var(--dk-ink); }
.dk-msg-line { font-size: 12px; color: var(--dk-ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* What the plan will not take, said where the files are rather than after they are gone. */
.dk-ask-note { margin: 0; font-size: 12px; font-weight: 600; color: var(--dk-ink-2); }
.dk-ask-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 0; list-style: none; }
.dk-ask-chip {
  display: inline-flex; align-items: center; gap: 6px; max-inline-size: 220px; padding: 3px 4px 3px 8px;
  border-radius: 8px; border: 1px solid var(--dk-line); background: var(--dk-surface);
  font-size: 12px; color: var(--dk-ink);
}
.dk-ask-chip .dk-icon { inline-size: 13px; block-size: 13px; color: var(--dk-ink-3); }
.dk-ask-chip-name { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dk-ask-chip button {
  display: grid; place-items: center; inline-size: 18px; block-size: 18px; padding: 0;
  border: 0; border-radius: 5px; background: transparent; color: var(--dk-ink-3); font-size: 14px; cursor: pointer;
}
.dk-ask-chip button:hover { background: var(--dk-surface-3); color: var(--dk-ink); }

/* Wraps, because the model's own label is the longest thing on this row and Syed chose its words (20 September:
   "Auto select · cheapest/best model for the task or select manually"). A row that cannot hold them puts them on a
   line of their own; it does not abbreviate what he wrote. */
.dk-ask-tools { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 6px; }
.dk-ask-tool, .dk-ask-send { display: grid; place-items: center; flex-shrink: 0; padding: 0; border: 0; cursor: pointer; }
/* Stays at the end of whichever line it lands on: when the model's label pushes it onto a second row, a Send button
   marooned at the left reads as a stray control rather than the end of the toolbar. */
.dk-ask-send { margin-inline-start: auto; }
.dk-ask-tool { inline-size: 34px; block-size: 34px; border-radius: 9px; background: transparent; color: var(--dk-ink-2); }
.dk-ask-tool:hover, .dk-ask-tool[aria-expanded="true"] { background: color-mix(in srgb, var(--dk-ink) 8%, transparent); color: var(--dk-ink); }
.dk-ask-tool .dk-icon { inline-size: 18px; block-size: 18px; }
/* The model the question goes to, named on the toolbar (the handoff, 20 September 2026). */
.dk-ask-model {
  display: inline-flex; align-items: center; gap: 4px; max-inline-size: 100%; block-size: 34px; padding: 0 8px;
  border: 0; border-radius: 9px; background: transparent; color: var(--dk-ink-2);
  font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.dk-ask-model:hover, .dk-ask-model[aria-expanded="true"] { background: color-mix(in srgb, var(--dk-ink) 8%, transparent); color: var(--dk-ink); }
.dk-ask-model-name { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dk-ask-model .dk-icon { inline-size: 14px; block-size: 14px; flex-shrink: 0; }
/* Dictation brings its own button (btn btn-quiet btn-xs); on this toolbar it is the same 34px square as the others. */
.dk-ask-tools .btn {
  inline-size: 34px; block-size: 34px; min-block-size: 0; padding: 0; border: 0; border-radius: 9px;
  display: grid; place-items: center; background: transparent; color: var(--dk-ink-2);
}
.dk-ask-tools .btn:hover:not(:disabled) { background: color-mix(in srgb, var(--dk-ink) 8%, transparent); color: var(--dk-ink); }
.dk-ask-tools .btn.is-listening { background: var(--accent-soft); color: var(--accent); }
.dk-ask-tools .btn svg { inline-size: 18px; block-size: 18px; }
/* Send is the one red button on this screen, with white on it, and it is dead until there is something to send. */
.dk-ask-send {
  inline-size: 36px; block-size: 36px; border-radius: 12px;
  background: var(--accent-fill); color: var(--on-accent);
}
.dk-ask-send:hover:not(:disabled) { background: var(--accent-fill-hover); }
.dk-ask-send:disabled { opacity: .4; cursor: default; }
.dk-ask-send .dk-icon { inline-size: 17px; block-size: 17px; }

/* Compare takes the 1 of the handoff's 1.75 : 1 (20 September 2026); it shared the row by a fixed width before. */
.dk-compare {
  flex: 1 1 0; min-inline-size: 0; display: flex; align-items: center; gap: 14px; padding: 18px;
  border-radius: 14px; border: 1px solid var(--dk-line); background: var(--dk-surface); color: var(--dk-ink);
  text-align: start; cursor: pointer; transition: transform var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}
.dk-compare:hover { transform: translateY(-2px); box-shadow: var(--dk-shadow-2); }
.dk-compare-text { flex: 1; min-inline-size: 0; display: grid; gap: 4px; }
.dk-compare-text b { font-size: 16px; font-weight: 600; }
.dk-compare-text span { font-size: 13px; line-height: 1.45; color: var(--dk-ink-2); }
.dk-compare-go { color: var(--dk-ink-3); display: grid; }
.dk-compare-go .dk-icon { inline-size: 20px; block-size: 20px; }

/* A connected folder's card on the home screen, when it is asked for there. */
.dk-folder {
  display: grid; grid-template-columns: auto 1fr; align-items: center; gap: 4px 14px; padding: 16px;
  border-radius: 14px; border: 1px dashed var(--dk-line); background: color-mix(in srgb, var(--dk-surface) 70%, transparent);
  text-align: start; cursor: pointer; transition: transform var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}
.dk-folder:hover { transform: translateY(-2px); box-shadow: var(--dk-shadow-2); }
.dk-hero-text { display: grid; gap: 3px; min-inline-size: 0; }
.dk-hero-text b { font-size: var(--fs-lg); }
.dk-hero-text span { color: var(--dk-ink-2); font-size: var(--fs-sm); }
.dk-folder-ico {
  inline-size: 46px; block-size: 46px; border-radius: 13px; display: grid; place-items: center;
  background: var(--dk-surface-3); color: var(--dk-ink-2);
}
.dk-folder-ico .dk-icon { inline-size: 22px; block-size: 22px; }

/* "Your apps", a real heading, with Customise and "+ View all apps" at its end (Syed, 18 September). */
.dk-section-head { display: flex; align-items: center; gap: 16px; }
.dk-section-head h2 { margin: 0; font-size: 21px; font-weight: 700; letter-spacing: normal; color: var(--dk-ink); }
.dk-section-head .dk-btn { font-size: 13.5px; }
.dk-section-head .dk-btn-quiet { font-weight: 400; color: var(--dk-ink-2); padding-inline: 8px; }
.dk-section-head .dk-btn:not(.dk-btn-quiet) {
  block-size: 36px; padding: 0 14px; gap: 7px; border-radius: 10px;
  border-color: var(--dk-line-strong); background: var(--dk-surface-2);
}
.dk-section-head .dk-btn .dk-icon { inline-size: 14px; block-size: 14px; }

/* THE APPS AS THE MOCKUP DRAWS THEM (Syed, 18 September): a squircle for each, 76px, a shade lighter than the
   ground with a hairline edge and the app's own coloured icon in it, and the name BENEATH it — no card around the
   name. Five across at the mockup's width, like the icons on a phone's home screen. */
/* Ten columns of 44px tiles (the handoff, 20 September 2026), and fewer columns as the room narrows rather than
   tiles that grow. The old grid filled the width with 150px cells, which made a handful of apps look enormous. */
.dk-tiles { display: grid; grid-template-columns: repeat(10, minmax(0, 1fr)); gap: 6px 8px; }
@media (max-width: 1439px) { .dk-tiles { grid-template-columns: repeat(8, minmax(0, 1fr)); } }
@media (max-width: 1279px) { .dk-tiles { grid-template-columns: repeat(6, minmax(0, 1fr)); } }
/* touch-action none so a finger drag moves the tile instead of scrolling the page away under it: the move is
   driven by pointer events now, and the browser must not claim the gesture first (21 September 2026). */
.dk-tile-wrap { position: relative; display: grid; touch-action: none; }
/* Connected apps: the outside tools, each on a white tile carrying its own logo, under a divider (the handoff,
   20 September 2026). The tiles are the same size as the apps above them. */
.dk-section-split { margin-block-start: 6px; padding-block-start: 16px; border-block-start: 1px solid var(--dk-line); }
.dk-tiles-logos .dk-glyph { background: var(--dk-surface); background-image: none; border: 1px solid var(--dk-line); box-shadow: var(--dk-shadow-1); }
[data-theme="dark"] .dk-tiles-logos .dk-glyph { background: var(--dk-surface-2); border-color: var(--dk-line-strong); }

/*
  Moving an app (Syed, 18 September). The app being moved fades; the one it is over carries a bar on the side it will
  land — beside a tile on the home screen, above or below a pin in the rail, which is a column.
*/
.dk-tile-wrap.is-moving, .dk-dock-btn.is-moving { opacity: .35; }
.dk-dock-btn[data-move-key] { touch-action: none; }
.dk-tile-wrap.is-land-before::before, .dk-tile-wrap.is-land-after::after {
  content: ''; position: absolute; top: 6%; bottom: 6%; inline-size: 3px; border-radius: 2px;
  background: var(--accent); pointer-events: none;
}
.dk-tile-wrap.is-land-before::before { inset-inline-start: -7px; }
.dk-tile-wrap.is-land-after::after { inset-inline-end: -7px; }
.dk-dock-btn.is-land-before { box-shadow: inset 0 3px 0 var(--accent); }
.dk-dock-btn.is-land-after { box-shadow: inset 0 -3px 0 var(--accent); }
.dk-tile {
  display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 14px 6px 12px;
  border-radius: 12px; border: 1px solid var(--dk-line-soft); background: color-mix(in srgb, var(--dk-surface) 82%, transparent);
  cursor: pointer; transition: transform var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}
.dk-tile:hover { transform: translateY(-2px); box-shadow: var(--dk-shadow-2); }
.dk-tile-name { font-size: var(--fs-xs); text-align: center; max-inline-size: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.dk-tiles .dk-tile {
  gap: 10px; padding: 2px 2px 4px; border: 0; border-radius: 18px; background: transparent; color: var(--dk-ink);
}
.dk-tiles .dk-tile:hover { transform: none; box-shadow: none; }
.dk-tiles .dk-glyph {
  inline-size: 44px; block-size: 44px; border-radius: 12px; font-size: 15px;
  transition: transform var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}
.dk-tiles .dk-tile:hover .dk-glyph { transform: translateY(-2px); box-shadow: var(--dk-shadow-2); }
/* A NETFIVE app or a folder with a symbol: the neutral squircle, the icon in the app's colour. A library app's logo
   keeps its white chip, and a folder's picture fills its square. */
.dk-tiles .dk-glyph-sym, .dk-tiles .dk-folder-glyph:not(.dk-glyph-picture) {
  background: var(--dk-surface); border: 1px solid var(--dk-line); box-shadow: none;
}
[data-theme="dark"] .dk-tiles .dk-glyph-sym,
[data-theme="dark"] .dk-tiles .dk-folder-glyph:not(.dk-glyph-picture) { background: var(--dk-surface-2); border-color: var(--dk-line-strong); }
.dk-tiles .dk-glyph-sym .dk-sym { inline-size: 32px; block-size: 32px; }
.dk-tiles .dk-folder-glyph svg { inline-size: 30px; block-size: 30px; }
/* TWO LINES, AND THEY HAVE TO BE ALLOWED TO HAPPEN (Syed, 22 September 2026: "app names overlap
   when we zoom from 100% to 125%").

   This rule has always asked for a two-line clamp, and the clamp has never done anything: the base
   .dk-tile-name above sets `white-space: nowrap`, which this did not reset, and text that cannot
   wrap cannot be clamped to two lines of anything. So every name was one unbreakable line as wide
   as the words needed, and the longest ones simply ran into the tile beside them.

   Measured rather than judged by eye: "QUIRE Documents" wants 107px, and a column is 83px at ten
   across, 86 at eight, 96 at six. It spilled at EVERY width - zoom did not cause it, it only made
   the columns narrow enough to notice. Syed circled "QUIRE Documents / QUIRE Sheets" and
   "NETFIVE Students / Connected apps", which are exactly the two longest names in their rows.

   overflow-wrap: anywhere for the other end of it - one long word with no space in it, which no
   amount of wrapping can break without being told it may. */
.dk-tiles .dk-tile-name {
  max-inline-size: 100%; font-size: 12px; font-weight: 600; line-height: 1.25; color: var(--dk-ink);
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  white-space: normal; overflow-wrap: anywhere;

  /* Room for two lines whether or not a name needs them, so a row of tiles keeps one baseline and
     the grid does not step up and down as somebody renames an app. */
  min-block-size: 2.5em;
}

/* Add app: the same square with a dashed edge and a grey plus, and its name in grey beneath. */
.dk-tiles .dk-tile-add .dk-glyph {
  background: transparent; border: 1px dashed color-mix(in srgb, var(--dk-line-strong) 70%, var(--dk-ink-3)); color: var(--dk-ink-3);
}
.dk-tiles .dk-tile-add .dk-glyph .dk-icon { inline-size: 26px; block-size: 26px; }
.dk-tiles .dk-tile-add .dk-tile-name { font-weight: 400; color: var(--dk-ink-2); }
/* Customise's × and pen sit on the corners of the square, not of the column it is centred in. */
.dk-tiles .dk-tile-x { inset-block-start: -6px; inset-inline-end: calc(50% - 38px - 10px); }
.dk-tiles .dk-tile-icon { inset-block-start: -6px; inset-inline-start: calc(50% - 38px - 10px); }
.dk-editing .dk-tile { animation: dk-wiggle 1.6s ease-in-out infinite; cursor: grab; }

/* Moving a tile earlier or later in Customise, for a keyboard or a screen reader, beside drag and drop. */
.dk-tile-moves { display: flex; justify-content: center; gap: 4px; margin-block-start: 4px; }
.dk-tile-move {
  inline-size: 26px; block-size: 22px; border-radius: var(--dk-r-s); border: 1px solid var(--dk-line-soft);
  background: var(--dk-surface); color: var(--dk-ink-2); font-size: var(--fs-sm); line-height: 1; cursor: pointer;
}
.dk-tile-move:disabled { opacity: .35; cursor: default; }
.dk-tile-x {
  position: absolute; inset-block-start: -6px; inset-inline-end: -6px; inline-size: 20px; block-size: 20px;
  border-radius: 50%; border: 0; background: var(--dk-crit); color: #fff; font-size: 13px; line-height: 1;
  display: grid; place-items: center; cursor: pointer; box-shadow: var(--dk-shadow-1);
}

/* ---- the right-hand column (Syed's shell design, 19 September) ------------------------------------
   320px, full height, its own ground and a hairline on its left, so it reads as a panel. The day and
   its week as a quiet header; Up next, Notifications and Recent conversations as cards; Handoff as one
   muted line at its foot. It lies under the windows, as the old Today panel did, so a window can
   still use the whole desk. */

.dk-today {
  position: absolute; inset-block: 0; inset-inline-end: 0; z-index: 2; inline-size: 340px;
  padding: 22px 18px; overflow-y: auto; overflow-x: hidden;
  display: flex; flex-direction: column; gap: 12px;
  background: var(--dk-rail);
  border-inline-start: 1px solid var(--dk-line);
  animation: dk-slide 200ms var(--ease) both;
}
.dk-today > * { flex-shrink: 0; }
@media (max-width: 1080px) { .dk-today { display: none; } }

.dk-rail-head { display: flex; align-items: baseline; gap: 7px; padding: 0 2px; }
.dk-rail-head b { font-size: 13px; font-weight: 600; color: var(--dk-ink); }
.dk-rail-head span { margin-inline-start: auto; font-size: 11.5px; color: var(--dk-ink-3); }

.dk-card {
  background: var(--dk-surface); border: 1px solid var(--dk-line); border-radius: 12px; padding: 12px;
  display: grid; gap: 0; align-content: start; line-height: 1.35;
  /* A grid column grows to its widest line, and a conversation title never wraps (Syed, 15 September). */
  grid-template-columns: minmax(0, 1fr); min-inline-size: 0;
}
.dk-card-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-block-end: 10px; min-inline-size: 0; }

/* ---- "AI usage this cycle" (Syed's NETFIVE-AI-Handoff, 20 September 2026) -------------------
 *
 * The handoff's own values: an 8px track, green for the lowest-cost models and indigo for the
 * advanced ones. Those two are NOT the account's accent and must not become it - somebody on the
 * ruby accent would otherwise get a red half and a red bar, and the split would stop being
 * readable as a split. Red is the logo, Ask and Send; this is a measurement.
 *
 * The bar is aria-hidden in the markup and the legend carries the percentages as text, so nothing
 * here is colour-only and a screen reader gets the numbers rather than two coloured spans.
 */
.dk-usage { gap: 10px; }
.dk-usage-figure { display: flex; align-items: baseline; gap: 6px; min-inline-size: 0; }
.dk-usage-figure b { font-size: 15px; font-weight: 800; letter-spacing: -.01em; }
.dk-usage-figure span { font-size: 12px; color: var(--dk-muted); }

.dk-usage-bar { display: flex; overflow: hidden; block-size: 8px; border-radius: 999px; background: var(--dk-track, #f1f2f5); }
.dk-usage-bar > span { display: block; }

.dk-usage-key { display: grid; gap: 6px; margin: 0; padding: 0; list-style: none; font-size: 12px; }
.dk-usage-key li { display: flex; align-items: center; gap: 8px; min-inline-size: 0; }
.dk-usage-key b { font-weight: 700; font-variant-numeric: tabular-nums; }
.dk-usage-name { flex: 1 1 auto; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dk-usage-dot { inline-size: 8px; block-size: 8px; border-radius: 999px; flex: 0 0 auto; }

/* Both halves from the desk's palette, which is where the pair is now defined and where it changes.
 * The dark values come with the tokens, so there is no second rule here to keep in step - the old
 * pair of theme overrides is how a colour ends up changed in one theme and not the other. */
.dk-usage-economy  { background: var(--dk-usage-economy); }
.dk-usage-advanced { background: var(--dk-usage-advanced); }
:root[data-theme="dark"] .dk-usage-bar { background: #23262d; }
.dk-card-lbl { margin: 0; display: flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 600; letter-spacing: normal; color: var(--dk-ink); }
.dk-card-all { font-size: 11.5px; flex-shrink: 0; }
.dk-card-text { margin: 0; font-size: 12.5px; color: var(--dk-ink-2); overflow-wrap: anywhere; }
/* How many are unread, in the bell's red, softly. */
.dk-card-count {
  padding: 0 6px; border-radius: 999px; font-size: 10.5px; font-weight: 600; line-height: 1.6;
  background: color-mix(in srgb, #E11D33 13%, transparent); color: #B91C1C;
}
[data-theme="dark"] .dk-card-count { color: #FCA5A5; }
.dk-card-foot {
  display: flex; align-items: center; gap: 10px; margin-block-start: 10px; padding-block-start: 9px;
  border-block-start: 1px solid var(--dk-line-soft); font-size: 11.5px;
}
.dk-link-quiet { color: var(--dk-ink-3); }

.dk-card-list { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 4px; }
.dk-today .dk-btn { white-space: normal; }
.dk-card-row, .dk-note-row {
  inline-size: calc(100% + 8px); margin: -2px -4px; padding: 3px 4px; display: flex; align-items: center; gap: 8px;
  border: 0; border-radius: 6px; background: transparent; text-align: start;
  font-size: 12.5px; color: var(--dk-ink); cursor: pointer; min-inline-size: 0;
}
.dk-card-row:hover, .dk-note-row:hover { background: var(--dk-surface-3); }
.dk-card-row .dk-icon { inline-size: 14px; block-size: 14px; color: var(--dk-ink-3); }
.dk-card-when { flex-shrink: 0; font-size: 11px; color: var(--dk-ink-3); white-space: nowrap; }

/* ---- Up next: one timeline of what is left today — appointments and tasks together, the next thing first
   (NETFIVE-Home-Handoff-v3 §4.8, 20 September 2026). The time keeps a column of its own so the titles line up. ---- */
.dk-next-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.dk-next-row {
  display: flex; align-items: center; gap: 10px; min-inline-size: 0;
  padding: 8px 10px; border-radius: 12px; font-size: 13px;
}
/* The next thing, tinted, with its time in the accent — the one row somebody is looking for. */
.dk-next-row.is-next { background: var(--accent-soft); }
.dk-next-row.is-next .dk-next-time { color: var(--accent); font-weight: 700; }
.dk-next-time {
  flex-shrink: 0; inline-size: 64px; white-space: nowrap; font-size: 12px; color: var(--dk-ink-2);
}
.dk-next-what { min-inline-size: 0; display: grid; gap: 1px; }
.dk-next-what b { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dk-next-meta { font-size: 12px; color: var(--dk-ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dk-next-row input { inline-size: 14px; block-size: 14px; margin: 0; flex-shrink: 0; accent-color: var(--dk-ink); cursor: pointer; }
.dk-next-row.is-done { opacity: .55; }
.dk-next-row.is-done .dk-next-what { text-decoration: line-through; color: var(--dk-ink-2); }
/* A task among the appointments says so, quietly. */
.dk-next-tag {
  flex-shrink: 0; padding: 1px 6px; border-radius: 6px;
  background: var(--dk-surface-3); color: var(--dk-ink-2); font-size: 10.5px; font-weight: 600;
}
/* Join is the darkest thing on the panel: it is the one row that is about to start without you. */
.dk-next-join {
  flex-shrink: 0; block-size: 30px; padding: 0 12px; border: 0; border-radius: 8px;
  background: var(--dk-ink); color: var(--dk-surface); font: inherit; font-size: 12.5px; font-weight: 700; cursor: pointer;
}
.dk-next-join:hover { background: color-mix(in srgb, var(--dk-ink) 88%, #FFFFFF); }
.dk-next-none { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--dk-ink-2); }
.dk-next-none .dk-icon { inline-size: 14px; block-size: 14px; color: var(--dk-ink-3); }
.dk-next-start {
  inline-size: 100%; block-size: 32px; margin-block-start: 10px; gap: 7px;
  background: var(--dk-surface); font-size: 12.5px; font-weight: 500;
}
.dk-next-start .dk-icon { inline-size: 14px; block-size: 14px; }
.dk-next-lbl {
  margin-block: 12px 8px; padding-block-start: 10px; border-block-start: 1px solid var(--dk-line-soft);
  font-size: 10px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--dk-ink-3);
}
.dk-next-tasks { list-style: none; margin: 0; padding: 0; display: grid; gap: 7px; }
.dk-next-tasks li { display: flex; align-items: center; gap: 9px; font-size: 12.5px; min-inline-size: 0; }
.dk-next-tasks input { inline-size: 14px; block-size: 14px; margin: 0; flex-shrink: 0; accent-color: var(--accent); cursor: pointer; }
.dk-next-tasks li.is-done { opacity: .55; }
.dk-next-tasks li.is-done .dk-grow { text-decoration: line-through; color: var(--dk-ink-2); }
.dk-pri-high {
  padding: 1px 6px; border-radius: 999px; flex-shrink: 0;
  background: var(--accent-soft); color: var(--accent); font-size: 10.5px; font-weight: 600;
}

/* ---- Notifications: one row for repeats, a time on each, failures marked ---- */
.dk-note-row { align-items: flex-start; cursor: default; }
.dk-note-row.is-unread { background: color-mix(in srgb, var(--dk-ink) 3.5%, transparent); }
.dk-note-open {
  flex: 1; min-inline-size: 0; display: flex; align-items: flex-start; gap: 8px; padding: 0;
  border: 0; background: transparent; color: inherit; font: inherit; text-align: start; line-height: 1.4; cursor: pointer;
}
.dk-note-open .dk-grow { white-space: normal; overflow-wrap: anywhere; }
.dk-note-row:not(.is-unread) .dk-note-open { color: var(--dk-ink-2); }
/* The dot is the colour of the app the notification opens in while it is unread; read, it greys. */
.dk-note-dot { inline-size: 7px; block-size: 7px; margin-block-start: 6px; border-radius: 50%; flex-shrink: 0; background: var(--dk-app, var(--accent)); }
[data-theme="dark"] .dk-note-dot { background: color-mix(in srgb, var(--dk-app, var(--accent)) 65%, #FFFFFF); }
.dk-note-row:not(.is-unread) .dk-note-dot { background: var(--dk-line-strong); }
.dk-note-warn { display: grid; margin-block-start: 1px; color: var(--dk-warn); }
.dk-note-warn .dk-icon { inline-size: 14px; block-size: 14px; }
.dk-note-act { flex-shrink: 0; font-size: 12px; font-weight: 500; line-height: 1.4; }
.dk-note-count {
  flex-shrink: 0; padding: 0 5px; border-radius: 999px; font-size: 10.5px; font-weight: 600; line-height: 1.6;
  background: var(--dk-surface-3); color: var(--dk-ink-2);
}
.dk-note-row .dk-card-when { line-height: 1.6; }

/* ---- the foot: what is coming, in one muted line ---- */
.dk-rail-foot { margin-block-start: auto; display: flex; flex-wrap: wrap; gap: 8px; padding: 8px 2px 0; }
.dk-rail-soon { display: flex; align-items: center; gap: 8px; font-size: 11.5px; color: var(--dk-ink-2); }
.dk-rail-soon .dk-icon { inline-size: 13px; block-size: 13px; color: var(--dk-ink-3); }
.dk-soon {
  font-size: 10.5px; font-weight: 400; color: var(--dk-ink-3); background: transparent;
  border: 1px solid var(--dk-line-strong); border-radius: 999px; padding: 1px 6px;
}

/* ---- windows ----------------------------------------------------------------- */

/* Their own layer, so a window's z-index is counted among windows only and can never
   rise above the snap preview, the dialog or the toast. */
.dk-wins { position: absolute; inset: 0; z-index: 3; pointer-events: none; }

.dk-win {
  position: absolute; pointer-events: auto; display: flex; flex-direction: column; min-inline-size: 280px;
  background: var(--dk-surface); border: 1px solid var(--dk-line); border-radius: var(--dk-r);
  box-shadow: var(--dk-shadow-2); overflow: hidden; animation: dk-win-in 200ms var(--ease) both;
}
/* The front window: a stronger edge and a bolder name, so it is marked by more than colour (a11y review). */
.dk-win.is-focused {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--dk-line));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 55%, transparent), var(--dk-shadow-2);
}
.dk-win.is-focused .dk-win-name { font-weight: 700; }
.dk-win:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.dk-win-max { inset: 0; border-radius: 0; border: 0; }
.dk-win-hidden { display: none; }

.dk-win-bar {
  flex-shrink: 0; block-size: 34px; display: flex; align-items: center; gap: 3px; padding: 0 5px;
  background: var(--dk-surface-2); border-block-end: 1px solid var(--dk-line-soft); cursor: grab; user-select: none;
}
.dk-win.is-focused .dk-win-bar { background: color-mix(in srgb, var(--accent) 5%, var(--dk-surface-2)); }
.dk-win-max .dk-win-bar { cursor: default; }
.dk-win-name { font-size: var(--fs-sm); font-weight: 500; margin-inline-start: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-inline-size: 0; }
.dk-win-btn {
  inline-size: 24px; block-size: 24px; flex-shrink: 0; display: grid; place-items: center; border: 0; border-radius: 6px;
  background: transparent; color: var(--dk-ink-2); cursor: pointer; text-decoration: none;
}
.dk-win-btn:hover:not(:disabled) { background: color-mix(in srgb, var(--dk-ink) 9%, transparent); color: var(--dk-ink); }
.dk-win-btn:disabled { opacity: .3; cursor: default; }
.dk-win-close:hover:not(:disabled) { background: var(--dk-crit); color: #fff; }
.dk-win-body { position: relative; flex: 1; min-block-size: 0; overflow: auto; background: var(--dk-surface); container-type: inline-size; }
.dk-win-resize { position: absolute; inset-block-end: 0; inset-inline-end: 0; inline-size: 18px; block-size: 18px; cursor: nwse-resize; }
.dk-win-resize::after {
  content: ""; position: absolute; inset-block-end: 4px; inset-inline-end: 4px; inline-size: 7px; block-size: 7px;
  border-inline-end: 2px solid var(--dk-line); border-block-end: 2px solid var(--dk-line);
}
@container (max-width: 470px) { .dk-win-name { display: none; } }

.dk-frame { position: absolute; inset: 0; }
.dk-frame-page { display: block; inline-size: 100%; block-size: 100%; border: 0; background: transparent; }
.dk-frame-loading { position: absolute; inset: 0; display: grid; place-items: center; color: var(--dk-ink-3); font-size: var(--fs-sm); pointer-events: none; }
/* Over a window's page when that page is the sign-in or access-denied page: the window says what happened instead. */
.dk-frame-cover { position: absolute; inset: 0; z-index: 1; display: grid; place-items: center; padding: var(--sp-3); background: var(--dk-surface); }
.dk-tab-card { display: grid; gap: var(--sp-3); justify-items: center; text-align: center; max-inline-size: 360px; margin: var(--sp-6) auto; }

.dk-snap {
  position: absolute; z-index: 8; pointer-events: none; border-radius: var(--dk-r);
  border: 2px dashed var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent);
}
/* The pill at the top of the desk. It once announced full screen as well; that banner was removed on
   25 September (Syed) and what is left is the keyboard move and resize hint, which is a MODE somebody
   is in and has to be told they can leave. The name stayed to keep one class for one shape. */
.dk-solobar {
  position: absolute; z-index: 20; inset-block-start: 8px; left: 50%; translate: -50% 0;
  display: flex; align-items: center; gap: 8px; padding: 4px 6px 4px 14px; border-radius: 999px;
  background: var(--dk-ink); color: var(--dk-surface); font-size: var(--fs-sm); box-shadow: var(--dk-shadow-2);
}
.dk-solobar .dk-btn-quiet { color: var(--dk-surface); }

.dk-limit {
  position: absolute; inset: 0; z-index: 85; display: grid; place-items: center;
  background: color-mix(in srgb, var(--dk-ground) 62%, transparent);
}
.dk-limit-card {
  inline-size: min(430px, calc(100% - 32px)); display: grid; gap: var(--sp-3); padding: 20px;
  background: var(--dk-surface); border: 1px solid var(--dk-line); border-radius: 14px; box-shadow: var(--dk-shadow-2);
}
.dk-limit-card h2 { margin: 0; font-size: var(--fs-lg); }
.dk-limit-card p { margin: 0; color: var(--dk-ink-2); font-size: var(--fs-sm); }
.dk-meter { block-size: 6px; border-radius: 3px; background: var(--dk-surface-3); overflow: hidden; }
.dk-meter > span { display: block; block-size: 100%; background: var(--dk-warn); }

.dk-toast {
  position: absolute; z-index: 80; inset-block-end: 12px; left: 50%; translate: -50% 0;
  padding: 7px 14px; border-radius: 999px; background: var(--dk-ink); color: var(--dk-surface);
  font-size: var(--fs-sm); box-shadow: var(--dk-shadow-2); animation: dk-win-in 200ms var(--ease) both;
}

/* ---- taskbar and launcher -------------------------------------------------------- */

.dk-taskbar {
  flex-shrink: 0; block-size: 40px; display: flex; align-items: center; gap: 6px; padding: 0 8px;
  background: var(--dk-bar); border-block-start: 1px solid var(--dk-line);
  position: relative; z-index: 40;
}
.dk-start {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px; border: 0; border-radius: 8px;
  background: var(--accent-fill); color: var(--on-accent); font-size: var(--fs-sm); font-weight: 600; cursor: pointer; white-space: nowrap;
}
.dk-start:hover { background: var(--accent-fill-hover); }
.dk-tasks { flex: 1; min-inline-size: 0; display: flex; align-items: center; gap: 4px; overflow-x: auto; scrollbar-width: none; }
.dk-task {
  display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border: 0; border-radius: 8px;
  background: transparent; color: var(--dk-ink-2); font-size: var(--fs-sm); cursor: pointer; white-space: nowrap; max-inline-size: 180px;
}
.dk-task:hover:not(:disabled) { background: color-mix(in srgb, var(--dk-ink) 7%, transparent); color: var(--dk-ink); }
.dk-task:disabled { opacity: .5; cursor: default; }
.dk-task[aria-pressed="true"] { background: var(--dk-surface); color: var(--dk-ink); box-shadow: var(--dk-shadow-1); }
.dk-task.is-min { opacity: .6; }
.dk-task-name { overflow: hidden; text-overflow: ellipsis; }

/* THE SEARCH PALETTE (Syed's shell design, 19 September). On the desk its field lies exactly over the bar's search
   box and the results hang under it, the bar left undimmed; on the phone it is a box near the top, as before. */
.dk-launch-scrim { position: fixed; inset: 0; z-index: 200; background: rgba(7, 14, 13, .28); }
.dk-launch-scrim.is-anchored { background: linear-gradient(to bottom, transparent 56px, rgba(7, 14, 13, .28) 56px); }
.dk-launch {
  inline-size: min(576px, calc(100% - 24px)); margin: 72px auto 0; padding: 8px; display: grid; gap: 4px;
  background: var(--dk-surface); color: var(--dk-ink); border: 1px solid var(--dk-line); border-radius: 14px; box-shadow: var(--dk-shadow-2);
}
.dk-launch.is-anchored { position: fixed; margin: 0; }
.dk-launch-field {
  display: flex; align-items: center; gap: 8px; block-size: 40px; padding: 0 10px; margin-block-end: 4px;
  border: 1px solid var(--dk-field); border-radius: 9px; background: var(--dk-surface-2); color: var(--dk-ink-3);
}
.dk-launch.is-anchored .dk-launch-field { position: absolute; margin: 0; background: var(--dk-surface); }
.dk-launch-field:focus-within {
  border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
}
.dk-launch-field > .dk-icon { inline-size: 15px; block-size: 15px; color: var(--accent); }
.dk-launch-input {
  flex: 1; min-inline-size: 0; padding: 0; border: 0; outline: none;
  background: transparent; color: var(--dk-ink); font: inherit; font-size: 13.5px;
}
.dk-launch-field .dk-kbd, .dk-launch-go .dk-kbd, .dk-launch-foot .dk-kbd {
  font-family: inherit; font-size: 10.5px; border-width: 1px; border-radius: 5px; padding: 1px 6px; background: var(--dk-surface-2);
}
.dk-launch-list { max-block-size: min(420px, calc(100vh - 180px)); overflow: auto; display: grid; gap: 1px; }
.dk-launch-group {
  padding: 8px 8px 4px; font-size: 10.5px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--dk-ink-3);
}
.dk-launch-item {
  display: flex; align-items: center; gap: 10px; inline-size: 100%; padding: 6px 8px; border: 0; border-radius: 8px;
  background: transparent; color: var(--dk-ink); text-align: start; font-size: 13.5px; cursor: pointer; min-inline-size: 0;
}
.dk-launch-item.is-active { background: color-mix(in srgb, var(--accent) 9%, var(--dk-surface)); }
.dk-launch-item .dk-hint { flex-shrink: 0; font-size: 11.5px; }
.dk-launch-ico {
  inline-size: 28px; block-size: 28px; border-radius: 7px; display: grid; place-items: center; flex-shrink: 0;
  background: var(--dk-surface-3); color: var(--dk-ink-2);
}
.dk-launch-ico .dk-icon { inline-size: 15px; block-size: 15px; }
.dk-launch-ico-ai { background: var(--accent-soft); color: var(--accent); }
.dk-launch-go { display: flex; align-items: center; gap: 6px; flex-shrink: 0; font-size: 11.5px; color: var(--dk-ink-3); }
.dk-launch-foot {
  display: flex; align-items: center; gap: 14px; margin-block-start: 4px; padding: 9px 8px 2px;
  border-block-start: 1px solid var(--dk-line-soft); font-size: 11.5px; color: var(--dk-ink-3);
}
.dk-launch-foot .dk-kbd { margin-inline-end: 3px; }

/* ---- the library ------------------------------------------------------------------ */

.dk-lib { padding: 12px; display: grid; gap: 12px; align-content: start; }
.dk-tabs { display: flex; flex-wrap: wrap; gap: 2px; border-block-end: 1px solid var(--dk-line-soft); }
.dk-tab {
  padding: 8px 10px; border: 0; border-block-end: 2px solid transparent; background: transparent;
  color: var(--dk-ink-2); font-size: var(--fs-sm); cursor: pointer; margin-block-end: -1px;
}
.dk-tab[aria-selected="true"] { color: var(--dk-ink); font-weight: 600; border-block-end-color: var(--accent); }
.dk-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.dk-chip {
  padding: 3px 10px; border-radius: 999px; border: 1px solid var(--dk-line); background: var(--dk-surface);
  color: var(--dk-ink-2); font-size: var(--fs-xs); cursor: pointer;
}
.dk-chip[aria-pressed="true"] { background: var(--accent-fill); border-color: var(--accent); color: var(--on-accent); }
.dk-lib-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 10px; }
.dk-lib-card { display: grid; gap: 8px; align-content: start; }

/* ---- what is coming, under what is here (Syed, 22 September 2026) ------------------------------
 *
 * An upcoming card is deliberately QUIETER than a real one and carries no buttons: there is nothing
 * to open, and a tile that looks pressable and does nothing is worse than no tile. Dimmed rather
 * than greyed out, because it is not disabled — it simply does not exist yet.
 */
.dk-lib-soon-head {
  margin: 18px 0 8px; font-size: var(--fs-sm); font-weight: 700; color: var(--dk-ink-2);
}
.dk-lib-card.is-soon { opacity: .72; }
.dk-lib-card.is-soon:hover { opacity: 1; }
/* The initials on a plain tile rather than a drawn icon: an app with no artwork yet should not
   borrow somebody else's, and two letters say as much at this size. */
.dk-lib-soon-tile {
  inline-size: 38px; block-size: 38px; flex-shrink: 0; border-radius: 10px;
  display: inline-flex; align-items: center; justify-content: center;
  color: #fff; font-size: var(--fs-sm); font-weight: 700; letter-spacing: .02em;
}
.dk-mode-soon { color: var(--dk-ink-3); }
.dk-lib-card-head { display: flex; align-items: center; gap: 10px; min-inline-size: 0; }
.dk-lib-card-name { display: grid; min-inline-size: 0; }
.dk-lib-card-name b { font-size: var(--fs-md); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dk-lib-card-name span { font-size: var(--fs-xs); color: var(--dk-ink-3); }
.dk-lib-card-blurb { margin: 0; font-size: var(--fs-sm); color: var(--dk-ink-2); }
.dk-mode { font-size: var(--fs-micro); font-weight: 600; }
.dk-mode-win { color: var(--dk-good); }
.dk-mode-tab { color: var(--dk-warn); }
.dk-lib-card-acts { display: flex; flex-wrap: wrap; gap: 6px; }
.dk-own { display: grid; gap: 12px; }

/* ---- calculator, files (notes and tasks are Syed's, at the end of this file) -------------- */

/*
 * THE CALCULATOR — Syed's calculator-kit V2 (18 September 2026), in the desktop's own palette.
 *
 * It still fills its window and grows with it (Syed, 16 September: "size not readjustable"): the keys share the height
 * and the text scales in container units of .dk-calc. What the kit changed is the hierarchy. Two key surfaces, not
 * three: digits lightest because they are pressed most, everything that acts on them one step darker; operators carry
 * the accent's colour, C and CE a warm tint used nowhere else, and "=" is the one filled key. History and memory dock
 * beside the keys from 620px across and below them under that, so the mode tabs never stop telling the truth.
 */
.dk-calc {
  --cx-danger: var(--dk-crit);
  --cx-danger-soft: color-mix(in srgb, var(--dk-crit) 11%, var(--dk-surface));
  --cx-op: var(--accent);
  container: calc / inline-size;
  block-size: 100%; display: flex; flex-direction: column; outline: none; background: var(--dk-surface);
}
/* The accent is a dark blue in both themes; on a dark key it all but disappears, so dark mixes it toward the ink. */
[data-theme="dark"] .dk-calc { --cx-op: color-mix(in srgb, var(--accent) 35%, var(--dk-ink)); }
.dk-calc:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.dk-calc-grow { flex: 1; }

/* The modes, a rule, and history's one entry point: separated, so History cannot read as a fourth mode. */
.dk-calc-bar { display: flex; align-items: center; gap: 10px; padding: 0 10px; border-block-end: 1px solid var(--dk-line-soft); }
.dk-calc-modes { display: flex; gap: 2px; min-inline-size: 0; }
.dk-calc-modes .dk-tab {
  padding: 10px 11px; border: 0; border-block-end: 2px solid transparent; margin-block-end: -1px; border-radius: 0;
  background: transparent; color: var(--dk-ink-2); font-size: var(--fs-md); cursor: pointer; white-space: nowrap;
}
.dk-calc-modes .dk-tab:hover { color: var(--dk-ink); }
.dk-calc-modes .dk-tab[aria-selected="true"] { color: var(--dk-ink); font-weight: 600; border-block-end-color: var(--accent); }
.dk-calc-rule { inline-size: 1px; block-size: 18px; margin-inline-start: auto; background: var(--dk-line-soft); }
.dk-calc-panelbtn {
  display: grid; place-items: center; padding: 5px 7px; border: 1px solid transparent; border-radius: 7px;
  background: transparent; color: var(--dk-ink-2); cursor: pointer;
}
.dk-calc-panelbtn:hover { background: var(--dk-surface-2); color: var(--dk-ink); }
.dk-calc-panelbtn[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent); }

.dk-calc-body { flex: 1; min-block-size: 0; display: flex; flex-direction: column; }
.dk-calc-main { flex: 1 1 auto; min-block-size: 0; min-inline-size: 0; display: flex; flex-direction: column; gap: 8px; padding: 10px 12px 8px; }

/* ---- the display: the sum so far over the number being typed ---- */
.dk-calc-screen {
  display: flex; flex-direction: column; justify-content: flex-end; gap: 1px; min-block-size: 88px; padding: 7px 12px 10px;
  background: var(--dk-surface-2); border: 1px solid var(--dk-line-soft); border-radius: 8px;
}
.dk-calc-meta { display: flex; gap: 5px; min-block-size: 17px; }
.dk-calc-chip {
  padding: 1px 6px; border-radius: 4px; background: var(--dk-surface-3); color: var(--dk-ink-3);
  font-family: var(--mono); font-size: 9.5px; font-weight: 600; letter-spacing: .07em;
}
.dk-calc-chip.is-memory { background: var(--accent-soft); color: var(--accent); }
/* Clipped from the left, so the newest part of a long sum stays in sight. */
.dk-calc-expr {
  min-block-size: 18px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; direction: rtl; unicode-bidi: plaintext;
  text-align: end; font-family: var(--mono); font-size: clamp(12px, 3.4cqi, 16px); color: var(--dk-ink-3);
}
/* One line, always: a long number steps its size down rather than wrapping. */
.dk-calc-result {
  display: block; overflow: hidden; white-space: nowrap; text-align: end; line-height: 1.16;
  font-size: clamp(28px, 9cqi, 48px); font-weight: 600; font-variant-numeric: tabular-nums; color: var(--dk-ink);
}
.dk-calc-result.fit-md { font-size: clamp(22px, 7cqi, 38px); }
.dk-calc-result.fit-sm { font-size: clamp(18px, 5.6cqi, 30px); }
.dk-calc-result.fit-xs { font-size: clamp(14px, 4.4cqi, 24px); }
.dk-calc-result.is-error { font-size: clamp(15px, 4.4cqi, 20px); color: var(--cx-danger); }

/* ---- memory: a slim strip where a whole row of keys used to be ---- */
.dk-calc-mem { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 3px; }
.dk-calc-mem button {
  block-size: 26px; padding: 0; border: 0; border-radius: 5px; background: transparent; color: var(--dk-ink-2);
  font: inherit; font-size: 11.5px; font-weight: 600; cursor: pointer;
}
.dk-calc-mem button:hover:not(:disabled) { background: var(--dk-surface-2); color: var(--dk-ink); }
/* Disabled still has to be readable: grey on grey was the kit's complaint. */
.dk-calc-mem button:disabled { color: var(--dk-ink-3); cursor: default; }
.dk-calc-mem .is-stored { font-weight: 500; font-size: 11px; }

/* ---- scientific: the angle is a mode, and looks like one; 2nd shows when it is on ---- */
.dk-calc-strip { display: flex; align-items: center; gap: 6px; }
.dk-calc-seg { display: inline-flex; gap: 2px; padding: 2px; border-radius: 7px; background: var(--dk-surface-3); }
.dk-calc-seg button {
  padding: 4px 10px; border: 0; border-radius: 5px; background: transparent; color: var(--dk-ink-3); cursor: pointer;
  font: inherit; font-size: 11px; font-weight: 600; letter-spacing: .05em;
}
.dk-calc-seg button:hover { color: var(--dk-ink); }
.dk-calc-seg button[aria-pressed="true"] { background: var(--dk-surface); color: var(--dk-ink); box-shadow: 0 1px 2px rgba(0, 0, 0, .12); }
.dk-calc-toggle, .dk-calc-chipkey {
  block-size: 28px; min-inline-size: 34px; padding: 0 9px; border: 1px solid var(--dk-line-soft); border-radius: 7px;
  background: var(--dk-surface-2); color: var(--dk-ink-2); font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.dk-calc-chipkey { font-family: var(--mono); }
.dk-calc-toggle:hover, .dk-calc-chipkey:hover { background: var(--dk-surface-3); color: var(--dk-ink); }
.dk-calc-toggle[aria-pressed="true"] { background: var(--accent-fill); border-color: transparent; color: var(--on-accent); }

/* ---- the keys ----

   THE ROWS HAVE NO FLOOR, AND THAT IS THE POINT (Syed, 21 September 2026: "Overlapping on 0 and (.),
   +- button does not work when click in between the button only works when clicked on the borders,
   equals to button does not work properly only the very first time").

   All three of those were one bug. The rows used to be `minmax(34px, 1fr)`: a floor. The pad is a flex
   child with `min-block-size: 0`, so its BOX shrinks with the window, but its CONTENT could not shrink
   below six rows of 34 plus the gaps - 234px, whatever the box was. Overflow is visible here, so the
   last rows simply painted outside the pad, over the keyboard hint below it. The hint is a later
   sibling and non-positioned, so IT painted on top, and being real text it takes the clicks: the part
   of the bottom row it covers is dead, the rest of the row still works. Hence a button that "only
   works when clicked on the borders".

   The equals key looked intermittent for the same reason. The result line changes font size as a
   number grows, which changes the screen's height, which moves the whole column and so moves which
   part of the bottom row the hint is sitting on. Nothing about equals was ever wrong.

   This was not an edge case: the window's own minimum is 420px tall (deskRegistry), which leaves the
   pad far less than 234px, so every calculator at or near its smallest size had it.

   minmax(0, 1fr) lets the rows share exactly the height the pad has. Tall windows are unchanged -
   1fr already made the rows fill - and short ones now fit instead of bleeding. A key can end up
   shorter than 34px, which is the honest outcome: a small key you can press beats a comfortable one
   you cannot. If they should never get that small, the answer is a taller minHeight on the app, not a
   floor here that the box cannot honour. */
.dk-calc-keys {
  flex: 1; min-block-size: 0; display: grid; gap: 6px;
  grid-template-columns: repeat(4, minmax(0, 1fr)); grid-auto-rows: minmax(0, 1fr);
}
.is-scientific .dk-calc-keys { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 5px; grid-auto-rows: minmax(0, 1fr); }
.dk-calc-keys.is-converter { grid-auto-rows: minmax(0, 1fr); }
.dk-calc-key {
  display: grid; place-items: center; min-inline-size: 0; padding: 0; border: 1px solid var(--dk-line-soft); border-radius: 7px;
  background: var(--dk-surface-2); color: var(--dk-ink-2); box-shadow: 0 1px 0 rgba(0, 0, 0, .04);
  font: inherit; font-size: clamp(14px, 4.4cqi, 22px); font-weight: 500; cursor: pointer;
  transition: background 90ms ease, border-color 90ms ease;
}
.dk-calc-key:active { transform: translateY(.5px); }
/* Inline, not a grid: a grid would put <sup> on its own row and every exponent would read as a subscript. */
.dk-calc-face { line-height: 1.1; white-space: nowrap; }
.dk-calc-face sup { font-size: .62em; vertical-align: super; line-height: 1; }
.dk-calc-key.is-digit { background: var(--dk-surface); color: var(--dk-ink); font-weight: 600; font-size: clamp(15px, 4.8cqi, 24px); }
.dk-calc-key.is-digit:hover { background: var(--dk-surface-2); }
.dk-calc-key.is-fn:hover { background: var(--dk-surface-3); color: var(--dk-ink); }
.dk-calc-key.is-op { color: var(--cx-op); font-weight: 600; font-size: clamp(16px, 5.2cqi, 26px); }
.dk-calc-key.is-op:hover { background: var(--accent-soft); border-color: var(--accent-line); }
.dk-calc-key.is-danger { background: var(--cx-danger-soft); border-color: transparent; color: var(--cx-danger); font-weight: 600; }
.dk-calc-key.is-danger:hover { border-color: var(--cx-danger); }
.dk-calc-key.is-accent { background: var(--accent-fill); border-color: transparent; color: var(--on-accent); font-weight: 700; font-size: clamp(16px, 5.2cqi, 26px); }
.dk-calc-key.is-accent:hover { background: var(--accent-fill-hover); }
.dk-calc-key.is-wide { grid-column: span 2; }
/* Dark's two lowest surfaces are all but the same black; the keys that act on the digits step up one, so the pad
   still reads as two grounds. */
[data-theme="dark"] .dk-calc-key.is-fn, [data-theme="dark"] .dk-calc-key.is-op { background: var(--dk-surface-3); }
.is-scientific .dk-calc-key { font-size: clamp(13px, 3.6cqi, 19px); }
.is-scientific .dk-calc-key.is-digit { font-size: clamp(14px, 4cqi, 21px); }

.dk-calc-kbd { margin: 0; text-align: center; font-size: 11px; line-height: 1.5; color: var(--dk-ink-3); }
.dk-calc-kbd kbd {
  padding: 1px 4px; border: 1px solid var(--dk-line-soft); border-radius: 4px; background: var(--dk-surface-2);
  font-family: var(--mono); font-size: 10px;
}

/* ---- history and memory ---- */
.dk-calc-side {
  flex: 0 0 42%; min-block-size: 0; display: flex; flex-direction: column;
  border-block-start: 1px solid var(--dk-line-soft); background: var(--dk-surface);
}
.dk-calc-sidehead { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-block-end: 1px solid var(--dk-line-soft); }
.dk-calc-pills { display: inline-flex; gap: 3px; padding: 2px; border-radius: 999px; background: var(--dk-surface-3); }
.dk-calc-pills button {
  padding: 4px 11px; border: 0; border-radius: 999px; background: transparent; color: var(--dk-ink-3);
  font: inherit; font-size: 12px; cursor: pointer;
}
.dk-calc-pills button[aria-selected="true"] { background: var(--dk-surface); color: var(--dk-ink); font-weight: 600; }
.dk-calc-close {
  margin-inline-start: auto; padding: 2px 7px; border: 0; border-radius: 5px; background: transparent;
  color: var(--dk-ink-3); font-size: 17px; line-height: 1; cursor: pointer;
}
.dk-calc-close:hover { background: var(--dk-surface-2); color: var(--dk-ink); }
.dk-calc-list { flex: 1; min-block-size: 0; overflow: auto; list-style: none; margin: 0; padding: 6px; display: grid; align-content: start; gap: 2px; }
.dk-calc-entry {
  inline-size: 100%; display: flex; flex-direction: column; align-items: flex-end; gap: 2px; padding: 7px 9px;
  border: 0; border-radius: 7px; background: transparent; color: var(--dk-ink); font: inherit; cursor: pointer;
}
.dk-calc-entry:hover { background: var(--dk-surface-2); }
.dk-calc-entry-sum {
  max-inline-size: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; direction: rtl; unicode-bidi: plaintext;
  font-family: var(--mono); font-size: 11px; color: var(--dk-ink-3);
}
.dk-calc-entry-value { font-size: 16px; font-weight: 600; font-variant-numeric: tabular-nums; }
.dk-calc-sideempty { display: flex; flex-direction: column; gap: 6px; padding: 26px 18px; text-align: center; }
.dk-calc-sideempty h4 { margin: 0; font-size: 13.5px; font-weight: 600; color: var(--dk-ink); }
.dk-calc-sideempty p { margin: 0; font-size: 12px; line-height: 1.55; color: var(--dk-ink-2); }
.dk-calc-sidefoot { display: flex; gap: 6px; padding: 8px; border-block-start: 1px solid var(--dk-line-soft); }
.dk-calc-ghost {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 11px; border: 1px solid var(--dk-line-soft); border-radius: 7px;
  background: var(--dk-surface); color: var(--dk-ink-2); font: inherit; font-size: 12px; font-weight: 500; cursor: pointer;
}
.dk-calc-ghost:hover:not(:disabled) { border-color: var(--dk-line); color: var(--dk-ink); }
.dk-calc-ghost[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent); }
.dk-calc-ghost:disabled { color: var(--dk-ink-3); cursor: default; }

@container calc (min-width: 620px) {
  .dk-calc.has-panel .dk-calc-body { flex-direction: row; }
  .dk-calc-side { flex: 0 0 clamp(220px, 36%, 300px); border-block-start: 0; border-inline-start: 1px solid var(--dk-line-soft); }
}
/* At the narrowest a window allows, the three modes and the history button still sit on one line. */
@container calc (max-width: 340px) {
  .dk-calc-modes .dk-tab { padding-inline: 6px; font-size: var(--fs-xs); }
  .dk-calc-bar { gap: 6px; padding-inline: 6px; }
}

/* ---- the converter ---- */
.dk-calc-convert { flex: 1; min-block-size: 0; display: flex; flex-direction: column; gap: 9px; }
.dk-calc-field { display: flex; flex-direction: column; gap: 4px; }
.dk-calc-label { font-size: 10px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--dk-ink-3); }
.dk-calc-pair { position: relative; display: flex; flex-direction: column; gap: 6px; }
.dk-calc-card {
  display: flex; flex-direction: column; gap: 4px; padding: 7px 10px 9px;
  background: var(--dk-surface-2); border: 1px solid var(--dk-line-soft); border-radius: 8px;
}
.dk-calc-card.is-result { background: var(--accent-soft); border-color: var(--accent-line); }
.dk-calc-cardrow { display: flex; align-items: center; gap: 8px; }
.dk-calc-amount {
  flex: 1; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: clamp(20px, 7cqi, 30px); font-weight: 600; font-variant-numeric: tabular-nums; color: var(--dk-ink);
}
input.dk-calc-amount { padding: 0; border: 0; background: transparent; font-family: inherit; outline: none; }
input.dk-calc-amount:focus-visible { box-shadow: 0 2px 0 var(--accent); }
.dk-calc-amount.fit-md { font-size: clamp(18px, 5.6cqi, 25px); }
.dk-calc-amount.fit-sm { font-size: clamp(15px, 4.6cqi, 21px); }
.dk-calc-amount.fit-xs { font-size: clamp(13px, 3.8cqi, 17px); }
.dk-calc-amount.is-error { color: var(--cx-danger); font-size: clamp(15px, 4.4cqi, 19px); }
/* Swap on the divider between the two fields — where the eye already is, not among the digits. */
.dk-calc-swap {
  position: absolute; inset-inline-end: 12px; inset-block-start: 50%; translate: 0 -50%; z-index: 2;
  display: grid; place-items: center; inline-size: 30px; block-size: 30px; border-radius: 999px;
  border: 1px solid var(--dk-line-soft); background: var(--dk-surface); color: var(--dk-ink-2); cursor: pointer;
  box-shadow: var(--dk-shadow-1);
}
.dk-calc-swap:hover { border-color: var(--accent-line); color: var(--accent); }
.dk-calc-tools { display: flex; align-items: center; gap: 8px; }
/* A native select underneath, so the keyboard, screen readers and the phone's picker all keep working. */
.dk-calc-select { position: relative; display: inline-flex; align-items: center; flex: 0 1 auto; min-inline-size: 0; }
.dk-calc-select::after {
  content: ""; position: absolute; inset-inline-end: 10px; inline-size: 6px; block-size: 6px; pointer-events: none;
  border-inline-end: 1.5px solid var(--dk-ink-3); border-block-end: 1.5px solid var(--dk-ink-3); rotate: 45deg; translate: 0 -2px;
}
.dk-calc-select select {
  appearance: none; -webkit-appearance: none; inline-size: 100%; max-inline-size: 190px; padding: 7px 28px 7px 10px;
  border: 1px solid var(--dk-line-soft); border-radius: 7px; background: var(--dk-surface); color: var(--dk-ink);
  font: inherit; font-size: 12.5px; cursor: pointer;
}
.dk-calc-select select:hover { border-color: var(--dk-line); }
.dk-calc-field .dk-calc-select select { max-inline-size: none; }

.dk-files { padding: 12px; display: grid; gap: 10px; align-content: start; }
/* One column of settings, centred in the window rather than pinned to its left edge (Syed, 19 September). */
.dk-files-intro { display: grid; gap: 10px; inline-size: 100%; max-inline-size: 640px; margin-inline: auto; }
.dk-files-intro p { margin: 0; color: var(--dk-ink-2); font-size: var(--fs-sm); }
.dk-files-title { margin: 0; font-size: var(--fs-lg); font-weight: 650; color: var(--dk-ink); }

/* The connect screen (Syed, 16 September): a drop area, where the next folder goes, then the folders kept. */
.dk-connect {
  display: grid; justify-items: center; gap: 6px; padding: 22px 16px; text-align: center; cursor: pointer;
  border: 1.5px dashed var(--dk-line-strong); border-radius: var(--dk-r); background: var(--dk-surface-2);
}
.dk-connect:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--dk-line-strong)); }
.dk-connect.is-over { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--dk-surface-2)); }
.dk-connect-ico { inline-size: 40px; block-size: 40px; color: var(--dk-ink-2); }
.dk-connect-title { font-size: var(--fs-md); color: var(--dk-ink); }
.dk-connect-note { font-size: var(--fs-sm); color: var(--dk-ink-2); }
.dk-connect .dk-btn-primary { margin-block-start: 6px; }
.dk-connect-opts { display: flex; flex-wrap: wrap; gap: 10px 28px; }
/* Where the next folder goes is asked inside the card that connects it, under a line, so it reads as options for this
   folder and not for the ones already listed (Syed, 19 September). The card browses on a click; the options do not. */
.dk-connect .dk-connect-opts {
  justify-self: stretch; justify-content: center; margin-block-start: 10px; padding-block-start: 12px;
  border-block-start: 1px solid var(--dk-line-soft); text-align: start; cursor: default;
}
.dk-connect-opt { display: flex; align-items: flex-start; gap: 8px; cursor: pointer; }
.dk-connect-opt input { inline-size: 16px; block-size: 16px; margin: 2px 0 0; accent-color: var(--accent); }
.dk-connect-opt b { display: block; font-size: var(--fs-sm); color: var(--dk-ink); font-weight: 600; }
.dk-connect-opt span span { font-size: var(--fs-xs); color: var(--dk-ink-3); }
.dk-folders-head { display: flex; align-items: center; gap: 8px; padding-block-start: 10px; border-block-start: 1px solid var(--dk-line-soft); }
.dk-folders-head h3 { margin: 0; font-size: var(--fs-md); color: var(--dk-ink); }
.dk-files-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.dk-crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; min-inline-size: 0; font-size: var(--fs-sm); color: var(--dk-ink-2); }
.dk-crumb-step { display: inline-flex; align-items: center; gap: 2px; min-inline-size: 0; }
.dk-crumb {
  background: none; border: 0; padding: 4px 6px; border-radius: var(--dk-r-s); font: inherit; color: var(--accent);
  cursor: pointer; max-inline-size: 22ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dk-crumb:hover { background: var(--dk-surface-2); text-decoration: underline; }
.dk-crumb-sep { color: var(--dk-ink-3); }
.dk-crumb-here { padding: 4px 6px; font-weight: 600; color: var(--dk-ink); max-inline-size: 22ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dk-table-wrap { overflow-x: auto; }
.dk-table { inline-size: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.dk-table th { text-align: start; font-weight: 600; color: var(--dk-ink-3); padding: 6px 8px; border-block-end: 1px solid var(--dk-line-soft); white-space: nowrap; }
.dk-table td { padding: 6px 8px; border-block-end: 1px solid var(--dk-line-soft); white-space: nowrap; }

/* A file open in the Files window: a head, then the editor or the picture filling the rest. */
.dk-file { display: flex; flex-direction: column; block-size: 100%; min-block-size: 0; }
.dk-file-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px 12px; border-block-end: 1px solid var(--dk-line-soft); }
.dk-file-name { font-weight: 600; font-size: var(--fs-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-inline-size: 0; }
.dk-file > .dk-note-box, .dk-file > .dk-error { margin: 8px 12px 0; }
.dk-file-text {
  flex: 1; min-block-size: 160px; border: 0; padding: 12px 14px; resize: none; background: transparent; color: var(--dk-ink);
  font-family: var(--mono); font-size: var(--fs-sm); line-height: 1.55; tab-size: 4; white-space: pre; overflow: auto;
}
.dk-file-text:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; background: color-mix(in srgb, var(--accent) 3%, transparent); }
.dk-file-img { flex: 1; min-block-size: 0; display: grid; place-items: center; padding: 12px; overflow: auto; }
.dk-file-img img { max-inline-size: 100%; max-block-size: 100%; object-fit: contain; }
/* A PDF's pages in the window, with the same tools as a PDF opened from a conversation. */
.dk-file-pdf { flex: 1; min-block-size: 0; display: flex; flex-direction: column; }
/* A file NETFIVE cannot show: what it is, and Download. */
.dk-file-none { flex: 1; display: grid; align-content: center; justify-items: center; gap: 8px; padding: 24px; text-align: center; }
.dk-file-none p { margin: 0; max-inline-size: 46ch; }
.dk-file-type { color: var(--dk-ink-2); }
.dk-newtype { inline-size: auto; max-inline-size: 280px; }
.dk-newfile-name { margin: 0; }

/* ---- inside a connected folder, to Syed's design (atrium-file-browser, 19 September 2026) --------------------------
   A toolbar (Up, the crumbs, search, list or grid, Add files, New), the selection's own bar in place of the column
   heads, rows with an icon for what each is and their actions on the right, a dashed place to drop files below them,
   and a status bar. The desk's own colours, so light, dark and every accent follow; the design's indigo is the accent. */
/* The measure (Syed, 19 September): in a maximised window the list stretched edge to edge, a name at one side and its
   size 1,700px away at the other. What is in the folder, and what is on the toolbar and the status bar, is held to
   1100px and centred; below that nothing changes, above it the extra width is even margin. The window is its own
   container, so every narrow rule below asks how wide the window is, never the screen. */
.fb {
  position: relative; display: flex; flex-direction: column; block-size: 100%; min-block-size: 0; background: var(--dk-surface); color: var(--dk-ink);
  container: fb / inline-size; --fb-measure: 1100px;
}
.fb-grow { flex: 1; }
.fb-glyph { inline-size: 16px; block-size: 16px; flex: 0 0 auto; display: block; }
/* The toolbar and the status bar are the window's own and run edge to edge; what is on them keeps to the measure, with
   the rows' own inset, so Up stands over the list's first column and New over its last. */
.fb-bar { padding-block: 10px; border-block-end: 1px solid var(--dk-line-soft); }
.fb-measure {
  display: flex; align-items: center; gap: 8px; flex-wrap: nowrap;
  inline-size: 100%; max-inline-size: var(--fb-measure); margin-inline: auto; padding-inline: 14px 12px;
}
.fb-icon {
  inline-size: 30px; block-size: 30px; display: grid; place-items: center; flex: 0 0 auto;
  border: 0; border-radius: var(--dk-r-s); background: transparent; color: var(--dk-ink-2); cursor: pointer;
}
.fb-icon:hover { background: color-mix(in srgb, var(--dk-ink) 7%, transparent); color: var(--dk-ink); }
.fb-crumbs { display: flex; align-items: center; gap: 2px; min-inline-size: 0; flex: 1 1 auto; flex-wrap: nowrap; overflow: hidden; font-size: var(--fs-sm); }
.fb-crumb-step { display: inline-flex; align-items: center; gap: 2px; min-inline-size: 0; flex: 0 1 auto; }
/* Long names give way before the toolbar does, after the search box has: each crumb shortens with an ellipsis and keeps
   its full name as its title, and All folders, the way back, never shortens. */
.fb-crumbs > .fb-crumb:first-child { flex-shrink: 0; }
.fb-crumbs .fb-crumb, .fb-crumbs .fb-crumb-here { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; }
.fb-crumb {
  border: 0; background: transparent; padding: 4px 6px; border-radius: var(--dk-r-s); color: var(--dk-ink-2);
  font: inherit; cursor: pointer; white-space: nowrap;
}
.fb-crumb:hover { color: var(--dk-ink); background: color-mix(in srgb, var(--dk-ink) 6%, transparent); }
.fb-crumb-sep { color: var(--dk-ink-3); padding-inline: 2px; }
.fb-crumb-here { padding: 4px 6px; font-weight: 650; color: var(--dk-ink); max-inline-size: 24ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fb-search { position: relative; display: flex; align-items: center; flex: 0 6 200px; min-inline-size: 120px; }
.fb-search .fb-glyph { position: absolute; inset-inline-start: 10px; inline-size: 14px; block-size: 14px; color: var(--dk-ink-3); pointer-events: none; }
.fb-search input {
  inline-size: 100%; block-size: 34px; padding: 0 10px 0 31px; border-radius: var(--dk-r-s);
  border: 1px solid var(--dk-field); background: var(--dk-surface); color: var(--dk-ink); font: inherit; font-size: var(--fs-sm);
}
.fb-search input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.fb-seg { display: flex; gap: 2px; padding: 2px; border: 1px solid var(--dk-line); border-radius: var(--dk-r-s); background: var(--dk-surface-2); }
.fb-seg button {
  inline-size: 30px; block-size: 28px; display: grid; place-items: center; border: 0; border-radius: 6px;
  background: transparent; color: var(--dk-ink-3); cursor: pointer;
}
.fb-seg button[aria-pressed="true"] { background: var(--dk-surface); color: var(--dk-ink); box-shadow: var(--dk-shadow-1); }
.fb-add, .fb-new, .fb-paste { block-size: 34px; }
/* Paste appears only while something is waiting, so it must not shove the rest of the toolbar about when it does: it
   takes its width from its own label and gives way before Add files and New do. A cut is drawn with the accent, since
   pasting it empties the folder it came from and that is worth seeing before the click. */
.fb-paste { flex: 0 0 auto; }
.fb-paste.fb-cut { border-color: color-mix(in srgb, var(--accent) 45%, var(--dk-line)); color: var(--accent); }
.fb-new .fb-glyph:last-child { inline-size: 12px; block-size: 12px; opacity: .85; }
.fb-menuwrap { position: relative; display: inline-flex; }
.fb-menu {
  position: absolute; inset-inline-end: 0; inset-block-start: calc(100% + 6px); z-index: 20; min-inline-size: 210px; padding: 5px;
  display: grid; background: var(--dk-surface); border: 1px solid var(--dk-line); border-radius: var(--dk-r); box-shadow: var(--dk-shadow-2);
}
.fb-menu [role="menuitem"] {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px; border: 0; border-radius: var(--dk-r-s);
  background: transparent; color: var(--dk-ink); font: inherit; font-size: var(--fs-sm); text-align: start; cursor: pointer; white-space: nowrap;
}
.fb-menu [role="menuitem"]:hover, .fb-menu [role="menuitem"]:focus-visible { background: color-mix(in srgb, var(--accent) 9%, transparent); }
.fb-menu [role="menuitem"] .fb-glyph { color: var(--dk-ink-2); inline-size: 15px; block-size: 15px; }
.fb-sep { block-size: 1px; background: var(--dk-line-soft); margin: 4px 6px; }
.fb-danger, .fb-menu .fb-danger { color: var(--dk-crit); }

.fb-notes { display: grid; gap: 6px; padding: 10px 14px 0; inline-size: 100%; max-inline-size: var(--fb-measure); margin-inline: auto; }
.fb-notes > * { margin: 0; }

/* The columns, defined once for the heads and every row (Syed, 19 September): SIZE and MODIFIED ran together as one
   phrase, and a scroll bar in the list alone moved the values out from under their heads. The heads now sit in the
   list, on this same template, with 16px between columns. A folder with no dates at all has no Modified column. */
.fb-list { --fb-cols: 40px minmax(0, 1fr) 110px 150px 76px; }
.fb-list.is-undated { --fb-cols: 40px minmax(0, 1fr) 110px 76px; }
.fb-head, .fb-row {
  display: grid; grid-template-columns: var(--fb-cols); align-items: center; column-gap: 16px;
  padding-inline: 14px 12px;
}
.fb-head { block-size: 36px; border-block-end: 1px solid var(--dk-line-soft); background: var(--dk-surface-2); }
/* The heads, or in their place while something is selected what can be done to all of it, stay in sight as the list
   scrolls under them. */
.fb-list > .fb-head, .fb-list > .fb-bulk { position: sticky; inset-block-start: 0; z-index: 2; }
.fb-sort {
  display: inline-flex; align-items: center; gap: 4px; border: 0; background: transparent; padding: 0; cursor: pointer;
  font: inherit; font-size: var(--fs-micro, 11px); font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--dk-ink-2);
}
.fb-sort .fb-glyph { inline-size: 12px; block-size: 12px; }
/* Size is right-aligned, so its arrow goes before the word and the word ends where the numbers end. */
.fb-col-size { justify-self: end; flex-direction: row-reverse; }
.fb-check-cell { display: grid; place-items: center; }
.fb-check { inline-size: 16px; block-size: 16px; accent-color: var(--accent); cursor: pointer; margin: 0; }
.fb-bulk {
  display: flex; align-items: center; gap: 6px; min-block-size: 40px; padding: 4px 14px;
  background: color-mix(in srgb, var(--accent) 9%, var(--dk-surface)); border-block-end: 1px solid color-mix(in srgb, var(--accent) 22%, var(--dk-line-soft));
  font-size: var(--fs-sm);
}
.fb-bulk-count { color: var(--accent); font-weight: 650; }
.fb-bulk-sep { inline-size: 1px; block-size: 18px; background: color-mix(in srgb, var(--accent) 25%, var(--dk-line)); margin-inline: 6px; }
.fb-bulk-btn {
  display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; border: 0; border-radius: var(--dk-r-s);
  background: transparent; color: var(--accent); font: inherit; font-weight: 550; cursor: pointer;
}
.fb-bulk-btn:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.fb-bulk-btn .fb-glyph { inline-size: 14px; block-size: 14px; }
.fb-bulk-btn.fb-danger { color: var(--dk-crit); }
.fb-confirm { background: color-mix(in srgb, var(--dk-crit) 8%, var(--dk-surface)); border-color: color-mix(in srgb, var(--dk-crit) 25%, var(--dk-line-soft)); }

/* The scrolling body centres what it holds, and each thing in it keeps to the measure. */
.fb-body { flex: 1; min-block-size: 0; overflow: auto; display: flex; flex-direction: column; align-items: center; }
.fb-body > * { inline-size: 100%; max-inline-size: var(--fb-measure); }
/* Wider than the measure, a scroll bar would pull the list half its width off the toolbar's line; a gutter kept on both
   sides centres the list exactly where the toolbar is centred. Only then, because narrower it would cost the rows. */
@container fb (min-width: 1140px) {
  .fb-body { scrollbar-gutter: stable both-edges; }
}
.fb-quiet { margin: 0; padding: 18px 14px; color: var(--dk-ink-2); font-size: var(--fs-sm); }

/* The rows: an icon for what each is, the name in plain words, then its size, when it changed, and its actions. */
.fb-rows { display: grid; }
.fb-row {
  position: relative; min-block-size: 48px; border-block-end: 1px solid var(--dk-line-soft); cursor: default;
  font-size: var(--fs-sm);
}
.fb-row:hover { background: color-mix(in srgb, var(--dk-ink) 3%, transparent); }
.fb-row.is-on { background: color-mix(in srgb, var(--accent) 7%, var(--dk-surface)); }
.fb-row.is-on::before { content: ""; position: absolute; inset-block: 0; inset-inline-start: 0; inline-size: 2px; background: var(--accent); }
.fb-namecell { display: flex; align-items: center; gap: 10px; min-inline-size: 0; }
.fb-ico {
  inline-size: 30px; block-size: 30px; flex: 0 0 auto; display: grid; place-items: center; border-radius: 8px;
  background: var(--dk-surface-3); color: var(--dk-ink-2);
}
.fb-ico.is-folder { background: color-mix(in srgb, var(--accent) 12%, var(--dk-surface)); color: var(--accent); }
.fb-ico[data-family="picture"] { color: #0e7c86; }
.fb-ico[data-family="pdf"] { color: #c2410c; }
.fb-ico[data-family="table"] { color: #15803d; }
.fb-ico[data-family="slides"] { color: #b45309; }
.fb-ico[data-family="doc"] { color: #1d4ed8; }
.fb-ico.is-large { inline-size: 44px; block-size: 44px; border-radius: 12px; }
.fb-ico.is-large .fb-glyph { inline-size: 20px; block-size: 20px; }
.fb-name {
  min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border: 0; background: transparent; padding: 2px 0;
  color: var(--dk-ink); font: inherit; font-weight: 600; text-align: start; cursor: pointer;
}
.fb-name:hover { text-decoration: underline; text-underline-offset: 3px; }
.fb-size {
  justify-self: end; text-align: end; font-family: var(--mono); font-size: 12px; font-variant-numeric: tabular-nums;
  color: var(--dk-ink-2); white-space: nowrap;
}
.fb-when { justify-self: stretch; color: var(--dk-ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The actions show where the pointer or the keyboard is, on what is selected, and while their menu is open; they are
   always there to be found. Faded rather than removed, so the keyboard still reaches them in order. */
.fb-acts { display: inline-flex; justify-content: flex-end; gap: 4px; opacity: 0; transition: opacity var(--dur-2, .15s) ease; }
.fb-row:hover .fb-acts, .fb-row:focus-within .fb-acts, .fb-row.is-on .fb-acts, .fb-acts:has([aria-expanded="true"]) { opacity: 1; }
.fb-act {
  inline-size: 30px; block-size: 30px; display: grid; place-items: center; border: 1px solid var(--dk-line); border-radius: var(--dk-r-s);
  background: var(--dk-surface); color: var(--dk-ink-2); cursor: pointer; box-shadow: var(--dk-shadow-1);
}
.fb-act:hover { color: var(--dk-ink); background: var(--dk-surface-2); }
.fb-rename { display: grid; gap: 4px; min-inline-size: 0; flex: 1; }
.fb-rename .dk-input { padding: 4px 8px; font-size: var(--fs-sm); }
.fb-rename-error { color: var(--dk-crit); font-size: var(--fs-xs); }

/* The grid: a card for each, the selected one outlined in the accent. */
.fb-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; padding: 14px; }
.fb-card {
  position: relative; display: grid; justify-items: center; align-content: start; gap: 6px; padding: 16px 10px 12px;
  border: 1px solid var(--dk-line); border-radius: var(--dk-r); background: var(--dk-surface); text-align: center; font-size: var(--fs-sm);
}
.fb-card:hover { border-color: var(--dk-line-strong); }
.fb-card.is-on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, var(--dk-surface)); }
.fb-card .fb-name { max-inline-size: 100%; }
.fb-card .fb-size { justify-self: center; }
.fb-card-check, .fb-card-acts { position: absolute; inset-block-start: 6px; opacity: 0; }
.fb-card-check { inset-inline-start: 8px; }
.fb-card-acts { inset-inline-end: 6px; }
.fb-card-acts .fb-acts { opacity: 1; }
.fb-card:hover .fb-card-check, .fb-card:focus-within .fb-card-check, .fb-card.is-on .fb-card-check,
.fb-card:hover .fb-card-acts, .fb-card:focus-within .fb-card-acts, .fb-card-acts:has([aria-expanded="true"]) { opacity: 1; }
/* A touch screen has no hover to find a row's or a card's actions by, so there they are always shown (Syed, 19
   September). After the rules that fade them, so it wins. */
@media (hover: none) {
  .fb-acts, .fb-card-check, .fb-card-acts { opacity: 1; }
}

/* Empty space does work (the design's notes): the rest of the window is where files are dropped. It fills what the
   list leaves, but within bounds (Syed, 19 September: a folder of one row gave it 800px of a maximised window), and
   keeps to the measure, its edges on the rows' own inset. */
.fb-drop {
  flex: 1 1 auto; min-block-size: 120px; max-block-size: 260px; margin: 14px;
  inline-size: calc(100% - 28px); max-inline-size: calc(var(--fb-measure) - 28px);
  display: grid; place-content: center; justify-items: center; gap: 8px;
  border: 1.5px dashed var(--dk-line-strong); border-radius: var(--dk-r); background: transparent;
  color: var(--dk-ink-2); font: inherit; font-size: var(--fs-sm); cursor: pointer;
}
.fb-drop .fb-glyph { inline-size: 20px; block-size: 20px; color: var(--dk-ink-3); }
.fb-drop:hover { border-color: var(--accent); color: var(--dk-ink); }
/* An empty folder: bounded the same way, so it is a panel, not the whole window. */
.fb-empty {
  flex: 1 1 auto; min-block-size: 240px; max-block-size: 420px; margin: 16px;
  inline-size: calc(100% - 32px); max-inline-size: calc(var(--fb-measure) - 32px);
  display: grid; place-content: center; justify-items: center; gap: 8px; padding: 24px; text-align: center;
  border: 1.5px dashed var(--dk-line-strong); border-radius: var(--dk-r); background: color-mix(in srgb, var(--dk-ink) 2%, transparent);
}
.fb-empty b { font-size: var(--fs-lg); color: var(--dk-ink); }
.fb-empty p { margin: 0; max-inline-size: 36ch; color: var(--dk-ink-2); font-size: var(--fs-sm); }
.fb-empty-acts { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-block-start: 8px; }
.fb-status {
  padding-block: 7px; border-block-start: 1px solid var(--dk-line-soft);
  background: var(--dk-surface-2); color: var(--dk-ink-2); font-size: var(--fs-xs);
}
/* Files dragged over the window: the whole of it says where they will go. */
.fb-veil {
  position: absolute; inset: 8px; z-index: 30; display: grid; place-content: center; justify-items: center; gap: 8px;
  border: 2px dashed var(--accent); border-radius: var(--dk-r); background: color-mix(in srgb, var(--accent) 10%, color-mix(in srgb, var(--dk-surface) 85%, transparent));
  color: var(--accent); font-size: var(--fs-md); pointer-events: none;
}
.fb-veil .fb-glyph { inline-size: 26px; block-size: 26px; }

/* A narrow window keeps the list readable: the date goes first, then the size, and the buttons keep only their pictures.
   Asked of the Files window itself (the fb container), since it is a window on the desk and the screen's width says
   nothing about it. Modified goes under 720px rather than squeezing the name (Syed, 19 September). */
/* Paste gives up its word before anything else on the toolbar does, because it is the newcomer: it appears only while
   something is waiting, and in a window this narrow the width it takes comes straight out of the crumbs, which is how
   "Footages For Ai Manqabat" became "fb-…". The icon and its full sentence on the tooltip stay. */
@container fb (max-width: 860px) {
  .fb-paste .fb-label { display: none; }
}
@container fb (max-width: 720px) {
  .fb-list, .fb-list.is-undated { --fb-cols: 40px minmax(0, 1fr) 110px 76px; }
  .fb-when, .fb-col-modified { display: none; }
}
@container fb (max-width: 640px) {
  .fb-list, .fb-list.is-undated { --fb-cols: 34px minmax(0, 1fr) 80px 70px; }
  .fb-label { display: none; }
  .fb-search { flex-basis: 140px; }
}
@container fb (max-width: 420px) {
  .fb-bar { padding-block: 8px; }
  .fb-bar .fb-measure { flex-wrap: wrap; padding-inline: 10px; }
  .fb-list, .fb-list.is-undated { --fb-cols: 30px minmax(0, 1fr) 66px; }
  .fb-size, .fb-col-size { display: none; }
  .fb-search { order: 10; flex-basis: 100%; }
}

/* ---- the phone ------------------------------------------------------------------------ */

/* The phone (Syed, 18 September): the desk's home screen in one column, at the phone's own sizes. The bar keeps the
   mark, the switch, the bell and the avatar, and gives up the words — NETFIVE stays, the person's name goes. */
.dk-phone .dk-bar { gap: 8px; padding: 0 12px; block-size: 52px; }
.dk-phone .dk-brand-mark { inline-size: 24px; block-size: 24px; }
.dk-phone .dk-brand-word { font-size: 15px; }
.dk-phone .dk-avatar-name { display: none; }
.dk-phone-home {
  flex: 1; overflow-x: hidden; overflow-y: auto; padding: 20px 16px 28px; display: flex; flex-direction: column; gap: 18px;
  position: relative; background: var(--dk-ground);
}
.dk-phone-home > * { min-inline-size: 0; flex-shrink: 0; }
.dk-phone .dk-greeting { font-size: 26px; }
.dk-phone .dk-sub { font-size: 14px; }
.dk-phone .dk-heroes { gap: 12px; }
.dk-phone .dk-compare { flex-basis: 100%; }
.dk-phone .dk-section-head { gap: 10px; }
.dk-phone .dk-section-head h2 { font-size: 19px; }
.dk-phone-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(76px, 1fr)); gap: 16px 8px; }
/* The same squircle with the name beneath as the desk's apps, smaller. */
.dk-phone-grid .dk-tile { gap: 7px; padding: 2px; border: 0; background: transparent; min-inline-size: 0; }
.dk-phone-grid .dk-tile:hover { transform: none; box-shadow: none; }
.dk-phone-grid .dk-glyph { inline-size: 58px; block-size: 58px; border-radius: 14px; }
.dk-phone-grid .dk-glyph-sym { background: var(--dk-surface); border: 1px solid var(--dk-line); box-shadow: none; }
[data-theme="dark"] .dk-phone-grid .dk-glyph-sym { background: var(--dk-surface-2); border-color: var(--dk-line-strong); }
.dk-phone-grid .dk-glyph-sym .dk-sym { inline-size: 26px; block-size: 26px; }
.dk-phone-grid .dk-tile-name { font-size: 12.5px; font-weight: 600; color: var(--dk-ink); }
.dk-phone-today { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; }
.dk-phone-app { flex: 1; min-block-size: 0; display: flex; flex-direction: column; }
.dk-phone-head {
  flex-shrink: 0; display: flex; align-items: center; gap: 8px; padding: 4px 8px;
  background: var(--dk-bar); border-block-end: 1px solid var(--dk-line-soft);
}
.dk-phone-body { position: relative; flex: 1; min-block-size: 0; overflow: auto; background: var(--dk-surface); container-type: inline-size; }

@media (max-width: 900px) { .dk-hide-narrow { display: none; } }

/* ---- motion ------------------------------------------------------------------------------ */

@keyframes dk-win-in { from { opacity: 0; transform: translateY(8px) scale(.985); } }
@keyframes dk-slide { from { opacity: 0; transform: translateX(8px); } }
@keyframes dk-wiggle { 0%, 100% { transform: rotate(-.6deg); } 50% { transform: rotate(.6deg); } }
@keyframes dk-pulse { 50% { opacity: .25; } }

@media (prefers-reduced-motion: reduce) {
  .dk *, .dk *::before, .dk *::after { animation: none !important; transition: none !important; }
}

/* ============================================================================
   SYED'S TASKS AND NOTES (15 September 2026)
   His apps.css follows, unchanged: every colour and size in it is a token. On the desktop those tokens
   are the desktop's own, so both apps follow the Atrium palette in light and dark; the names his CSS
   uses that the product does not define (priority hues, spacing, radii, the reading face, timing) are
   defined here, for these two apps only.
   ============================================================================ */
.dk .nf-app {
  --bg: var(--dk-ground);
  --surface: var(--dk-surface-2);
  --surface-2: var(--dk-surface);
  --surface-3: var(--dk-surface-2);
  --surface-4: var(--dk-surface-3);
  --border: var(--dk-line-soft);
  --border-strong: var(--dk-line-strong);
  --text: var(--dk-ink);
  --muted: var(--dk-ink-2);
  --faint: var(--dk-ink-3);
  --ok: var(--dk-good);
  --ok-bg: color-mix(in srgb, var(--dk-good) 14%, var(--dk-surface));
  --warning: var(--dk-warn);
  --warning-bg: color-mix(in srgb, var(--dk-warn) 14%, var(--dk-surface));
  --critical: var(--dk-crit);
  --critical-bg: color-mix(in srgb, var(--dk-crit) 12%, var(--dk-surface));
  /* Syed's priority hues, checked by him for deuteranopia and protanopia; the light high one is a shade
     darker so it reads at 4.5:1 on the desktop's surface. */
  --pri-high: #B4501A;
  --pri-normal: var(--dk-ink-3);
  --pri-low: #1F6FB2;
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --r1: 6px;
  --r2: 8px;
  --r3: 11px;
  --r4: 14px;
  --rf: 999px;
  /* Long-form reading only, the body of a note (Syed, 19 September: tokens.css's --prose-font). */
  --read: 'Source Serif 4', Georgia, 'Times New Roman', serif;
  /* The scrollbars here are drawn on `.dk` now, with the reasoning, so that every desk scroller
     gets them rather than only the two in this app. Nothing to set. */
  --sh-1: var(--dk-shadow-1);
  --sh-2: var(--dk-shadow-1);
  --sh-3: var(--dk-shadow-2);
  --dur: 160ms;
  --ease: cubic-bezier(0.2, 0.8, 0.3, 1);
}

[data-theme="dark"] .dk .nf-app {
  --pri-high: #D8763A;
  --pri-low: #4A95D6;
}

/*
 * Styles for the Tasks and Notes window apps.
 *
 * Assumes the NETFIVE tokens are already on :root (the portal shell sets
 * them; `tokens.css` is the standalone fallback). Nothing here hard-codes
 * a colour, so both apps follow the shell's theme and any theme the
 * customer picks later.
 *
 * Sizing rule: these render inside a draggable window, so every layout
 * decision is made with container queries against `.nf-app`, never with
 * viewport media queries — a 380px window on a 27" monitor must lay out
 * like a phone, and @media cannot see that.
 */

.nf-app {
  container-type: inline-size;
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--surface-2);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--fs-md);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
.nf-app *,
.nf-app *::before,
.nf-app *::after {
  box-sizing: border-box;
}
.nf-app button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}
.nf-app input,
.nf-app textarea,
.nf-app select {
  font: inherit;
  color: inherit;
}
/* Icons are sized here, once. An unsized inline SVG defaults to 300×150,
   which is how a "+" ends up the size of the button it sits in. */
.nf-app svg {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  display: block;
}
.nf-app :focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r1);
}
@media (prefers-reduced-motion: reduce) {
  .nf-app *,
  .nf-app *::before,
  .nf-app *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---------------- toolbar ---------------- */

.nf-bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.nf-bar--wrap {
  flex-wrap: wrap;
}
.nf-search {
  position: relative;
  flex: 1 1 140px;
  min-width: 0;
  display: flex;
  align-items: center;
}
.nf-search svg {
  position: absolute;
  left: 8px;
  width: 13px;
  height: 13px;
  color: var(--faint);
  pointer-events: none;
}
.nf-search input {
  width: 100%;
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-radius: var(--r2);
  padding: 6px 26px 6px 27px;
  font-size: var(--fs-sm);
}
.nf-search input::placeholder {
  color: var(--faint);
}
.nf-search .nf-clear {
  position: absolute;
  right: 5px;
  width: 18px;
  height: 18px;
  border-radius: var(--rf);
  display: grid;
  place-items: center;
  color: var(--faint);
  font-size: 13px;
  line-height: 1;
}
.nf-search .nf-clear:hover {
  background: var(--surface-4);
  color: var(--text);
}

.nf-seg {
  display: flex;
  background: var(--surface-4);
  border-radius: var(--r2);
  padding: 2px;
  gap: 2px;
  flex: 0 0 auto;
}
.nf-seg button {
  font-size: var(--fs-xs);
  padding: 4px 9px;
  border-radius: var(--r1);
  color: var(--muted);
  white-space: nowrap;
  display: flex;
  align-items: center;
  gap: 5px;
}
.nf-seg button:hover {
  color: var(--text);
}
.nf-seg button[aria-pressed='true'] {
  background: var(--surface-2);
  color: var(--text);
  box-shadow: var(--sh-1);
  font-weight: 600;
}
.nf-seg .nf-n {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  font-variant-numeric: tabular-nums;
  color: var(--faint);
}
.nf-seg button[aria-pressed='true'] .nf-n {
  color: var(--accent);
}
.nf-seg button.nf-alarm .nf-n {
  color: var(--critical);
  font-weight: 700;
}

.nf-icbtn {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: var(--r2);
  display: grid;
  place-items: center;
  color: var(--muted);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.nf-icbtn:hover {
  background: var(--surface-4);
  color: var(--text);
}
.nf-icbtn[aria-pressed='true'] {
  background: var(--accent-soft);
  color: var(--accent);
}
.nf-icbtn svg {
  width: 15px;
  height: 15px;
}

.nf-sort {
  flex: 0 0 auto;
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-radius: var(--r2);
  padding: 5px 7px;
  font-size: var(--fs-xs);
  color: var(--muted);
  max-width: 128px;
}

/* ---------------- scroll region + sections ---------------- */

/* The wheel stops here. Without this, reaching the end of the list passed the rest of the gesture
   to whatever was behind it, so the pane under the pointer scrolled sometimes and the window behind
   it scrolled other times, depending only on whether this list had already run out - Syed's
   "sometimes both sections scroll together" (22 September 2026). Measured: at the end of the list,
   one wheel gesture moved the ancestor 300px with `auto` and 0px with `contain`. */
.nf-scroll {
  flex: 1;
  min-height: 0;
  overflow: auto;
  scrollbar-width: thin;
  overscroll-behavior: contain;
}
/* These are inert in Chromium, which ignores ::-webkit-scrollbar-* once the standard
   `scrollbar-width` or `scrollbar-color` is set on the same element - so this thumb colour looked
   like the fix and had never drawn anything. Kept for Safari before 18.2, which has the pseudo
   elements and not the standard properties, and given the same colour as the standard one so the
   two cannot disagree. --border-strong, which was here, measures 1.7:1 on this surface: it would
   have been close to invisible even where it did apply. */
.nf-scroll::-webkit-scrollbar {
  width: 9px;
}
.nf-scroll::-webkit-scrollbar-thumb {
  background: var(--dk-ink-3, var(--border-strong));
  border-radius: var(--rf);
  border: 3px solid transparent;
  background-clip: content-box;
}

.nf-sec {
  padding: 0 var(--sp-3);
}
.nf-sechead {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 9px 0 4px;
  background: linear-gradient(var(--surface-2) 72%, transparent);
  font-size: var(--fs-micro);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
  font-weight: 600;
}
.nf-sechead .nf-n {
  font-family: var(--mono);
  letter-spacing: 0;
  color: var(--faint);
}
.nf-sechead.is-overdue {
  color: var(--critical);
}
.nf-sechead.is-overdue .nf-n {
  color: var(--critical);
}
.nf-sechead .nf-line {
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* ---------------- task row ---------------- */

/* One row = one line. Everything that used to sit on a second line — the
   date chip, tags, the stamp — is now inline and right-aligned, and the
   full text plus both timestamps live in the row's tooltip. Long text
   wraps to a second line and stops there. */
.nf-task {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 4px 8px 4px 7px;
  min-height: 32px;
  border-radius: var(--r2);
  position: relative;
  transition: background var(--dur) var(--ease);
}
.nf-task::before {
  content: '';
  position: absolute;
  left: 0;
  top: 4px;
  bottom: 4px;
  width: 2px;
  border-radius: var(--rf);
  background: transparent;
}
.nf-task.pri-high::before {
  background: var(--pri-high);
}
.nf-task.pri-low::before {
  background: var(--pri-low);
}
.nf-task:hover {
  background: var(--surface-3);
}
.nf-task.is-new {
  animation: nf-in 220ms var(--ease) both;
}
@keyframes nf-in {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
}

.nf-check {
  appearance: none;
  -webkit-appearance: none;
  width: 17px;
  height: 17px;
  margin: 0;
  flex: 0 0 auto;
  border: 1.5px solid var(--border-strong);
  border-radius: 5px;
  background: var(--surface-2);
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.nf-check:hover {
  border-color: var(--accent);
}
.nf-check:checked {
  background: var(--accent);
  border-color: var(--accent);
}
.nf-check:checked::after {
  content: '';
  width: 9px;
  height: 5px;
  border-left: 2px solid var(--on-accent);
  border-bottom: 2px solid var(--on-accent);
  transform: translateY(-1px) rotate(-45deg);
}

.nf-task .nf-body {
  flex: 1 1 auto;
  min-width: 0;
}
.nf-task .nf-text {
  font-size: var(--fs-md);
  line-height: 1.35;
  overflow-wrap: anywhere;
  cursor: text;
  border-radius: var(--r1);
  padding: 1px 3px;
  margin-left: -3px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.nf-task .nf-text:hover {
  background: var(--surface-4);
}
.nf-task.done .nf-text {
  color: var(--faint);
  text-decoration: line-through;
  text-decoration-color: var(--border-strong);
}
.nf-task .nf-edit {
  width: 100%;
  background: var(--surface-2);
  border: 1px solid var(--accent);
  border-radius: var(--r1);
  padding: 2px 5px;
  font-size: var(--fs-md);
}

/*
  ✨ Plan (Syed, 18 September): "a tiny sparkle icon near Smart order … not a giant AI button". A quiet, text-coloured
  button that only takes the accent on hover, a short menu, and a sheet over the list for reviewing what it suggests.
*/
.nf-plan { position: relative; flex: 0 0 auto; }
.nf-planbtn {
  display: inline-flex; align-items: center; gap: 4px; padding: 4px 8px; border: 1px solid transparent;
  border-radius: var(--r2); background: none; color: var(--muted); font: inherit; font-size: var(--fs-sm); cursor: pointer;
}
.nf-planbtn:hover, .nf-planbtn[aria-expanded="true"] { color: var(--accent); border-color: var(--border); }
.nf-planmenu {
  position: absolute; inset-inline-end: 0; top: calc(100% + 4px); z-index: 5; min-inline-size: 220px; padding: 4px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r2); box-shadow: var(--sh-3);
}
.nf-planmenu button {
  display: block; inline-size: 100%; padding: 7px 10px; border: 0; border-radius: 6px; background: none;
  color: var(--text); font: inherit; font-size: var(--fs-sm); text-align: start; cursor: pointer;
}
.nf-planmenu button:hover, .nf-planmenu button:focus-visible { background: var(--surface-3); }
.nf-plannote { margin: 4px 10px 4px; color: var(--muted); font-size: var(--fs-xs); }
.nf-sheet {
  position: absolute; inset: 52px 12px 12px; z-index: 6; display: grid; grid-template-rows: auto minmax(0, 1fr) auto;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r2); box-shadow: var(--sh-3);
}
.nf-sheethead { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-block-end: 1px solid var(--border); font-weight: 600; }
.nf-sheetbody { overflow: auto; padding: 10px 12px; display: grid; align-content: start; gap: 10px; }
.nf-sheetfoot { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-block-start: 1px solid var(--border); }
.nf-sheetfield { display: grid; gap: 4px; font-size: var(--fs-sm); color: var(--muted); }
.nf-sheetfield select, .nf-sheetfield textarea { inline-size: 100%; font: inherit; color: var(--text); }
.nf-sheetwait { display: flex; align-items: center; gap: 6px; margin: 0; color: var(--muted); }
.nf-suggest { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
ol.nf-suggest { counter-reset: none; }
.nf-suggest label { display: flex; align-items: baseline; gap: 8px; padding: 6px 4px; border-radius: 6px; cursor: pointer; }
.nf-suggest label:hover { background: var(--surface-3); }
.nf-suggest small { display: block; color: var(--muted); font-size: var(--fs-xs); }
.nf-suggestpri { flex: 0 0 auto; font-size: var(--fs-xs); font-weight: 600; }
.nf-suggestat { flex: 0 0 auto; font-variant-numeric: tabular-nums; color: var(--muted); font-size: var(--fs-sm); }

/*
  Tasks' row date (Syed, 18 September). The date alone on the row; pointed at or tabbed to, it opens into the full
  record over the row, anchored at the date's right edge so it grows into the row rather than out of it. Over the row
  and not beside it, because .nf-scroll clips anything that leaves it. The short delay stops a card flashing on every
  row the pointer crosses on its way down the list.
*/
.nf-stamp-hold { position: relative; cursor: default; border-radius: 4px; }
.nf-stamp-card {
  position: absolute; inset-inline-end: -6px; top: 50%; transform: translateY(-50%);
  z-index: 2; padding: 4px 8px; border-radius: var(--r2); white-space: nowrap;
  /* The row menu's own surface, border and shadow, so the card reads as the same family in both themes. */
  background: var(--surface-2); color: var(--text); border: 1px solid var(--border);
  box-shadow: var(--sh-3); font-size: var(--fs-xs);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .12s ease, visibility 0s linear .12s;
}
.nf-stamp-hold:hover .nf-stamp-card,
.nf-stamp-hold:focus-visible .nf-stamp-card {
  opacity: 1; visibility: visible;
  transition: opacity .12s ease .25s, visibility 0s linear .25s;
}
.nf-keepword {
  margin-inline-start: 4px; padding: 0 4px; border: 0; border-radius: 4px; background: none;
  color: inherit; opacity: .6; font: inherit; line-height: 1; cursor: pointer;
}
.nf-keepword:hover, .nf-keepword:focus-visible { opacity: 1; background: var(--surface-3); }
.nf-meta {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-xs);
  color: var(--faint);
}
.nf-meta .nf-stamp {
  font-variant-numeric: tabular-nums;
  cursor: help;
  white-space: nowrap;
}
.nf-more {
  font-size: var(--fs-micro);
  color: var(--faint);
  cursor: help;
}
.nf-dot {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--border-strong);
}

.nf-due {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  border-radius: var(--rf);
  padding: 1px 8px 1px 6px;
  font-size: var(--fs-xs);
  font-weight: 600;
  background: var(--surface-4);
  color: var(--muted);
  white-space: nowrap;
}
.nf-due svg {
  width: 11px;
  height: 11px;
}
.nf-due.is-today {
  background: var(--accent-soft);
  color: var(--accent);
}
.nf-due.is-overdue {
  background: var(--critical-bg);
  color: var(--critical);
}
.nf-due.is-done {
  background: transparent;
  color: var(--faint);
  font-weight: 400;
  padding-left: 0;
}

.nf-tag {
  display: inline-flex;
  align-items: center;
  border-radius: var(--rf);
  padding: 1px 8px;
  font-size: var(--fs-xs);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--muted);
  white-space: nowrap;
  cursor: pointer;
}
.nf-tag:hover {
  border-color: var(--accent-line);
  color: var(--accent);
}
.nf-tag.on {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--accent);
  font-weight: 600;
}

.nf-rowacts {
  display: flex;
  align-items: center;
  gap: 1px;
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
}
.nf-task:hover .nf-rowacts,
.nf-task:focus-within .nf-rowacts,
.nf-noterow:hover .nf-rowacts,
.nf-noterow:focus-within .nf-rowacts {
  opacity: 1;
}
.nf-rowacts .nf-icbtn[aria-pressed='true'],
.nf-rowacts .nf-icbtn.is-on {
  opacity: 1;
}
.nf-task .nf-rowacts .nf-icbtn,
.nf-noterow .nf-rowacts .nf-icbtn {
  width: 24px;
  height: 24px;
}
.nf-rowacts .nf-icbtn svg {
  width: 13px;
  height: 13px;
}
.nf-pinned .nf-rowacts .nf-pin {
  opacity: 1;
  color: var(--warning);
}
.nf-del:hover {
  background: var(--critical) !important;
  color: #fff !important;
}

/* priority picker */
.nf-pri {
  position: relative;
}
.nf-primenu {
  position: absolute;
  right: 0;
  top: 26px;
  z-index: 20;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r2);
  box-shadow: var(--sh-3);
  padding: 3px;
  min-width: 132px;
}
.nf-primenu button {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  text-align: left;
  padding: 6px 8px;
  border-radius: var(--r1);
  font-size: var(--fs-sm);
  white-space: nowrap;
}
.nf-primenu button:hover {
  background: var(--surface-3);
}
.nf-primenu i {
  width: 8px;
  height: 8px;
  border-radius: 2px;
  flex: 0 0 auto;
}
.nf-primenu input[type='date'] {
  width: 100%;
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-radius: var(--r1);
  padding: 4px 6px;
  font-size: var(--fs-xs);
}
.nf-primenu .nf-sep {
  height: 1px;
  background: var(--border);
  margin: 3px 2px;
}
.nf-menudel {
  color: var(--critical);
}
.nf-menudel:hover {
  background: var(--critical-bg) !important;
}
.nf-menudel svg {
  width: 12px;
  height: 12px;
}
.nf-primenu h6 {
  margin: 4px 0 2px 8px;
  font-size: var(--fs-micro);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--faint);
}

/* ---------------- quick add ---------------- */

.nf-compose {
  flex: 0 0 auto;
  border-top: 1px solid var(--border);
  background: var(--surface);
  padding: var(--sp-2) var(--sp-3);
}
.nf-composerow {
  display: flex;
  gap: var(--sp-2);
  align-items: center;
}
.nf-composerow input {
  flex: 1;
  min-width: 0;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r2);
  padding: 8px 11px;
  font-size: var(--fs-md);
  transition: border-color var(--dur) var(--ease);
}
.nf-composerow input:focus {
  border-color: var(--accent);
  outline: none;
}
.nf-composerow input::placeholder {
  color: var(--faint);
}
.nf-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 13px;
  border-radius: var(--r2);
  font-size: var(--fs-sm);
  font-weight: 600;
  background: var(--accent-fill);
  color: var(--on-accent);
  white-space: nowrap;
  transition: background var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.nf-btn:hover {
  background: var(--accent-fill-hover);
}
.nf-btn[disabled] {
  opacity: 0.45;
  cursor: default;
}
.nf-btn.ghost {
  background: var(--surface-2);
  color: var(--muted);
  border: 1px solid var(--border);
  font-weight: 500;
}
.nf-btn.ghost:hover {
  background: var(--surface-3);
  color: var(--text);
}
.nf-parsed {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  font-size: var(--fs-xs);
  color: var(--faint);
  min-height: 18px;
}
.nf-hint {
  font-size: var(--fs-xs);
  color: var(--faint);
}
.nf-hint code {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0 4px;
  color: var(--muted);
}

/* ---------------- empty / toast ---------------- */

.nf-empty {
  display: grid;
  place-items: center;
  text-align: center;
  padding: var(--sp-5) var(--sp-4);
  gap: 3px;
  color: var(--faint);
  min-height: 140px;
}
.nf-empty b {
  font-size: var(--fs-md);
  color: var(--muted);
  font-weight: 600;
}
.nf-empty p {
  margin: 0;
  font-size: var(--fs-sm);
  max-width: 34ch;
  line-height: 1.5;
}
.nf-empty svg {
  width: 22px;
  height: 22px;
  color: var(--border-strong);
  margin-bottom: 4px;
}

.nf-toast {
  position: absolute;
  left: 50%;
  bottom: 14px;
  transform: translateX(-50%);
  z-index: 40;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  background: var(--text);
  color: var(--bg);
  border-radius: var(--rf);
  padding: 7px 9px 7px 15px;
  font-size: var(--fs-sm);
  box-shadow: var(--sh-3);
  white-space: nowrap;
  animation: nf-toast-in 180ms var(--ease) both;
}
@keyframes nf-toast-in {
  from {
    opacity: 0;
    transform: translate(-50%, 8px);
  }
}
.nf-toast button {
  font-weight: 700;
  color: var(--bg);
  text-decoration: underline;
  text-underline-offset: 2px;
  padding: 2px 6px;
  border-radius: var(--r1);
}
.nf-toast button:hover {
  background: rgba(127, 127, 127, 0.3);
}

.nf-foot {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 6px var(--sp-3);
  border-top: 1px solid var(--border);
  background: var(--surface);
  font-size: var(--fs-xs);
  color: var(--faint);
}
.nf-foot .nf-sp {
  flex: 1;
}
.nf-prog {
  flex: 0 0 68px;
  height: 4px;
  border-radius: var(--rf);
  background: var(--surface-4);
  overflow: hidden;
}
.nf-prog i {
  display: block;
  height: 100%;
  background: var(--ok);
  border-radius: var(--rf);
  transition: width 260ms var(--ease);
}

/* ---------------- notes: two panes ---------------- */

.nf-split {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 236px 1fr;
}
@container (max-width: 620px) {
  .nf-split {
    grid-template-columns: 1fr;
  }
  .nf-split.show-editor > .nf-list {
    display: none;
  }
  .nf-split:not(.show-editor) > .nf-pane {
    display: none;
  }
}
.nf-backbtn {
  display: none;
}
@container (max-width: 620px) {
  .nf-backbtn {
    display: grid;
  }
}

.nf-list {
  min-width: 0;
  border-right: 1px solid var(--border);
  background: var(--surface);
  display: flex;
  flex-direction: column;
}
.nf-list .nf-scroll {
  padding-bottom: var(--sp-3);
}
.nf-newnote {
  margin: var(--sp-2) var(--sp-3) 0;
}

/* ---------------- notes: the clearer toolbar, and Ask NETFIVE (Syed, 19 September 2026) ---------------- */

/* The one main action of a desk app, filled in the accent. Two classes, because `.nf-app button` (a class and an
   element) outranks a lone `.nf-btn`: that is why New note drew pale, with no fill and the page's own text colour.
   Fixing `.nf-btn` itself would change every desk app's buttons at once, so it is left for Syed to decide. */
.nf-app .nf-primary {
  background: var(--accent-fill);
  color: var(--on-accent);
  font-weight: 600;
  box-shadow: var(--sh-1);
}
.nf-app .nf-primary:hover {
  background: var(--accent-fill-hover);
}
.nf-app .nf-primary[disabled] {
  opacity: 0.45;
  cursor: default;
}
.nf-newnote .nf-primary {
  padding-block: 9px;
}

/* A toolbar button and the menu it opens, below it at its right edge. */
.nf-menuwrap {
  position: relative;
  flex: 0 0 auto;
}
.nf-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  z-index: 30;
  min-width: 210px;
  padding: 4px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r2);
  box-shadow: var(--sh-3);
}
.nf-menu [role^='menuitem'] {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  text-align: left;
  padding: 7px 10px;
  border-radius: var(--r1);
  font-size: var(--fs-sm);
  white-space: nowrap;
}
.nf-menu [role^='menuitem']:hover:not([disabled]),
.nf-menu [role^='menuitem']:focus-visible {
  background: var(--surface-3);
}
.nf-menu [role^='menuitem'][disabled] {
  opacity: 0.45;
  cursor: default;
}
.nf-menu svg {
  width: 13px;
  height: 13px;
  color: var(--muted);
}
.nf-menu .nf-sep {
  height: 1px;
  background: var(--border);
  margin: 4px 2px;
}
.nf-menu .nf-menudel,
.nf-menu .nf-menudel svg {
  color: var(--critical);
}
.nf-menucheck {
  margin-inline-start: auto;
  color: var(--accent);
}
.nf-menuhint {
  margin: 4px 10px 6px;
  max-width: 230px;
  font-size: var(--fs-xs);
  color: var(--faint);
  white-space: normal;
}

/* Ask NETFIVE: small, as Syed asked, and in the accent's soft tint so it reads as the app's AI rather than a view. */
.nf-app .nf-askbtn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 9px 0 10px;
  border: 1px solid var(--accent-line);
  border-radius: var(--r2);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
}
.nf-app .nf-askbtn:hover,
.nf-app .nf-askbtn[aria-expanded='true'] {
  background: color-mix(in srgb, var(--accent) 16%, var(--surface-2));
}
.nf-askbtn svg:last-child {
  width: 11px;
  height: 11px;
  opacity: 0.8;
}

/* Too narrow for words: the buttons keep their pictures, and their names stay as their titles. */
@container (max-width: 380px) {
  .nf-askbtn-label,
  .nf-seg-label {
    display: none;
  }
}

/* The answer, above the note it is about. Nothing in the note changes until a button here is pressed. */
.nf-ask {
  flex: 0 1 auto;
  min-height: 0;
  max-height: 48%;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0 var(--sp-4) var(--sp-3);
  padding: 10px 12px 12px;
  border: 1px solid var(--accent-line);
  border-radius: var(--r3);
  background: color-mix(in srgb, var(--accent-soft) 55%, var(--surface-2));
}
.nf-ask header {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-sm);
  color: var(--accent);
}
.nf-ask header b {
  color: var(--text);
}
.nf-ask header .nf-sp {
  flex: 1;
}
.nf-ask-status {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--fs-sm);
  color: var(--muted);
}
.nf-ask-error {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--critical);
}
.nf-ask-spin {
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid var(--accent-line);
  border-top-color: var(--accent);
  animation: nf-ask-spin 0.8s linear infinite;
}
@keyframes nf-ask-spin {
  to {
    transform: rotate(360deg);
  }
}
.nf-ask-text {
  max-height: 240px;
  overflow: auto;
  padding: 8px 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r2);
}
.nf-ask-items {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 240px;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.nf-ask-items label {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 4px 6px;
  border-radius: var(--r1);
  font-size: var(--fs-sm);
  cursor: pointer;
}
.nf-ask-items label:hover {
  background: var(--surface-3);
}
.nf-ask-items input {
  margin-top: 3px;
  accent-color: var(--accent);
}
.nf-ask-acts {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* ---------------- notes v2, to Syed's design (atrium-notes-v2, 19 September 2026) ----------------
   Scoped to .nf-v2 so Tasks, which shares the nf- rules, is untouched. One accent on a quiet ground; a sidebar with
   its order as a menu, a count, and Pinned and All notes as sections; a note that reads as writing — a large title,
   one honest line under it, and the body in the reading face on a 640px measure; a formatting bar while writing; the
   Ask panel; a status bar; and a screen for when no note is open. */
.nf-v2 .nf-list { background: var(--surface); }
.nf-v2 .nf-listbar { background: transparent; border-bottom: 0; padding-bottom: 4px; }
.nf-v2 .nf-search input { padding-block: 8px; background: var(--surface-2); }
.nf-v2 .nf-newnote .nf-primary { padding-block: 10px; border-radius: var(--r2); }
.nf-sortrow { display: flex; align-items: center; gap: 6px; padding: 10px var(--sp-3) 4px; }
.nf-app .nf-sortbtn {
  display: inline-flex; align-items: center; gap: 4px; padding: 3px 4px; border-radius: var(--r1);
  font-size: var(--fs-sm); color: var(--text);
}
.nf-app .nf-sortbtn:hover { background: var(--surface-3); }
.nf-sortbtn svg { width: 12px; height: 12px; opacity: 0.7; }
.nf-count { font-size: var(--fs-xs); color: var(--faint); }
.nf-menu.is-start { right: auto; left: 0; }
.nf-menutick { inline-size: 12px; color: var(--accent); }
.nf-v2 .nf-tagbar { gap: 5px; overflow-x: auto; background: transparent; border: 0; padding-top: 2px; }
.nf-v2 .nf-notelist { padding: 2px 8px 12px; }
.nf-section {
  margin: 12px 4px 4px; font-size: var(--fs-micro); font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--faint);
}
.nf-v2 .nf-noterow { padding: 9px 10px 8px; border: 1px solid transparent; }
.nf-v2 .nf-noterow.on { background: var(--accent-soft); border-color: color-mix(in srgb, var(--accent) 22%, transparent); }
.nf-v2 .nf-t { display: flex; align-items: center; gap: 6px; font-weight: 600; }
.nf-v2 .nf-t > span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nf-v2 .nf-t-pin { width: 12px; height: 12px; color: var(--accent); }
.nf-v2 .nf-muted { color: var(--faint); font-style: italic; font-weight: 400; }
.nf-v2 .nf-t .nf-muted { font-weight: 500; }

/* The note: a top bar, then the page. */
/* The pane is the window's height and no more: the page scrolls inside it, so the status bar stays in sight. */
.nf-v2 .nf-pane { background: var(--surface-2); min-height: 0; overflow: hidden; }
.nf-v2 .nf-topbar { background: transparent; border-bottom: 1px solid var(--border); padding: 10px var(--sp-4); gap: 8px; }
.nf-v2 .nf-seg button { padding: 5px 11px; font-size: var(--fs-sm); }
.nf-v2 .nf-askbtn { height: 32px; padding: 0 12px; font-size: var(--fs-sm); }

/* The Ask panel: a question of your own, and the actions, below the button. */
.nf-askpanel {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 30; width: min(340px, calc(100cqw - 32px));
  padding: 12px; display: grid; gap: 10px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r3); box-shadow: var(--sh-3);
}
.nf-askpanel-head { font-size: var(--fs-sm); font-weight: 650; }
.nf-askpanel-input {
  width: 100%; padding: 9px 11px; border-radius: var(--r2); border: 1px solid var(--border-strong);
  background: var(--surface); font-size: var(--fs-sm);
}
.nf-askpanel-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.nf-askpanel-list { display: grid; gap: 1px; }
.nf-app .nf-askitem {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; padding: 7px 8px; border-radius: var(--r1);
  font-size: var(--fs-sm);
}
.nf-app .nf-askitem:hover:not([disabled]), .nf-app .nf-askitem:focus-visible { background: var(--surface-3); }
.nf-app .nf-askitem[disabled] { opacity: 0.45; cursor: default; }
.nf-askitem-ico { display: grid; place-items: center; color: var(--muted); }
.nf-askitem-ico svg { width: 14px; height: 14px; }

/* Writing: the formatting bar. */
.nf-formatbar {
  flex: 0 0 auto; display: flex; align-items: center; gap: 2px; padding: 6px var(--sp-4);
  border-bottom: 1px solid var(--border); background: var(--surface);
}
.nf-app .nf-fmt {
  min-width: 30px; height: 28px; padding: 0 7px; border-radius: var(--r1); display: grid; place-items: center;
  color: var(--muted); font-size: var(--fs-sm); font-weight: 600;
}
.nf-app .nf-fmt:hover { background: var(--surface-3); color: var(--text); }
.nf-fmt-b { font-weight: 800; }
.nf-fmt-i { font-style: italic; font-family: var(--read); font-size: 15px; }
.nf-fmt-word { font-family: var(--mono); font-size: var(--fs-xs); font-weight: 500; }
.nf-fmt-sep { width: 1px; height: 18px; margin: 0 6px; background: var(--border); }

/* The page: a title, one line, a rule, and the body on a measure. */
.nf-v2 .nf-doc { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; }
.nf-v2 .nf-measure { width: 100%; max-width: 688px; margin: 0 auto; padding: 28px 24px 24px; flex: 1; display: flex; flex-direction: column; }
.nf-v2 .nf-measure.is-wide { max-width: none; }
.nf-v2 .nf-titlein { padding: 0; font-size: 30px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.2; }
.nf-v2 .nf-titlein::placeholder { color: var(--faint); font-style: italic; }
.nf-v2 .nf-notemeta { padding: 10px 0 0; gap: 6px 8px; font-size: var(--fs-sm); color: var(--muted); }
.nf-meta-text { margin-right: 4px; }
.nf-v2 .nf-tagchip { display: inline-flex; align-items: center; gap: 3px; }
.nf-app .nf-addtag {
  display: inline-flex; align-items: center; gap: 4px; padding: 2px 9px; border-radius: var(--rf);
  border: 1px dashed var(--border-strong); color: var(--muted); font-size: var(--fs-xs);
}
.nf-app .nf-addtag:hover { color: var(--text); border-color: var(--accent); }
.nf-addtag svg { width: 11px; height: 11px; }
.nf-taginput { width: 120px; padding: 2px 9px; border-radius: var(--rf); border: 1px solid var(--accent); background: var(--surface); font-size: var(--fs-xs); }
.nf-rule { height: 1px; background: var(--border); margin: 16px 0 18px; }
.nf-v2 .nf-ask { margin: 0 0 16px; max-height: none; }
.nf-ask-question { margin: 0; font-size: var(--fs-sm); color: var(--muted); font-style: italic; }
.nf-v2 .nf-editor { flex: 1; min-height: 240px; gap: 24px; }
.nf-v2 .nf-editor textarea {
  padding: 0; font-family: var(--read); font-size: 17px; line-height: 1.7; tab-size: 4; color: var(--text);
}
.nf-v2 .nf-editor textarea:focus { outline: none; box-shadow: none; }
.nf-v2 .nf-md { padding: 0; font-size: 17px; line-height: 1.7; overflow: visible; }
.nf-v2 .nf-md:focus { outline: none; }
.nf-v2 .nf-editor.split textarea { border-right: 1px solid var(--border); padding-right: 24px; }
.nf-body-empty { margin: 0; color: var(--faint); font-style: italic; }

/* The status bar: saved or not, and where the caret is while writing. */
.nf-status {
  flex: 0 0 auto; display: flex; align-items: center; gap: 8px; padding: 7px var(--sp-4);
  border-top: 1px solid var(--border); background: var(--surface); font-size: var(--fs-xs); color: var(--muted);
}
.nf-status .nf-sp { flex: 1; }
.nf-v2 .nf-status .nf-saved i { width: 7px; height: 7px; border-radius: 50%; background: var(--ok); }
.nf-v2 .nf-status .nf-saved.dirty i { background: var(--warning); }
.nf-v2 .nf-status .nf-saved.failed { color: var(--critical); }
.nf-v2 .nf-status .nf-saved.failed i { background: var(--critical); }

/* No note open: a quiet page that says what to do, and the keys worth knowing. */
.nf-noselect { flex: 1; display: grid; place-content: center; justify-items: center; gap: 10px; padding: 24px; text-align: center; }
.nf-noselect-badge {
  width: 60px; height: 60px; border-radius: 16px; display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent);
}
.nf-noselect-badge svg { width: 28px; height: 28px; }
.nf-noselect h2 { margin: 6px 0 0; font-size: var(--fs-lg); }
.nf-noselect p { margin: 0; color: var(--muted); font-size: var(--fs-sm); }
.nf-app .nf-btn-lg { padding: 10px 18px; margin-top: 8px; }
.nf-shortcuts { display: flex; flex-wrap: wrap; justify-content: center; gap: 16px; margin-top: 18px; font-size: var(--fs-xs); color: var(--muted); }
.nf-shortcuts kbd {
  display: inline-block; padding: 1px 6px; margin-right: 4px; border: 1px solid var(--border-strong); border-radius: 5px;
  background: var(--surface-2); font-family: var(--mono); font-size: 11px; color: var(--text);
}

@container (max-width: 620px) {
  .nf-v2 .nf-measure { padding: 18px 16px; }
  .nf-v2 .nf-titlein { font-size: 24px; }
}

.nf-noterow {
  display: block;
  position: relative;
  width: 100%;
  text-align: left;
  padding: 9px 10px;
  border-radius: var(--r2);
  cursor: pointer;
  transition: background var(--dur) var(--ease);
}
.nf-noterow:hover {
  background: var(--surface-3);
}
.nf-noterow.on {
  background: var(--accent-soft);
}
.nf-noterow.on::before {
  content: '';
  position: absolute;
  left: 2px;
  top: 10px;
  bottom: 10px;
  width: 2.5px;
  border-radius: var(--rf);
  background: var(--accent);
}
.nf-noterow .nf-t {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: -0.01em;
}
.nf-noterow .nf-t svg {
  width: 11px;
  height: 11px;
  color: var(--warning);
}
.nf-noterow .nf-t span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.nf-noterow.on .nf-t {
  color: var(--accent);
}
.nf-noterow .nf-snip {
  margin: 2px 0 0;
  font-size: var(--fs-xs);
  color: var(--muted);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.4;
  min-height: 1.4em;
}
.nf-noterow .nf-when {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
  font-size: var(--fs-micro);
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}
.nf-noterow .nf-rowacts {
  position: absolute;
  right: 6px;
  top: 6px;
  background: inherit;
}
.nf-mark {
  background: color-mix(in srgb, var(--warning) 34%, transparent);
  border-radius: 2px;
  color: inherit;
}

/* ---------------- notes: editor ---------------- */

.nf-pane {
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--surface-2);
}
.nf-titlein {
  width: 100%;
  border: 0;
  background: transparent;
  padding: var(--sp-4) var(--sp-5) 2px;
  font-size: var(--fs-xl);
  font-weight: 600;
  letter-spacing: -0.02em;
}
.nf-titlein::placeholder {
  color: var(--faint);
}
.nf-titlein:focus {
  outline: none;
}
.nf-notemeta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px 8px;
  padding: 0 var(--sp-5) var(--sp-3);
  font-size: var(--fs-xs);
  color: var(--faint);
}
.nf-saved {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.nf-saved i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ok);
}
.nf-saved.dirty i {
  background: var(--warning);
  animation: nf-pulse 1.3s var(--ease) infinite;
}
@keyframes nf-pulse {
  50% {
    opacity: 0.3;
  }
}

.nf-tagedit {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  padding: 0 var(--sp-5) var(--sp-3);
}
.nf-tagedit .nf-tag {
  gap: 5px;
}
.nf-tagedit .nf-tag button {
  color: var(--faint);
  font-size: 12px;
  line-height: 1;
  padding: 0 1px;
}
.nf-tagedit .nf-tag button:hover {
  color: var(--critical);
}
.nf-tagedit input {
  background: transparent;
  border: 1px dashed var(--border-strong);
  border-radius: var(--rf);
  padding: 2px 9px;
  font-size: var(--fs-xs);
  width: 92px;
  color: var(--muted);
}
.nf-tagedit input:focus {
  border-style: solid;
  border-color: var(--accent);
  outline: none;
}

.nf-editor {
  flex: 1;
  min-height: 0;
  display: flex;
}
.nf-editor.split > * {
  width: 50%;
}
.nf-editor.split .nf-md {
  border-left: 1px solid var(--border);
}
.nf-editor textarea {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  padding: 0 var(--sp-5) var(--sp-5);
  resize: none;
  font-family: var(--mono);
  font-size: var(--fs-sm);
  line-height: 1.7;
  tab-size: 2;
}
.nf-editor textarea:focus {
  outline: none;
}

.nf-md {
  flex: 1;
  min-width: 0;
  overflow: auto;
  padding: 0 var(--sp-5) var(--sp-5);
  font-family: var(--read);
  font-size: var(--fs-lg);
  line-height: 1.65;
  /* `scrollbar-width` does not inherit, so without this the preview drew a 15px system bar beside
     the list's 10px one - two bars of different widths in one window. And the wheel stops here as
     it does in the list. */
  scrollbar-width: thin;
  overscroll-behavior: contain;
}
.nf-md > *:first-child {
  margin-top: 0;
}
.nf-md h1,
.nf-md h2,
.nf-md h3 {
  font-family: var(--font);
  letter-spacing: -0.02em;
  margin: 1.2em 0 0.4em;
  line-height: 1.25;
}
.nf-md h1 {
  font-size: var(--fs-xl);
}
.nf-md h2 {
  font-size: var(--fs-lg);
}
.nf-md h3 {
  font-size: var(--fs-md);
  color: var(--muted);
}
.nf-md p {
  margin: 0 0 0.75em;
}
/* Bold, in a face that HAS a bold.
 *
 * The reading serif is Source Serif 4 and its file stops at weight 600 - measured, not read off
 * the @font-face: the same file declared 400-900 renders 700 and 800 identically to 600, so the
 * axis ends in the font rather than in the CSS. <strong> asks for 700, clamps to 600, and at 40px
 * "Ali Hi" is 101.13px at 400 against 103.34px at 600. Two per cent. Syed: "there is no prominent
 * change in Ali" (22 September 2026).
 *
 * Widening the declaration would have looked like a fix and changed nothing. A heavier subset of
 * the serif would work, but that serif is fetched by every note and the comment above its
 * @font-face is protecting bytes on the wire, not typography. So emphasis is set in the interface
 * face, which is already loaded on any page that has a desk and does have a real 700. Serif text
 * with a sans emphasis is a deliberate pairing rather than an accident; if Syed would rather have
 * the serif's own bold, this rule comes out and a heavier subset goes in. */
.nf-md strong,
.nf-md b {
  font-family: var(--font);
  font-weight: 700;
}
.nf-md ul,
.nf-md ol {
  margin: 0 0 0.75em;
  padding-left: 1.3em;
}
.nf-md li {
  margin-bottom: 0.25em;
}
.nf-md blockquote {
  margin: 0 0 0.75em;
  padding-left: 12px;
  border-left: 2px solid var(--accent-line);
  color: var(--muted);
}
.nf-md code {
  font-family: var(--mono);
  font-size: 0.85em;
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0 4px;
}
.nf-md pre {
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-radius: var(--r2);
  padding: var(--sp-3);
  overflow: auto;
  margin: 0 0 0.75em;
}
.nf-md pre code {
  background: none;
  border: 0;
  padding: 0;
  font-size: var(--fs-xs);
}
.nf-md a {
  color: var(--accent);
  text-underline-offset: 2px;
}
.nf-md hr {
  border: 0;
  border-top: 1px solid var(--border);
  margin: 1.2em 0;
}
.nf-md .nf-cb {
  list-style: none;
  margin-left: -1.3em;
  display: flex;
  align-items: flex-start;
  gap: 8px;
}
.nf-md .nf-cb input {
  margin-top: 5px;
}
.nf-md .nf-cb.checked {
  color: var(--faint);
  text-decoration: line-through;
  text-decoration-color: var(--border-strong);
}

/* ---------------- confirm strip ---------------- */

.nf-confirm {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 7px var(--sp-3);
  background: var(--critical-bg);
  border-top: 1px solid var(--critical);
  font-size: var(--fs-sm);
  color: var(--critical);
}
/* The delete question sits under the toolbar now rather than at the foot of the pane, so its rule
   goes on the edge it meets the document on. The error bar below the app keeps the original. */
.nf-confirm-top {
  flex: 0 0 auto;
  border-top: 0;
  border-bottom: 1px solid var(--critical);
}
.nf-confirm .nf-sp {
  flex: 1;
}
.nf-confirm button {
  font-size: var(--fs-xs);
  font-weight: 600;
  border: 1px solid currentColor;
  border-radius: var(--rf);
  padding: 2px 11px;
}
.nf-confirm .nf-plain {
  border-color: transparent;
  color: var(--muted);
}

/* ---------------- narrow window ---------------- */

@container (max-width: 430px) {
  .nf-bar {
    flex-wrap: wrap;
  }
  .nf-search {
    flex: 1 1 100%;
    order: -1;
  }
  .nf-sort {
    max-width: none;
    flex: 1;
  }
  .nf-titlein,
  .nf-notemeta,
  .nf-tagedit,
  .nf-md,
  .nf-editor textarea {
    padding-left: var(--sp-3);
    padding-right: var(--sp-3);
  }
  .nf-editor.split {
    flex-direction: column;
  }
  .nf-editor.split > * {
    width: 100%;
    height: 50%;
  }
  .nf-editor.split .nf-md {
    border-left: 0;
    border-top: 1px solid var(--border);
  }
}

@container (max-width: 360px) {
  .nf-task .nf-tag,
  .nf-task .nf-more {
    display: none;
  }
}

/* The Tasks window is typically the narrow one. Below ~520px the date and
   the tags earn their place; the "added 2 h ago" stamp does not — it stays
   in the row's tooltip — and dropping it is what keeps a short task on one
   line instead of wrapping. */
@container (max-width: 520px) {
  .nf-task .nf-stamp {
    display: none;
  }
}

/* ============================================================================
   MEETINGS (the MeetApp conversion, 15 September 2026)
   The lobby and panels follow the theme. The stage behind the video stays dark in both themes, as in every video
   call, because faces and screens read better against it. Laid out by container queries, since the page also runs
   inside a desktop window.
   ============================================================================ */
.mt-mount {
  container-type: inline-size;
  position: relative;
  min-block-size: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--fs-md);
}
.mt-grow { flex: 1; min-inline-size: 0; }
.mt-mirror { transform: scaleX(-1); }

.mt-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 9px 16px; border-radius: 10px; border: 1px solid var(--border-strong);
  background: var(--surface); color: var(--text); font: inherit; font-weight: 600; cursor: pointer;
}
.mt-btn:hover { background: var(--surface-3); }
.mt-btn-primary { background: var(--accent-fill); border-color: var(--accent); color: var(--on-accent); }
.mt-btn-primary:hover { background: var(--accent-fill-hover); }
.mt-btn:disabled { opacity: .55; cursor: default; }
.mt-input {
  padding: 9px 12px; border: 1px solid var(--border-strong); border-radius: 10px;
  background: var(--surface); color: var(--text); font: inherit; font-size: var(--fs-md); min-inline-size: 0;
}
.mt-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.mt-code-input { font-family: var(--mono); letter-spacing: .12em; text-transform: uppercase; }
.mt-hint { margin: 0; color: var(--muted); font-size: var(--fs-sm); }
.mt-error { margin: 0; color: var(--critical); font-size: var(--fs-sm); }

/* Devices and the invite link (Syed, 17 September: say why there is no camera, and show the link). */
.mt-trouble { color: var(--text); }
.mt-inline-btn {
  padding: 0; border: 0; background: none; color: var(--accent); font: inherit; text-decoration: underline;
  cursor: pointer;
}
.mt-inline-btn:hover { color: var(--accent-hover); }
.mt-select {
  padding: 9px 12px; border: 1px solid var(--border-strong); border-radius: 10px;
  background: var(--surface); color: var(--text); font: inherit; font-size: var(--fs-sm); inline-size: 100%;
  min-inline-size: 0;
}
.mt-none { margin: 0; color: var(--muted); font-size: var(--fs-sm); }

/* Reactions: they rise over the call and are gone in four seconds, like the noise they stand for. */
.mt-react-wrap { display: flex; }
.mt-react-strip {
  position: absolute; inset-block-end: calc(100% + 8px); inset-inline-start: 50%; transform: translateX(-50%);
  display: flex; gap: 2px; padding: 6px; border-radius: 999px; background: #1F2B2E;
  border: 1px solid rgba(255, 255, 255, .14); box-shadow: 0 8px 24px rgba(0, 0, 0, .45); z-index: 3;
  /* And on a window too narrow even for a centred strip, the emoji go onto a second row. Six of them
     shown small is the point; four shown and two hidden is the bug. */
  flex-wrap: wrap; justify-content: center; max-inline-size: calc(100vw - 16px);
}
.mt-react-btn {
  min-inline-size: 40px; padding: 4px 6px; border: 0; border-radius: 999px; background: none;
  font-size: 22px; line-height: 1.2; cursor: pointer;
}
.mt-react-btn:hover { background: rgba(255, 255, 255, .12); }

/* A poll: the question, the choices, and bars that only appear when the room has sent numbers. */
.mt-poll-q { margin: 0; font-size: var(--fs-md); color: var(--text); font-weight: 600; }
.mt-choices { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.mt-choice { display: grid; gap: 4px; }
.mt-choice-btn {
  inline-size: 100%; padding: 9px 12px; border: 1px solid var(--border-strong); border-radius: 10px;
  background: var(--surface); color: var(--text); font: inherit; text-align: start; cursor: pointer;
}
.mt-choice-btn:hover { border-color: var(--accent); }
.mt-choice-face { display: flex; gap: 8px; align-items: baseline; font-size: var(--fs-sm); color: var(--text); }
.mt-choice-count { color: var(--muted); font-variant-numeric: tabular-nums; }
.mt-choice-bar { block-size: 6px; border-radius: 999px; background: var(--surface-3); overflow: hidden; }
.mt-choice-bar span { display: block; block-size: 100%; background: var(--accent); }
/* The person's own answer, so a closed poll still says what they chose. */
.mt-choice.is-mine .mt-choice-face { font-weight: 600; }
.mt-choice.is-mine .mt-choice-bar span { background: var(--accent-fill-hover); }

/* Breakout rooms. */
.mt-where { background: rgba(143, 156, 232, .3); }
.mt-groups { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.mt-group {
  display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--surface); font-size: var(--fs-sm); color: var(--text);
}
.mt-group.is-mine { border-color: var(--accent); }
.mt-seat { display: flex; align-items: center; gap: 8px; font-size: var(--fs-sm); color: var(--text); }
.mt-seat-pick { inline-size: auto; max-inline-size: 55%; padding: 5px 8px; }
.mt-to-all { font-weight: 600; }

/* The whiteboard, over the tiles the way a shared screen is. */
.mt-board {
  min-inline-size: 0; min-block-size: 0; display: grid; grid-template-rows: auto minmax(0, 1fr);
  background: #0F1A18;
}
.mt-board-tools {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px; padding: 8px 10px;
  background: #16211F; border-block-end: 1px solid rgba(255, 255, 255, .1);
}
.mt-board-gap { inline-size: 10px; }
.mt-ink {
  inline-size: 26px; block-size: 26px; border-radius: 50%; border: 2px solid transparent; cursor: pointer;
  padding: 0;
}
.mt-ink.is-on { border-color: #FFFFFF; }
.mt-nib {
  display: grid; place-items: center; inline-size: 30px; block-size: 26px; border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, .18); background: none; cursor: pointer; padding: 0;
}
.mt-nib.is-on { background: rgba(255, 255, 255, .16); }
.mt-nib span { display: block; border-radius: 50%; background: #E9F2F0; }
/* touch-action: a finger on a phone must draw rather than scroll the page out from under the board. */
.mt-board-paper { inline-size: 100%; block-size: 100%; touch-action: none; cursor: crosshair; display: block; }
/* Watching rather than writing: the pointer says so before anybody tries to draw and finds they cannot
   (Syed, 25 September). Panning still works, so the hand is right for both. */
.mt-board-paper.is-watching { cursor: grab; }
.mt-board-paper.is-watching:active { cursor: grabbing; }
/* Zoomed in, the board can be moved about, and the pointer says so before anybody tries (Syed, 22 September). */
.mt-board-paper.is-zoomed { cursor: grab; }
.mt-board-paper.is-zoomed:active { cursor: grabbing; }
/* The magnification, wide enough for "2.5×" so the toolbar does not shuffle as it changes, and lined up on the
   digits rather than jumping about between 1× and 4×. */
.mt-board-level { min-inline-size: 48px; font-variant-numeric: tabular-nums; }
.mt-board-said { margin: 6px 12px 0; font-size: var(--fs-sm); color: var(--dk-ink-2); }
.mt-reactions { position: absolute; inset: 0; pointer-events: none; overflow: hidden; z-index: 2; }
.mt-reaction {
  position: absolute; inset-block-end: 12px; display: grid; justify-items: center; gap: 2px;
  animation: mt-reaction-rise 4s ease-out forwards;
}
.mt-reaction-face { font-size: 34px; line-height: 1; }
.mt-reaction-name {
  padding: 1px 8px; border-radius: 999px; background: rgba(0, 0, 0, .6); color: #FFFFFF;
  font-size: var(--fs-xs); white-space: nowrap;
}
@keyframes mt-reaction-rise {
  0% { transform: translateY(20px); opacity: 0; }
  15% { transform: translateY(0); opacity: 1; }
  100% { transform: translateY(-220px); opacity: 0; }
}
/* Somebody who asked for less movement still sees it, in place, without the climb. */
@media (prefers-reduced-motion: reduce) {
  .mt-reaction { animation: mt-reaction-fade 4s ease-out forwards; }
  @keyframes mt-reaction-fade { 0%, 80% { opacity: 1; } 100% { opacity: 0; } }
}
/* A long address: it wraps rather than hiding its own end, because reading it is the point. */
.mt-link {
  font-family: var(--mono); font-size: var(--fs-sm); inline-size: 100%; resize: none;
  overflow-wrap: anywhere; line-height: 1.4;
}
.mt-code-big { margin: 0; font-family: var(--mono); font-size: var(--fs-lg); letter-spacing: .12em; color: var(--text); }
.mt-or { margin: 4px 0 0; color: var(--muted); font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .08em; }

/* ---- the lobby ---- */
.mt-lobby { flex: 1; display: grid; place-items: center; padding: 24px; }
.mt-lobby-card {
  inline-size: min(900px, 100%); display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); gap: 24px;
  padding: 24px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 16px; box-shadow: var(--sh-2);
}
.mt-preview { position: relative; aspect-ratio: 16 / 9; background: #0B1012; border-radius: 12px; overflow: hidden; }
.mt-preview video { inline-size: 100%; block-size: 100%; object-fit: cover; display: block; }
.mt-preview-off { position: absolute; inset: 0; display: grid; place-items: center; }
.mt-preview-bar { position: absolute; inset-inline: 0; inset-block-end: 12px; display: flex; justify-content: center; gap: 10px; }
.mt-round {
  inline-size: 44px; block-size: 44px; border-radius: 50%; border: 0; display: grid; place-items: center; cursor: pointer;
  background: rgba(255, 255, 255, .88); color: #0F1A18;
}
.mt-round svg { inline-size: 20px; block-size: 20px; }
.mt-round.is-off { background: #C4382A; color: #fff; }
.mt-round:disabled { opacity: .5; cursor: default; }
.mt-form { display: grid; gap: 12px; align-content: start; }
.mt-title { margin: 0; font-size: var(--fs-xl); letter-spacing: -.01em; }
.mt-field { display: grid; gap: 6px; font-size: var(--fs-sm); color: var(--muted); }
.mt-avatar-circle {
  inline-size: 76px; block-size: 76px; border-radius: 50%; display: grid; place-items: center;
  background: #2C3B3E; color: #E9F2F0; font-size: 26px; font-weight: 700;
}

/* ---- the call ---- */
.mt-call {
  position: relative; flex: 1; min-block-size: 0; display: grid; grid-template-rows: auto auto auto minmax(0, 1fr) auto;
  background: #0B1012; color: #E9F2F0;
}
.mt-top { display: flex; align-items: center; gap: 10px; padding: 8px 14px; font-size: var(--fs-sm); min-block-size: 44px; }
.mt-top-name { font-weight: 600; }
.mt-top-count { color: #A2B4B0; }
.mt-badge { padding: 2px 9px; border-radius: 999px; background: rgba(255, 255, 255, .12); font-size: var(--fs-xs); }
/* Recording: red and moving, because it is the one thing in a call nobody may miss. */
.mt-badge.is-recording { display: inline-flex; align-items: center; gap: 6px; background: rgba(226, 74, 74, .24); color: #FFDCDC; }
.mt-rec-dot { inline-size: 8px; block-size: 8px; border-radius: 50%; background: #FF5A5A; animation: mt-rec-pulse 1.6s ease-in-out infinite; }
@keyframes mt-rec-pulse { 50% { opacity: .2; } }
@media (prefers-reduced-motion: reduce) { .mt-rec-dot { animation: none; } }
.mt-code { font-family: var(--mono); letter-spacing: .06em; }
/* The meeting's clock (Syed, 22 September 2026). Tabular figures so the badge does not twitch wider
   and narrower every second as the digits change — a number that jitters reads as a fault. */
.mt-clock { font-variant-numeric: tabular-nums; letter-spacing: .02em; }
.mt-bar {
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 10px; padding: 6px 12px;
  background: rgba(143, 156, 232, .22); font-size: var(--fs-sm);
}
.mt-body { position: relative; grid-row: 4; display: grid; grid-template-columns: minmax(0, 1fr) auto; min-block-size: 0; }
.mt-grid { display: grid; gap: 10px; padding: 10px; grid-auto-rows: minmax(160px, 1fr); min-block-size: 0; overflow: auto; }
.mt-tile { position: relative; min-block-size: 0; background: #1A2426; border-radius: 12px; overflow: hidden; }
.mt-tile.is-spotlight { grid-column: 1 / -1; grid-row: span 2; }
/* Tiles share the stage in equal rows, so a video fills its tile rather than sizing it.

   THIS RULE WAS MISSING and the comment above was all that was left of it (Syed's meeting test, 17 September). A
   <video> with no size is drawn at the camera's own — 640×480 — so every picture sat in the top-left of its tile with
   a band of dead space under it, or overflowed a short tile and cropped the person's chin. */
.mt-video { inline-size: 100%; block-size: 100%; object-fit: cover; display: block; }
.mt-video.is-hidden { visibility: hidden; }
.mt-tile.is-self .mt-video { transform: scaleX(-1); }
.mt-tile.is-screen .mt-video { object-fit: contain; transform: none; }
.mt-avatar { position: absolute; inset: 0; display: grid; place-items: center; }
.mt-label {
  position: absolute; inset-inline-start: 8px; inset-block-end: 8px; max-inline-size: calc(100% - 16px);
  display: flex; align-items: center; gap: 6px; padding: 3px 8px; border-radius: 7px;
  background: rgba(0, 0, 0, .6); font-size: var(--fs-xs); white-space: nowrap; overflow: hidden;
}
.mt-label-icon svg, .mt-hand svg { inline-size: 14px; block-size: 14px; display: block; }
.mt-hand { position: absolute; inset-block-start: 8px; inset-inline-end: 8px; padding: 5px; border-radius: 50%; background: #D9A02B; color: #0F1A18; }
.mt-state { position: absolute; inset: 0; display: grid; place-items: center; background: rgba(0, 0, 0, .4); font-size: var(--fs-sm); }
.mt-tag {
  font-size: 10.5px; font-weight: 700; padding: 1px 7px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent);
}
.mt-tag.is-quiet { background: var(--surface-3); color: var(--muted); }
.mt-label .mt-tag { background: rgba(255, 255, 255, .18); color: #fff; }

.mt-controls {
  grid-row: 5; display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; padding: 10px;
  /* The anchor for the reaction strip below. It used to hang off the React button itself, which is fine
     on a wide toolbar and wrong on a phone: the toolbar wraps and centres, so that button can end up
     anywhere, and where it lands near an edge half the emoji fell outside the window (Syed, 25 September:
     "In the mobile layout, not all emojis are showing"). The toolbar is the full width of the meeting, so
     a strip centred on IT is on the screen at every width and wherever the button has wrapped to. */
  position: relative;
}
.mt-ctl {
  position: relative; display: inline-flex; flex-direction: column; align-items: center; gap: 3px;
  min-inline-size: 76px; padding: 8px 10px; border: 0; border-radius: 12px; cursor: pointer;
  background: rgba(255, 255, 255, .1); color: #E9F2F0; font: inherit; font-size: var(--fs-xs);
}
.mt-ctl:hover { background: rgba(255, 255, 255, .18); }
.mt-ctl svg { inline-size: 20px; block-size: 20px; }
.mt-ctl[aria-pressed="true"] { background: rgba(143, 156, 232, .35); }
.mt-ctl.is-off, .mt-ctl.is-leave { background: #C4382A; color: #fff; }
.mt-ctl:disabled { opacity: .45; cursor: default; }
.mt-ctl:focus-visible, .mt-round:focus-visible { outline: 2px solid #8F9CE8; outline-offset: 2px; }
.mt-count {
  position: absolute; inset-block-start: 3px; inset-inline-end: 12px; min-inline-size: 17px; padding: 0 5px;
  border-radius: 999px; background: #8F9CE8; color: #10141F; font-size: 10.5px; font-weight: 700; line-height: 17px;
}
/*
  THE WORDS GO WHEN THEY NO LONGER FIT ON ONE ROW. A meeting has fifteen controls now, and with their words they
  need about 1500px; below that the row wrapped and Leave — the one control nobody should have to look for — was
  what dropped to the second line. The name stays in the button for a screen reader and comes back as a tooltip
  (ControlButton sets title), so nothing becomes a mystery glyph.
*/
@container (max-width: 1500px) {
  .mt-ctl { min-inline-size: 0; padding: 8px; }
  .mt-ctl > span:not(.mt-count) { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); }
}

/* ---- panels ---- */
.mt-panel {
  inline-size: 320px; display: flex; flex-direction: column; min-block-size: 0;
  background: var(--surface-2); color: var(--text); border-inline-start: 1px solid var(--border);
}
@container (max-width: 820px) {
  .mt-body { grid-template-columns: minmax(0, 1fr); }
  .mt-panel { position: absolute; inset-block: 0; inset-inline-end: 0; inline-size: min(340px, 100%); z-index: 5; box-shadow: var(--sh-3); }
}
.mt-panel-head { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-block-end: 1px solid var(--border); font-weight: 600; }
.mt-panel-body { flex: 1; min-block-size: 0; overflow: auto; padding: 8px 12px; display: grid; align-content: start; gap: 8px; }
.mt-section-lbl { margin-block-start: 6px; font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .08em; color: var(--muted); }
.mt-switch { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: var(--fs-sm); }
.mt-switch input { inline-size: 18px; block-size: 18px; accent-color: var(--accent); }
.mt-person { display: grid; gap: 6px; padding: 8px 10px; border-radius: 10px; background: var(--surface); border: 1px solid var(--border); }
.mt-person-name { display: flex; align-items: center; gap: 6px; font-size: var(--fs-sm); font-weight: 600; }
.mt-person-acts { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.mt-mini {
  padding: 3px 10px; border-radius: 999px; border: 1px solid var(--border-strong);
  background: var(--surface-2); color: var(--text); font: inherit; font-size: var(--fs-xs); cursor: pointer;
}
.mt-mini:hover { background: var(--surface-3); }
.mt-mini.is-primary { background: var(--accent-fill); border-color: var(--accent); color: var(--on-accent); }
.mt-mini.is-danger { color: var(--critical); border-color: var(--critical); }
.mt-bar .mt-mini { background: rgba(255, 255, 255, .9); color: #0F1A18; border-color: transparent; }
.mt-confirm { font-size: var(--fs-xs); color: var(--critical); }
.mt-msg { display: grid; gap: 2px; font-size: var(--fs-sm); overflow-wrap: anywhere; }
.mt-msg.is-mine .mt-msg-who { color: var(--accent); }
.mt-msg-who { font-size: var(--fs-xs); font-weight: 600; color: var(--muted); }
.mt-msg-text { white-space: pre-wrap; }
.mt-compose { display: flex; gap: 8px; padding: 10px 12px; border-block-start: 1px solid var(--border); }

/* ---- what is left when it ends, and small notices ---- */
.mt-overlay { flex: 1; display: grid; place-items: center; padding: 24px; text-align: center; }
.mt-overlay-card {
  display: grid; gap: 14px; justify-items: center; max-inline-size: 440px; padding: 28px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 16px; box-shadow: var(--sh-2);
}
.mt-overlay-text { margin: 0; font-size: var(--fs-lg); }
.mt-toast {
  position: absolute; inset-inline-start: 50%; inset-block-end: 96px; transform: translateX(-50%); z-index: 10;
  padding: 8px 16px; border-radius: 999px; background: rgba(0, 0, 0, .85); color: #fff; font-size: var(--fs-sm);
}

/* The finished recording, which STAYS until it is dismissed. A toast that fades is not evidence a file exists, and
   in a desktop window this runs in a frame that can refuse a download begun by script and report nothing — so the
   person needs a link they can press themselves. Sat above the toast so both can show at once. */
.mt-recorded {
  position: absolute; inset-inline-start: 50%; inset-block-end: 140px; transform: translateX(-50%); z-index: 11;
  display: flex; align-items: center; gap: 10px; max-inline-size: min(560px, calc(100% - 32px));
  padding: 10px 12px; border-radius: var(--dk-r, 10px); background: rgba(0, 0, 0, .9); color: #fff;
  font-size: var(--fs-sm); box-shadow: 0 10px 28px rgba(0, 0, 0, .35);
}
.mt-recorded .mt-grow { min-inline-size: 0; overflow-wrap: anywhere; }
.mt-recorded a.mt-mini { text-decoration: none; display: inline-flex; align-items: center; }

/* ---- the lobby in a desktop window, and captions ----
   Syed, 15 September: "the meeting window should come in one view". A Meetings window opens at 720 by 480, where the
   stacked lobby needed scrolling. Side by side and tighter, the whole lobby fits there; only a window narrower than
   560 px stacks, and then the preview gives way. Placed after the lobby's base rules so these win. */
@container (max-width: 820px) {
  .mt-lobby { padding: 12px; }
  .mt-lobby-card { padding: 14px; gap: 14px; align-items: start; }
  .mt-title { font-size: var(--fs-lg); }
  .mt-form { gap: 8px; }
  .mt-lobby .mt-btn { padding: 8px 14px; }
  .mt-lobby .mt-input { padding: 7px 10px; }
}
@container (max-width: 560px) {
  .mt-lobby { place-items: start stretch; }
  .mt-lobby-card { grid-template-columns: minmax(0, 1fr); }
  .mt-preview { max-block-size: 34vh; margin-inline: auto; inline-size: 100%; }
}
.mt-join-row { display: flex; gap: 8px; }
.mt-join-row .mt-input { flex: 1; min-inline-size: 0; }
.mt-appear { margin: 0; font-size: var(--fs-sm); color: var(--muted); }
.mt-linkbtn { padding: 0; border: 0; background: none; color: var(--accent); font: inherit; cursor: pointer; text-decoration: underline; }

.mt-captions {
  position: absolute; inset-inline-start: 50%; inset-block-end: 14px; transform: translateX(-50%); z-index: 4;
  inline-size: max-content; max-inline-size: min(760px, 92%); display: grid; gap: 6px; justify-items: center;
  pointer-events: none;
}
.mt-caption { padding: 6px 12px; border-radius: 8px; background: rgba(0, 0, 0, .8); color: #fff; font-size: var(--fs-md); line-height: 1.4; }
.mt-caption-note {
  max-inline-size: 560px; padding: 4px 10px; border-radius: 8px; background: rgba(0, 0, 0, .62); color: #D8E1DF;
  font-size: var(--fs-xs); text-align: center;
}

/* ---- the folders somebody has connected (Syed, 15 September: option C) ---- */
.dk-folder-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.dk-folder-row {
  display: grid; gap: 8px; padding: 8px 10px;
  border: 1px solid var(--dk-line-soft); border-radius: var(--dk-r); background: var(--dk-surface);
}
.dk-folder-line { display: flex; align-items: center; gap: 10px; min-inline-size: 0; }
.dk-folder-text { display: grid; gap: 1px; }
.dk-folder-name { font-weight: 600; overflow-wrap: anywhere; }
.dk-folder-where { font-size: var(--fs-xs); color: var(--dk-ink-3); }
.dk-folder-more { font-size: 18px; line-height: 1; padding-inline: 8px; }
.dk-folder-opts {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px; padding: 8px 2px 2px;
  border-block-start: 1px solid var(--dk-line-soft);
}
.dk-chk { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-sm); color: var(--dk-ink-2); }
.dk-chk input { inline-size: 16px; block-size: 16px; accent-color: var(--accent); }
.dk-folder-glyph { display: grid; place-items: center; background: var(--dk-surface-3); color: var(--dk-ink-2); }
.dk-folder-glyph svg { inline-size: 22px; block-size: 22px; }

/* ============================================================================
   NETFIVE CHAT — the client's approved redesign (19 September 2026)
   Three columns: people, the conversation, and who you are talking to. Laid out by container
   queries rather than by the viewport, because this page spends most of its life inside a desktop
   window narrower than the screen it sits on — a media query would give a half-width window the
   full-width layout and push the composer off the side.

   CHAT'S COLOUR IS ITS OWN AND IS NOT THE ACCOUNT ACCENT. Every other screen in NETFIVE is painted
   in whatever accent the person picked, which is red today. Chat is teal, and the values below are
   the handoff's own design-tokens.css rather than var(--accent), because Chat and Rooms sit next to
   each other in the dock and a reader has to be able to tell at a glance which of the two they are
   typing into: one is private with no AI in it, the other has the assistant in the room. Wired to
   the shared accent the two would be the same colour on the same day, and the difference that
   matters most here would be the one nothing on screen showed.

   AND IT IS NOT A CLONE OF THE MESSENGER EVERYBODY KNOWS. The layout is the familiar one — bubbles,
   the time and ticks inside them, a lighter colour for your own — but the paint is NETFIVE's: mint
   bubbles on a dotted grey ground, teal marks, and the small corner on the sender's own side. No
   green, no beige wallpaper, no blue ticks, no yellow banner. That is a trade-dress line the client
   drew in the handoff, not a matter of taste.
   ============================================================================ */
.ch-mount {
  /* The handoff's token file, name for name, so any colour here can be checked against the client's
     own sheet rather than hunted through the rules below. */
  --ch-page: #F4F6F5; --ch-card: #FFFFFF; --ch-border: #E1E7E5; --ch-divider: #EDF1EF;
  --ch-input-border: #CFD8D5;
  --ch-text: #15201D; --ch-text-2: #3A4744; --ch-muted: #5A6864; --ch-placeholder: #7F8C88;

  /* A quote reads as a quiet inset of the bubble it is in, in both themes: darker than white on
     a light bubble, lighter than the page in the dark one. */
  --ch-quote-bg: #F1F4F3; --ch-quote-hover: #E7ECEA;

  --ch-primary: #0F766E; --ch-on-primary: #FFFFFF;
  --ch-primary-soft: #E3F5F2; --ch-primary-soft-2: #F1FAF8; --ch-primary-line: #5FB8AC;
  --ch-primary-text: #0B5E54;

  --ch-bubble-mine: #DCEFEA; --ch-bubble-mine-line: #C6E3DC;
  --ch-bubble-theirs: #FFFFFF; --ch-bubble-theirs-line: #E3E9E7;
  --ch-bubble-text: #102A26;

  /* Sixteen everywhere except the bottom corner on the sender's own side, which is four. That one
     corner is what says which way a message went, and it is why a bubble needs no tail drawn on it. */
  --ch-radius: 16px; --ch-tail: 4px;

  --ch-time: #56656F; --ch-tick: #7F8C88; --ch-tick-seen: #0F766E;
  --ch-wallpaper: #F3F5F4; --ch-dots: #DCE3E1;
  --ch-field: #F2F5F4; --ch-attach-ring: #B7D9D2;
  --ch-online: #22A06B; --ch-danger: #C0283B; --ch-badge: #D92D3A;

  /* The green a confirmation is written in. The handoff's own value; darker than the presence dot,
     because this one carries words and the dot carries none. */
  --ch-success-text: #1E6B3A;

  /* Rooms' own indigo, borrowed for the one link in Chat that leads out of it. The handoff's
     values: a door to somewhere else should not be painted in the colour of the room it is in. */
  --ch-rooms-link: #3B35B8; --ch-rooms-bg: #F7F6FE; --ch-rooms-line: #D9D7F5;
  --ch-focus: #5FB8AC;

  container-type: inline-size; position: relative; min-block-size: 100dvh;
  background: var(--ch-page); color: var(--ch-text); font-family: var(--font);
}

/* THE DARK THEME IS THE ATTRIBUTE ON THE ROOT AND NOTHING ELSE. This file has no
   prefers-color-scheme block anywhere in it on purpose: the person chooses the theme in their
   settings, and a Chat that followed the operating system instead would come up dark inside a desk
   the same person had set to light.

   The teal moves up its own ramp rather than staying put. #0F766E fills a button on white and is
   too dark to read as text on near-black, so on this theme the bright end does the talking — marks,
   ticks and links — and the deeper end keeps the filling. The mint bubble becomes a deep teal for
   the same reason a light bubble cannot simply be darkened: what has to survive is the difference
   between your message and theirs, not the hue. */
[data-theme="dark"] .ch-mount {
  --ch-page: var(--bg); --ch-card: var(--surface); --ch-border: var(--border); --ch-divider: var(--border);
  --ch-input-border: var(--border-strong);
  --ch-text: var(--text); --ch-text-2: #C8CBD2; --ch-muted: var(--muted); --ch-placeholder: var(--faint);

  --ch-quote-bg: #1A1D24; --ch-quote-hover: #22262F;

  --ch-primary: #14B8A6; --ch-on-primary: #04231B;
  --ch-primary-soft: #10312C; --ch-primary-soft-2: #0C2521; --ch-primary-line: #1C5A50;
  --ch-primary-text: #5EEAD4;

  --ch-bubble-mine: #10312C; --ch-bubble-mine-line: #1F5C52;
  --ch-bubble-theirs: #15171C; --ch-bubble-theirs-line: #23252B;
  --ch-bubble-text: #E8F1EE;

  --ch-time: #9A9DA6; --ch-tick: #8A8D97; --ch-tick-seen: #5EEAD4;
  --ch-wallpaper: #0A0B0D; --ch-dots: #191B20;
  --ch-field: #111216; --ch-attach-ring: #1C5A50;
  --ch-online: #3ECF8E; --ch-danger: #FF5F6D; --ch-badge: #FF3B50;
  --ch-success-text: #6EE7A8;
  --ch-rooms-link: #A9A4FF; --ch-rooms-bg: #17161F; --ch-rooms-line: #2B2940;
  --ch-focus: #2DD4BF;
}

/* Relative and clipped, because every overlay in Chat — the drawer, Details in a narrow window, the
   scrim, the mention list — is placed against THIS box. Nothing here is position: fixed: inside a
   desktop window a fixed element pins itself to the frame's viewport instead of the app. */
.ch-app {
  display: grid; grid-template-columns: minmax(0, 320px) minmax(0, 1fr);

  /* ONE ROW, NEVER TALLER THAN THE WINDOW. Left to an automatic row, the thread's own grid sized
     its message row to every message in it, the app grew past the window, and scrolling to the
     newest message scrolled the conversation's header out of sight with it. */
  grid-template-rows: minmax(0, 1fr);
  block-size: 100dvh; min-block-size: 0; position: relative; overflow: hidden;
}
.ch-app-wide { grid-template-columns: minmax(0, 320px) minmax(0, 1fr) minmax(0, 300px); }
.ch-loading { display: grid; place-items: center; block-size: 100dvh; color: var(--ch-muted); }

/* ---- buttons ----
   Filled in Chat's teal, for the reason at the top of this section: reading var(--accent) here would
   put the account's red on the one screen the client asked to be unmistakably teal. */
.ch-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 7px 13px; border-radius: 9px; border: 1px solid var(--ch-primary);
  background: var(--ch-primary); color: var(--ch-on-primary);
  font: inherit; font-size: var(--fs-sm); font-weight: 600; cursor: pointer;
}
.ch-btn:hover:not(:disabled) { background: var(--ch-primary-text); border-color: var(--ch-primary-text); }
.ch-btn:disabled { opacity: .55; cursor: default; }
.ch-btn-quiet { background: var(--ch-card); border-color: var(--ch-border); color: var(--ch-text-2); }
.ch-btn-quiet:hover:not(:disabled) { background: var(--ch-field); border-color: var(--ch-input-border); color: var(--ch-text); }

/* Blocking somebody is drawn as red words on white rather than as a red slab, which is how the
   design has it: a decision made once, not a button anybody should be reaching for. */
.ch-btn-danger { background: var(--ch-card); border-color: var(--ch-border); color: var(--ch-danger); }
.ch-btn-danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--ch-danger) 7%, var(--ch-card));
  border-color: color-mix(in srgb, var(--ch-danger) 40%, var(--ch-border)); color: var(--ch-danger);
}

/* One focus ring for the whole app, in the handoff's focus teal. It is stated once here rather than
   per control so that a control added later cannot quietly ship without one. */
.ch-mount :focus-visible { outline: 2px solid var(--ch-focus); outline-offset: 1px; }

.ch-problem { margin: 6px 14px; color: var(--ch-danger); font-size: var(--fs-sm); }
.ch-hint { margin: 6px 14px; color: var(--ch-muted); font-size: var(--fs-sm); }

.ch-avatar {
  display: inline-grid; place-items: center; flex: none; border-radius: 50%;
  color: #fff; font-weight: 700; letter-spacing: .02em; line-height: 1;
}

/* ---- the people ---- */
.ch-side {
  display: flex; flex-direction: column; gap: 2px; min-inline-size: 0; overflow-y: auto;
  border-inline-end: 1px solid var(--ch-border); background: var(--ch-card); padding-block-end: 12px;
}
.ch-find { padding: 12px 12px 6px; position: sticky; inset-block-start: 0; background: var(--ch-card); z-index: 2; }

/* The search is one grey pill, not a bordered field: it sits on white with nothing else competing
   for the eye at the top of the list, and a heavy outline there reads as the first thing to do. */
.ch-find-box {
  display: flex; align-items: center; gap: 8px; padding: 9px 12px;
  border: 1px solid transparent; border-radius: 10px; background: var(--ch-field);
}
.ch-find-box input {
  flex: 1; min-inline-size: 0; border: 0; background: none; color: var(--ch-text);
  font: inherit; font-size: var(--fs-sm);
}
.ch-find-box input::placeholder { color: var(--ch-placeholder); }
.ch-find-box input:focus { outline: none; }
.ch-find-box:focus-within { border-color: var(--ch-primary-line); background: var(--ch-card); }
.ch-find-mark { flex: none; color: var(--ch-muted); font-size: var(--fs-lg); line-height: 1; }

.ch-results { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 2px; }
.ch-results li { display: flex; align-items: center; gap: 10px; padding: 6px 4px; }
.ch-results-name {
  flex: 1; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-sm); font-weight: 600;
}

.ch-group { padding: 6px 0 0; }
.ch-group-head {
  margin: 0; padding: 6px 16px 4px; font-size: var(--fs-micro); font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ch-muted);
}
.ch-side-head { display: flex; align-items: center; gap: 8px; padding: 8px 16px 2px; }
.ch-side-title { margin: 0; flex: 1; min-inline-size: 0; font-size: var(--fs-sm); font-weight: 700; }
.ch-total {
  flex: none; padding: 2px 9px; border-radius: 999px; background: var(--ch-field);
  color: var(--ch-text-2); font-size: var(--fs-micro); font-weight: 700;
}

/* ---- a conversation row: name and time on one line, the last message under it ---- */
.ch-people, .ch-asks { list-style: none; margin: 0; padding: 0 8px; display: grid; gap: 1px; }
.ch-person {
  inline-size: 100%; display: flex; align-items: center; gap: 12px; padding: 9px 10px;
  border: 0; border-radius: 12px; background: none; color: inherit; font: inherit;
  text-align: start; cursor: pointer;
}
.ch-person:hover { background: var(--ch-field); }
.ch-person-on, .ch-person-on:hover {
  background: var(--ch-primary-soft); box-shadow: inset 3px 0 0 var(--ch-primary);
}
.ch-person-flat { cursor: default; }
.ch-person-flat:hover { background: none; }
.ch-person-main { flex: 1; min-inline-size: 0; display: grid; gap: 2px; }
.ch-person-top { display: flex; align-items: baseline; gap: 8px; min-inline-size: 0; }
.ch-person-name {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; font-size: var(--fs-md);
}
.ch-person-top .ch-person-name { flex: 1; min-inline-size: 0; }
.ch-person-when {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ch-muted); font-size: var(--fs-xs);
}
.ch-person-top .ch-person-when { flex: none; }
.ch-preview {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ch-muted); font-size: var(--fs-sm);
}
.ch-preview-who { color: var(--ch-text-2); }

/* Unread is weight as well as a count: a list where the only difference is a small number in the
   corner is a list somebody reads twice. */
.ch-person-unread .ch-person-name { font-weight: 800; }
.ch-person-unread .ch-preview { color: var(--ch-text); font-weight: 600; }
.ch-marks { flex: none; display: inline-flex; align-items: center; gap: 4px; }
.ch-unread {
  flex: none; display: inline-grid; place-items: center; min-inline-size: 22px; block-size: 22px;
  padding: 0 6px; border-radius: 999px; background: var(--ch-primary); color: var(--ch-on-primary);
  font-size: var(--fs-micro); font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1;
}

/* Somebody wants to be a contact: a card rather than a row, because unlike every row above it this
   one is a question with two answers and needs room for both. */
.ch-asks li {
  display: grid; gap: 10px; padding: 12px; border: 1px solid var(--ch-border); border-radius: 12px;
  background: var(--ch-card); margin: 0 2px;
}
.ch-ask-who { display: flex; align-items: center; gap: 10px; min-inline-size: 0; }
.ch-ask-name { font-weight: 700; font-size: var(--fs-sm); }
.ch-ask-note { color: var(--ch-muted); font-size: var(--fs-xs); overflow-wrap: anywhere; }
.ch-ask-do { display: flex; gap: 6px; }

/* ---- the conversation ---- */
/* A COLUMN, NOT A THREE-ROW GRID, and the change is worth a sentence because the grid failed
   silently. `grid-template-rows: auto minmax(0,1fr) auto` names exactly three tracks; adding the
   encryption line made a fourth child, which landed in an implicit row of zero height and did not
   appear at all — nothing in the markup or the stylesheet looked wrong, and the only symptom was a
   missing line. A flex column cannot fail that way: every child keeps its natural height, the
   thread below takes whatever is left, and the next thing added above the composer just works. */
.ch-thread {
  display: flex;
  flex-direction: column;
  min-inline-size: 0;
  min-block-size: 0;
  background: var(--ch-wallpaper);
}

/* The one child that absorbs the slack. min-block-size:0 is what lets it scroll rather than grow. */
.ch-thread > .ch-messages { flex: 1; min-block-size: 0; }
.ch-thread-head {
  display: flex; align-items: center; justify-content: flex-start; gap: 12px; padding: 10px 16px;
  border-block-end: 1px solid var(--ch-border); background: var(--ch-card);
}
.ch-thread-who { min-inline-size: 0; }
.ch-thread-name {
  margin: 0; font-size: var(--fs-lg); font-weight: 700;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ch-thread-sub { margin: 1px 0 0; color: var(--ch-muted); font-size: var(--fs-xs); }

/* In the header's own row, not floating over the corner: floating was fine while Details was the
   only thing there, and swallowed the clicks the moment Call appeared beside it. */
.ch-thread-tools { margin-inline-start: auto; display: flex; align-items: center; gap: 8px; flex: none; }
.ch-call, .ch-details-open { font-size: var(--fs-xs); }

/* THE DOTTED GREY GROUND, which is the one piece of this design somebody will recognise from across
   the room — and the reason it is dots and not a photographic wallpaper. The grid is 18px and the
   dot is a single pixel, so it reads as texture at arm's length and as nothing at all behind words.

   Messages sit at the BOTTOM, as in every chat: a short conversation pinned to the top of a tall
   window looks like a page that failed to load the rest. An automatic margin on the first child
   rather than end alignment: content aligned to the end of a scroll box overflows upwards, and older
   messages loaded above could then sit where no scrolling reaches. The browser's scroll anchoring is
   off here because the thread keeps the reader's place itself when a page lands above them, and two
   corrections for one jump is a jump. */
.ch-messages {
  overflow-y: auto; padding: 14px 18px 18px; display: flex; flex-direction: column; gap: 2px;
  overflow-anchor: none;
  background-color: var(--ch-wallpaper);
  background-image: radial-gradient(var(--ch-dots) 1px, transparent 1px);
  background-size: 18px 18px;
}
.ch-messages > :first-child { margin-block-start: auto; }
.ch-older { display: grid; place-items: center; padding: 4px 0 10px; color: var(--ch-muted); font-size: var(--fs-xs); }
.ch-empty { margin: auto; color: var(--ch-muted); font-size: var(--fs-sm); }

/* ---- what is true about this conversation's privacy ----
   ONE QUIET GREY LINE WITH A LOCK, NOT A COLOURED BANNER. A yellow strip at the top of every chat is
   the thing people stop seeing inside a week, and it is also somebody else's design. The note under
   it is a small block that opens on a press: a footnote, closed by default, because the line itself
   already says the two things that matter. */
/* PART OF THE FURNITURE, ABOVE THE THREAD RATHER THAN INSIDE IT — see the note in chat.tsx. It
   sits between the header and the scroller, so it is true all the time without ever covering a
   message. */
.ch-privacy {
  margin: 0;
  padding: 6px var(--sp-4);
  text-align: center;
  background: var(--ch-card);
  border-block-end: 1px solid var(--ch-border);
}
.ch-privacy-line {
  display: inline-flex; align-items: center; gap: 6px; max-inline-size: 60ch;
  padding: 3px 10px; border: 0; border-radius: 999px; background: none;
  color: var(--ch-muted); font: inherit; font-size: 11px; line-height: 1.5; text-align: center; cursor: pointer;
}
.ch-privacy-line:hover { color: var(--ch-primary-text); }
.ch-privacy-note {
  max-inline-size: 44ch; margin: 0 auto 10px; padding: 10px 13px;
  border: 1px solid var(--ch-border); border-radius: 12px; background: var(--ch-card);
  box-shadow: 0 10px 24px -16px rgb(0 0 0 / .5);
  color: var(--ch-text-2); font-size: var(--fs-xs); line-height: 1.55;
}
.ch-privacy-note p { margin: 0; }
.ch-privacy-note p + p { margin-block-start: 6px; }

/* ---- the day divider: a small uppercase label between two thin lines ----
   The lines are the divider's own pseudo-elements rather than a border on the label, so the label
   keeps the ground behind it and the rule never shows through the letters. */
.ch-day {
  display: grid; grid-template-columns: minmax(12px, 1fr) auto minmax(12px, 1fr); align-items: center;
  gap: 12px; margin: 14px 0 10px;
}
.ch-day::before, .ch-day::after { content: ""; block-size: 1px; background: var(--ch-divider); }
.ch-day span {
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ch-muted);
}

/* ---- the bubbles ---- */
.ch-msg { position: relative; display: flex; align-items: flex-end; gap: 8px; margin-block: 1px; }
.ch-msg-mine { flex-direction: row-reverse; }

/* NO FACE BESIDE A MESSAGE IN A DIRECT MESSAGE, which is how the design draws it and is not an
   omission. There are two people here, the header names and pictures the other one, and a small
   round portrait repeated down the left of every incoming line says nothing the side of the thread
   has not already said. It is hidden rather than removed from the markup because the markup is not
   this session's to change. */
.ch-msg > .ch-avatar { display: none; }

/* CONSECUTIVE MESSAGES FROM THE SAME PERSON ARE ONE RUN, and the only thing in the markup that says
   where a run ends is which side the next bubble is on — nothing marks the first of a run. So the
   gap is small by default and opens up on the one message where the side changes. :has() is what
   makes that readable at all: each message sits in its own wrapper, so the bubbles are not siblings
   and no plain adjacent selector can see across them. */
.ch-messages > :has(.ch-msg-mine) + :has(.ch-msg:not(.ch-msg-mine)),
.ch-messages > :has(.ch-msg:not(.ch-msg-mine)) + :has(.ch-msg-mine) { margin-block-start: 10px; }

.ch-bubble {
  position: relative; max-inline-size: min(560px, 65%); padding: 8px 12px 6px;
  border-radius: var(--ch-radius); border-end-start-radius: var(--ch-tail);
  background: var(--ch-bubble-theirs); border: 1px solid var(--ch-bubble-theirs-line);
  color: var(--ch-bubble-text);
}

/* Your own: mint, and the small corner swaps to the other side. No drop shadow on either — the
   design carries the whole separation on the outline, and a shadow on a dotted ground smears it. */
.ch-msg-mine .ch-bubble {
  background: var(--ch-bubble-mine); border-color: var(--ch-bubble-mine-line);
  border-start-start-radius: var(--ch-radius); border-end-start-radius: var(--ch-radius);
  border-end-end-radius: var(--ch-tail);
}
.ch-text { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; font-size: var(--fs-md); line-height: 1.45; }

/* ---- the time, and whether it got there ----
   Both live INSIDE the bubble at its bottom-right corner, which is the arrangement everybody already
   reads without being taught. Small and grey, so it is available rather than announced. */
.ch-when {
  display: flex; align-items: center; justify-content: flex-end; gap: 4px; clear: both;
  margin-block-start: 2px; font-size: 11px; line-height: 1.3; color: var(--ch-time);
}
.ch-edited { font-style: italic; }

/* ONE TICK SENT, TWO TICKS SEEN, and the second state is the only one that is coloured. Grey to teal
   rather than grey to blue: blue double ticks are the other product's mark, and this one is Chat's
   own teal for the same reason everything else on this screen is. */
.ch-ticks { color: var(--ch-tick); letter-spacing: -1.5px; font-size: 12px; }
.ch-ticks-seen { color: var(--ch-tick-seen); }

.ch-typing { margin: 6px 4px 2px; color: var(--ch-muted); font-size: var(--fs-xs); }

/* ---- editing and taking back ----
   The tools sit beside the bubble, on the side away from the edge, and appear when the message is
   pointed at or focused — a row of buttons under every message would be most of what the thread says.
   A device with no hover shows them always, faintly, because there is nothing to point with. */
.ch-msg-tools {
  display: flex; align-items: center; gap: 4px; flex-wrap: wrap; align-self: center;
  opacity: 0; transition: opacity .12s var(--ease);
}
.ch-msg:hover .ch-msg-tools, .ch-msg:focus-within .ch-msg-tools { opacity: 1; }
@media (hover: none) { .ch-msg-tools { opacity: .7; } }
.ch-tool {
  padding: 2px 9px; border: 1px solid var(--ch-border); border-radius: 999px;
  background: var(--ch-card); color: var(--ch-text-2); font: inherit; font-size: var(--fs-xs); cursor: pointer;
}
.ch-tool:hover { background: var(--ch-field); color: var(--ch-text); border-color: var(--ch-input-border); }
.ch-msg-tools .ch-btn { padding: 3px 10px; font-size: var(--fs-xs); }
.ch-msg-ask { font-size: var(--fs-xs); color: var(--ch-text-2); }

/* Taken back: a tombstone rather than a gap, and drawn as an outline with nothing in it so that it
   reads as an absence on either side of the thread. */
.ch-deleted { margin: 0; font-style: italic; color: var(--ch-muted); font-size: var(--fs-sm); }
.ch-bubble-gone, .ch-msg-mine .ch-bubble-gone {
  background: transparent; border-style: dashed; border-color: var(--ch-input-border); color: var(--ch-muted);
}
.ch-bubble-gone .ch-when, .ch-msg-mine .ch-bubble-gone .ch-when { color: var(--ch-muted); }

.ch-edit { display: grid; gap: 6px; min-inline-size: min(360px, 60cqi); }
.ch-edit textarea {
  inline-size: 100%; padding: 8px 11px; border: 1px solid var(--ch-input-border); border-radius: 10px;
  background: var(--ch-card); color: var(--ch-text); font: inherit; font-size: var(--fs-sm); resize: vertical;
}
.ch-edit-do { display: flex; justify-content: flex-end; gap: 6px; }
.ch-edit-do .ch-btn { padding: 4px 11px; font-size: var(--fs-xs); }

/* While your own message is being rewritten its bubble drops the mint: Save is a filled teal button,
   and on a mint ground it was a button nobody could see. */
.ch-msg-mine .ch-bubble:has(.ch-edit) { background: var(--ch-card); border-color: var(--ch-input-border); }

/* ---- @-mentions ----
   The picker lies over the thread just above the composer, inside the app like every other overlay in
   Chat. Mentions in a message are drawn apart; the reader's own name is lit, and a message addressed
   to them carries a mark down its edge, so it can be found by scrolling past. */
.ch-composer { position: relative; }
.ch-picker {
  position: absolute; inset-block-end: calc(100% + 4px); inset-inline-start: 16px; z-index: 20;
  inline-size: min(320px, calc(100% - 32px)); max-block-size: 232px; overflow-y: auto;
  margin: 0; padding: 4px; list-style: none;
  background: var(--ch-card); border: 1px solid var(--ch-border); border-radius: 12px;
  box-shadow: 0 12px 28px -14px rgb(0 0 0 / .45);
}
.ch-picker-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding: 7px 9px; border-radius: 8px; cursor: pointer; font-size: var(--fs-sm);
}
.ch-picker-row.is-active { background: var(--ch-primary-soft); color: var(--ch-primary-text); }
.ch-picker-label { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-picker-hint { flex: none; color: var(--ch-muted); font-size: var(--fs-xs); }

.ch-at { font-weight: 700; color: var(--ch-primary-text); }

/* The reader's own name, lit in the palette rather than in highlighter yellow: a yellow mark on this
   screen is the one colour the handoff rules out, and a soft teal ground says "this is about you"
   just as loudly next to a mint bubble. */
.ch-at-me {
  padding: 0 4px; border-radius: 5px; background: var(--ch-primary-soft);
  color: var(--ch-primary-text); box-shadow: inset 0 0 0 1px var(--ch-primary-line);
}
.ch-msg-for-me .ch-bubble { box-shadow: inset 3px 0 0 var(--ch-primary); }

/* A message that did not go: said in words, next to what it is about, and never in the bubble's own
   quiet grey — this is the one line in a thread somebody has to notice. */
.ch-warn {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-block-start: 5px;
  color: var(--ch-danger); font-size: var(--fs-xs); font-weight: 600;
}
.ch-retry {
  padding: 1px 9px; border: 1px solid currentColor; border-radius: 999px; background: none;
  color: inherit; font: inherit; cursor: pointer;
}

/* ---- is this window live ---- */
.ch-conn {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  color: var(--ch-muted); font-size: var(--fs-xs); white-space: nowrap;
}
.ch-conn-dot { flex: none; inline-size: 8px; block-size: 8px; border-radius: 50%; background: var(--ch-muted); }
.ch-conn-connected .ch-conn-dot { background: var(--ch-online); }
.ch-conn-reconnecting, .ch-conn-connecting { color: var(--ch-text-2); }
.ch-conn-reconnecting .ch-conn-dot, .ch-conn-connecting .ch-conn-dot { background: #C08A19; }
.ch-conn-offline { color: var(--ch-danger); font-weight: 700; }
.ch-conn-offline .ch-conn-dot { background: var(--ch-danger); }

/* ---- the composer: a white bar under the dotted ground ---- */
.ch-composer {
  /* WRAPS, so the reply bar can have its own line. Syed, 24 September 2026: "when we try to reply to
     any specific message it is not giving any option to type or do anything."

     .ch-replying already asks for inline-size: 100% and order: -1 - it was written to sit full width
     above the box. A flex container is SINGLE-LINE unless it wraps, so those two declarations could not
     do what they say: the bar took the front of the same row and squeezed the textarea to nothing.
     Everything was on screen and there was simply nowhere left to type. */
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px; padding: 10px 16px;
  border-block-start: 1px solid var(--ch-border); background: var(--ch-card);
}

/* A grey pill, as the design has it. It keeps `resize: vertical` even though a pill with a drag
   handle is not the prettiest corner on this screen: the box opens one row high, nothing in the
   script grows it, and taking the handle away would leave a long message scrolling inside a single
   line with no way to see what you had written. */
.ch-composer textarea {
  flex: 1; min-inline-size: 0; min-block-size: 40px; max-block-size: 160px; padding: 10px 16px;
  resize: vertical; border: 1px solid var(--ch-input-border); border-radius: 20px;
  background: var(--ch-field); color: var(--ch-text); font: inherit; font-size: var(--fs-md); line-height: 1.45;
}
.ch-composer textarea::placeholder { color: var(--ch-placeholder); }
.ch-composer textarea:focus-visible { outline: 2px solid var(--ch-focus); outline-offset: 1px; }

/* SEND SAYS SO WHEN IT CANNOT BE PRESSED. The design's Send is a filled teal circle and this one
   carries a word rather than an arrow, so it is the same teal as a pill — and empty it goes to the
   pale end of the same colour rather than to a dimmed version of the filled one. Half-opacity teal
   on white still reads as a live button, which is how somebody comes to press it three times. */
.ch-send { border-radius: 999px; min-block-size: 40px; padding-inline: 18px; }
.ch-send:disabled {
  opacity: 1; background: var(--ch-primary-soft); border-color: var(--ch-primary-line); color: var(--ch-muted);
}

/* Where the composer would be, when the reader has blocked this person. */
.ch-readonly {
  align-items: center; justify-content: space-between; color: var(--ch-muted); font-size: var(--fs-sm);
}

/* ---- who this is ---- */
.ch-details {
  border-inline-start: 1px solid var(--ch-border); background: var(--ch-card);
  padding: 14px; overflow-y: auto; display: grid; align-content: start; gap: 14px;
}
.ch-details-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.ch-details-head h2 { margin: 0; font-size: var(--fs-md); }
.ch-details-who { display: grid; justify-items: center; gap: 6px; padding: 10px 0 4px; text-align: center; }
.ch-details-name { font-size: var(--fs-lg); font-weight: 700; }
.ch-details-standing { color: var(--ch-muted); font-size: var(--fs-sm); }
.ch-details-do { display: grid; padding-block-start: 4px; border-block-start: 1px solid var(--ch-divider); }
.ch-details-note { margin: 0; color: var(--ch-muted); font-size: var(--fs-xs); line-height: 1.55; }

/* The encryption sentence, which reads the same as the note above but is a different sentence about
   a different thing — see the comment beside it in chat.tsx. */
.ch-details-privacy {
  margin: 0; padding: 0 var(--sp-4) var(--sp-3);
  color: var(--ch-muted); font-size: var(--fs-xs); line-height: 1.55;
}

.ch-nothing {
  display: grid; place-items: center; align-content: center; gap: 8px; padding: 24px; text-align: center;
  background: var(--ch-page);
}
.ch-nothing h1 { margin: 0; font-size: var(--fs-xl); }
.ch-nothing p { margin: 0; color: var(--ch-muted); }

/* The list control and the scrim exist in the markup at every width and are only drawn by the
   narrow query below — so the layout never depends on a script measuring the window. */
.ch-nav-toggle {
  display: none; flex: none; align-items: center; justify-content: center;
  inline-size: 36px; block-size: 36px; padding: 0; border: 1px solid var(--ch-border); border-radius: 10px;
  background: var(--ch-card); color: var(--ch-text-2); font: inherit; font-size: 18px; line-height: 1; cursor: pointer;
}
.ch-nav-toggle:hover { background: var(--ch-field); color: var(--ch-text); }
.ch-scrim { display: none; }

/* ---- narrow: the desktop's Chat window at its own size, or a phone ----
   At 720px and below the columns become one: the list when nothing is open, the conversation when
   something is. The window opens at 720px, so THIS is the layout most people see first — the list is
   a drawer over the conversation, one tap away, and Details lies over it from the other side. Two
   columns of 45% would leave neither usable. */
@container (max-width: 720px) {
  .ch-app, .ch-app-wide { grid-template-columns: minmax(0, 1fr); }

  /* Nothing open: the list IS the window, so the greeting beside it has nowhere to be. */
  .ch-app:not(.ch-app-open) .ch-nothing { display: none; }

  .ch-app-open .ch-side { display: none; }
  .ch-app-open.ch-nav-open .ch-side {
    display: flex; position: absolute; inset-block: 0; inset-inline-start: 0; z-index: 30;
    inline-size: min(320px, 88%); box-shadow: 0 0 24px rgba(0, 0, 0, .28);
  }
  .ch-nav-toggle { display: inline-flex; }

  /* ONE TAP AWAY WHATEVER IS OPEN. Details lies over the conversation with a scrim across the whole
     window, and the scrim used to cover the list control too: with Details open, the only way to the
     list was to find the scrim or the Hide button first. The control is lifted above the scrim, and
     pressing it puts Details away. Not while the list itself is open: then the drawer covers that
     corner, and the control would sit on top of the search box. */
  .ch-app-open:not(.ch-nav-open) .ch-nav-toggle { position: relative; z-index: 31; }

  .ch-details {
    position: absolute; inset-block: 0; inset-inline-end: 0; z-index: 30;
    inline-size: min(320px, 92%); box-shadow: 0 0 24px rgba(0, 0, 0, .28);
  }

  .ch-scrim {
    display: block; position: absolute; inset: 0; z-index: 29; padding: 0; border: 0;
    background: color-mix(in srgb, #000 38%, transparent); cursor: default;
  }

  /* At 570px the header carries a name, a status, the live dot, Call and Details. The word beside the
     dot is the first thing that can go: the dot is the whole state, and its title says it in words for
     anybody who hovers or asks. */
  .ch-conn-text { display: none; }

  .ch-messages { padding: 12px 12px 16px; }
  .ch-bubble { max-inline-size: 78%; }
}

/* ============================================================================
   NETFIVE MAIL, part 1 (the netfive-mail conversion — 16 September 2026)
   Folders, a list and one message. Laid out by container queries, because this page spends most of
   its life inside a desktop window narrower than the screen it sits on.
   ============================================================================ */
.ml-mount {
  container-type: inline-size; min-block-size: 100dvh;
  background: var(--bg); color: var(--text); font-family: var(--font);
}
.ml-app {
  display: grid; grid-template-columns: minmax(0, 200px) minmax(0, 320px) minmax(0, 1fr);
  block-size: 100dvh; min-block-size: 0;
}

.ml-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 8px 14px; border-radius: 9px; border: 1px solid var(--accent); background: var(--accent-fill);
  color: var(--on-accent); font: inherit; font-size: var(--fs-sm); font-weight: 600;
  cursor: pointer; text-decoration: none;
}
.ml-btn:hover { background: var(--accent-fill-hover); border-color: var(--accent-hover); }
.ml-btn-quiet { background: var(--surface); border-color: var(--border-strong); color: var(--text); }
.ml-btn-quiet:hover { background: var(--surface-3); }
.ml-note { margin: 8px 12px; color: var(--muted); font-size: var(--fs-sm); line-height: 1.5; }
.ml-problem { margin: 8px 12px; color: var(--critical); font-size: var(--fs-sm); }
.ml-centre { margin: auto; text-align: center; }
.ml-head {
  margin: 0; padding: 12px 14px 6px; font-size: var(--fs-xs); font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
}

/* ---- nothing connected yet: an offer, not an error ---- */
.ml-empty-screen {
  display: grid; place-items: center; align-content: center; gap: 12px;
  block-size: 100dvh; padding: 24px; text-align: center;
}
.ml-empty-screen h1 { margin: 0; font-size: var(--fs-xl); }
.ml-empty-screen p { margin: 0; max-inline-size: 52ch; color: var(--muted); line-height: 1.6; }

/* ---- the folders ---- */
.ml-folders {
  overflow-y: auto; border-inline-end: 1px solid var(--border); background: var(--surface-2);
}
.ml-folders ul, .ml-list ul { list-style: none; margin: 0; padding: 0 6px 12px; display: grid; gap: 2px; }
.ml-folder {
  inline-size: 100%; display: flex; align-items: center; gap: 8px; padding: 7px 8px;
  border: 0; border-radius: 9px; background: none; color: inherit; font: inherit;
  font-size: var(--fs-sm); text-align: start; cursor: pointer;
}
.ml-folder:hover { background: var(--surface-3); }
.ml-folder-on { background: var(--surface-3); box-shadow: inset 3px 0 0 var(--accent); font-weight: 600; }
.ml-folder-name { flex: 1; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ml-unread {
  flex: none; min-inline-size: 20px; padding: 1px 6px; border-radius: 999px;
  background: var(--accent-fill); color: var(--on-accent); font-size: var(--fs-xs); font-weight: 700; text-align: center;
}

/* ---- the list ---- */
.ml-list {
  overflow-y: auto; border-inline-end: 1px solid var(--border); background: var(--surface-2); min-inline-size: 0;
}
.ml-toolbar {
  display: flex; gap: 8px; padding: 10px 12px; position: sticky; inset-block-start: 0;
  background: var(--surface-2); border-block-end: 1px solid var(--border); z-index: 2;
}
.ml-search { flex: 1; min-inline-size: 0; display: flex; gap: 6px; }
.ml-search input {
  flex: 1; min-inline-size: 0; padding: 7px 10px; border: 1px solid var(--border-strong);
  border-radius: 9px; background: var(--surface); color: var(--text); font: inherit; font-size: var(--fs-sm);
}
.ml-search input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* A row is the message button and its star side by side. The star is its own control: a button inside
   a button is not something a browser will draw, and starring must not also open the message. */
.ml-item-row { position: relative; display: flex; align-items: stretch; }
.ml-item-row .ml-item { flex: 1; min-inline-size: 0; }
.ml-star {
  flex: none; inline-size: 34px; display: grid; place-items: start center; padding-block-start: 12px;
  background: none; border: none; cursor: pointer; font-size: 15px; line-height: 1;
  color: var(--muted); border-block-end: 1px solid var(--border);
}
.ml-star:hover:not(:disabled) { color: var(--text); }
.ml-star-on { color: var(--warning, #E0A100); }
.ml-star:disabled { cursor: default; opacity: .5; }

/* The gap between replying to a message and doing something to it: they are different kinds of act
   and the row reads as one run of buttons without it. */
.ml-actions-gap { flex: 1; min-inline-size: 8px; }

/* Deleting is the one button here that moves somebody's mail out of the folder they are looking at. */
.ml-btn-danger { color: var(--critical); }
.ml-btn-danger:hover:not(:disabled) { background: color-mix(in srgb, var(--critical) 12%, transparent); }

.ml-item {
  inline-size: 100%; display: grid; gap: 2px; padding: 9px 10px; border: 0; border-radius: 10px;
  background: none; color: inherit; font: inherit; text-align: start; cursor: pointer;
}
.ml-item:hover { background: var(--surface-3); }
.ml-item-on { background: var(--surface-3); box-shadow: inset 3px 0 0 var(--accent); }

/* Unread is weight, not colour: a list where the only difference is a shade of grey is a list
   somebody reads twice. */
.ml-item-unread .ml-from, .ml-item-unread .ml-subject { font-weight: 700; }
.ml-item-top { display: flex; align-items: baseline; gap: 8px; }
.ml-from {
  flex: 1; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-sm);
}
.ml-when { flex: none; color: var(--muted); font-size: var(--fs-xs); }
.ml-subject { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-sm); }
.ml-preview {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: var(--fs-xs);
}

/* ---- the message ---- */
.ml-message { overflow-y: auto; display: grid; align-content: start; min-inline-size: 0; }
.ml-message-head { padding: 16px 20px 10px; border-block-end: 1px solid var(--border); }
.ml-message-head h1 { margin: 0 0 6px; font-size: var(--fs-lg); overflow-wrap: anywhere; }
.ml-message-who { margin: 0; font-weight: 600; font-size: var(--fs-sm); overflow-wrap: anywhere; }
.ml-message-to, .ml-message-when { margin: 2px 0 0; color: var(--muted); font-size: var(--fs-xs); overflow-wrap: anywhere; }

/* The body IS text in this build — the line breaks the sender chose are the only formatting there
   is, and reflowing them loses the shape of a quoted reply. Each line is its own element now so
   that a quote can be marked as one; pre-wrap keeps the sender's spacing inside it. */
.ml-body {
  margin: 0; padding: 16px 20px 32px; overflow-wrap: anywhere;
  font: inherit; font-size: var(--fs-sm); line-height: 1.6;
}
.ml-line { margin: 0; white-space: pre-wrap; min-block-size: 1.6em; }

/* A QUOTE LOOKS LIKE A QUOTE (Syed, 25 September 2026: "make it well formatted"). It used to be the
   sender's "> " markers, drawn as themselves, which is the mail rather than a reading of it. The bar
   and the colour say the same thing without the punctuation, and the depth steps the bar across so a
   reply to a reply is still visibly further in. */
.ml-quoted {
  padding-inline-start: 10px; border-inline-start: 2px solid var(--border-strong); color: var(--muted);
}
.ml-quoted[data-depth="2"] { margin-inline-start: 10px; }
.ml-quoted[data-depth="3"] { margin-inline-start: 20px; }
.ml-quoted[data-depth="4"] { margin-inline-start: 30px; }

/* A link in somebody's mail: underlined, in the accent, and it must not run off the pane — mail is
   full of tracking addresses three hundred characters long. */
.ml-body a { color: var(--accent); text-decoration: underline; overflow-wrap: anywhere; }
.ml-body a:hover { text-decoration-thickness: 2px; }

/* THE SCROLLBARS IN THIS WINDOW (Syed, 25 September 2026: "scroll bar colour need to be changed").
   The site's own colour is resolved at :root against the site palette and inherits from there, so
   every scroller inside Mail drew in a grey that belongs to a light page. Named here against Mail's
   own surfaces instead. The whole declaration is given, because a scrollbar-color with one working
   half is not a thing: the browser takes both or neither. */
.ml-mount, .ml-mount * { scrollbar-color: var(--border-strong) transparent; }

/* ---- part 2: accounts, adding one, and writing (Syed, 16 September) ---- */
.ml-account-mark {
  flex: none; display: grid; place-items: center; inline-size: 22px; block-size: 22px; border-radius: 50%;
  background: var(--surface-4); color: var(--text); font-size: var(--fs-xs); font-weight: 700;
}
.ml-account-microsoft { background: #0f6cbd; color: #fff; }
.ml-account-imap { background: var(--accent-fill); color: var(--on-accent); }
.ml-add-account { color: var(--muted); }
.ml-account-actions { padding: 8px 12px 16px; border-block-start: 1px solid var(--border); }
.ml-account-actions .ml-note { margin: 0 0 8px; }
.ml-row { display: flex; flex-wrap: wrap; gap: 6px; }
.ml-link {
  padding: 0; border: 0; background: none; color: var(--accent); font: inherit; font-size: var(--fs-sm);
  text-decoration: underline; text-underline-offset: 2px; cursor: pointer;
}
.ml-btn:disabled { opacity: .55; cursor: default; }
.ml-btn-danger { background: var(--critical); border-color: var(--critical); color: #fff; }
.ml-btn-danger:hover { background: var(--critical); border-color: var(--critical); filter: brightness(.92); }
.ml-notice {
  margin: 8px 12px; padding: 7px 10px; border-radius: 9px; background: var(--ok-bg); color: var(--ok-fg);
  font-size: var(--fs-sm); font-weight: 600;
}
.ml-problem a { color: inherit; font-weight: 600; }
.ml-pickers { display: none; gap: 6px; padding: 8px 12px 0; }
.ml-pickers select {
  flex: 1; min-inline-size: 0; padding: 6px 8px; border: 1px solid var(--border-strong); border-radius: 9px;
  background: var(--surface); color: var(--text); font: inherit; font-size: var(--fs-sm);
}
.ml-message-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-block-end: 12px; }
.ml-message-head .ml-note { margin: 6px 0 0; }

/* "Choose a message" in the middle of the empty pane, not stuck to its top edge. */
.ml-message:has(> .ml-centre) { align-content: center; }
.ml-back { display: none; }

/* Adding an account: one column, readable width, in the middle of whatever window it is in. */
.ml-add-screen { block-size: 100dvh; overflow-y: auto; padding: 24px 16px 40px; }
.ml-add { display: grid; gap: 14px; max-inline-size: 560px; margin-inline: auto; }
.ml-add h1 { margin: 8px 0 0; font-size: var(--fs-2xl); }
.ml-add-why { margin: 0; color: var(--muted); line-height: 1.6; }
.ml-add > .ml-btn { justify-self: start; }
.ml-doors { display: grid; gap: 10px; margin-block-start: 6px; }
.ml-door {
  display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto; column-gap: 12px; row-gap: 2px;
  align-items: center; padding: 14px 16px; border: 1px solid var(--border-strong); border-radius: 12px;
  background: var(--surface); color: var(--text); font: inherit; text-align: start; cursor: pointer;
}
.ml-door:hover { border-color: var(--accent); background: var(--surface-2); }
.ml-door:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ml-door-mark {
  grid-row: 1 / span 2; display: grid; place-items: center; inline-size: 36px; block-size: 36px; border-radius: 10px;
  font-size: var(--fs-lg); font-weight: 800; color: #fff;
}
.ml-door-gmail { background: #d93025; }
.ml-door-microsoft { background: #0f6cbd; }
.ml-door-other { background: var(--accent-fill); color: var(--on-accent); }
.ml-door-name { font-weight: 700; }
.ml-door-note { color: var(--muted); font-size: var(--fs-sm); }
.ml-add-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-block-start: 4px; }
.ml-add-actions .ml-btn { min-block-size: 36px; }
.ml-guide {
  display: grid; gap: 8px; justify-items: start; padding: 12px 14px; border-radius: 12px;
  background: var(--surface-2); border: 1px solid var(--border); font-size: var(--fs-sm); line-height: 1.55;
}
.ml-guide p, .ml-guide ol { margin: 0; }
.ml-guide ol { padding-inline-start: 20px; display: grid; gap: 4px; }
.ml-field { display: grid; gap: 4px; font-size: var(--fs-sm); }
.ml-field > span { font-weight: 600; }
.ml-field input, .ml-field select {
  min-inline-size: 0; padding: 8px 10px; border: 1px solid var(--border-strong); border-radius: 9px;
  background: var(--surface); color: var(--text); font: inherit; font-size: var(--fs-md);
}
.ml-field input:focus-visible, .ml-field select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ml-hint { margin: -6px 0 0; color: var(--muted); font-size: var(--fs-xs); line-height: 1.5; }
.ml-servers { border: 1px solid var(--border); border-radius: 12px; padding: 10px 14px; }
.ml-servers summary { cursor: pointer; font-weight: 600; font-size: var(--fs-sm); }
.ml-servers-body { display: grid; gap: 14px; padding-block: 10px 4px; }
.ml-servers .ml-hint { margin: 0; }
.ml-server {
  display: grid; grid-template-columns: minmax(0, 1fr) 90px 120px; gap: 8px; margin: 0; padding: 0; border: 0;
}
.ml-server legend { padding: 0 0 6px; font-size: var(--fs-xs); font-weight: 700; color: var(--muted); }

/* Writing: the fields on top, the text filling the rest, Send where a thumb finds it. */
.ml-compose { display: flex; flex-direction: column; block-size: 100dvh; min-block-size: 0; }
.ml-compose-head { display: flex; align-items: center; gap: 10px; padding: 14px 20px 8px; }
.ml-compose-head h1 { margin: 0; font-size: var(--fs-lg); }
.ml-compose-fields { display: grid; padding: 0 20px; border-block-end: 1px solid var(--border); }
.ml-compose-row {
  display: flex; align-items: center; gap: 10px; margin: 0; padding: 6px 0; border-block-start: 1px solid var(--border);
  font-size: var(--fs-sm);
}
.ml-compose-label { flex: none; inline-size: 64px; color: var(--muted); }
.ml-compose-from { min-inline-size: 0; overflow-wrap: anywhere; }
.ml-compose-row input {
  flex: 1; min-inline-size: 0; padding: 6px 2px; border: 0; background: none; color: var(--text); font: inherit;
  font-size: var(--fs-md);
}
.ml-compose-row input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.ml-compose-body {
  flex: 1; min-block-size: 160px; margin: 0; padding: 14px 20px; border: 0; resize: none;
  background: var(--bg); color: var(--text); font: inherit; font-size: var(--fs-md); line-height: 1.6;
}
.ml-compose-body:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.ml-compose-actions {
  display: flex; gap: 8px; padding: 10px 20px 14px; border-block-start: 1px solid var(--border); background: var(--surface-2);
}

/* ---- narrow: a small window, or a phone ---- */
@container (max-width: 900px) {
  .ml-app { grid-template-columns: minmax(0, 180px) minmax(0, 1fr); }
  .ml-message { display: none; }
  .ml-app-reading .ml-list { display: none; }
  .ml-app-reading .ml-message { display: grid; }
  .ml-back { display: inline-flex; }
}

@container (max-width: 600px) {
  .ml-app { grid-template-columns: minmax(0, 1fr); }
  .ml-folders { display: none; }
  .ml-pickers { display: flex; }
  .ml-toolbar { flex-wrap: wrap; }
  .ml-server { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .ml-server .ml-field-host { grid-column: 1 / -1; }
  .ml-compose-head, .ml-compose-fields, .ml-compose-body, .ml-compose-actions { padding-inline: 16px; }
}

/* ============================================================================
   STUDENT (the Student conversion) — 16 September 2026
   Courses beside one course's parts, with the figures the server worked out. Container queries
   again: this lives in a desktop window more often than on a whole screen.
   ============================================================================ */
.st-mount {
  container-type: inline-size; min-block-size: 100dvh;
  background: var(--bg); color: var(--text); font-family: var(--font);
}
/* A column, not four named rows: the problem line, the add form and the empty state are each
   there or not there, and a grid that places children by their position put the courses in the
   "auto" row whenever one of them was missing — so the whole screen sat in the top third with the
   page showing underneath it. */
.st-app { position: relative; display: flex; block-size: 100dvh; min-block-size: 0; }
.st-shell { flex: 1; min-inline-size: 0; min-block-size: 0; display: flex; flex-direction: column; }
.st-loading { display: grid; place-items: center; block-size: 100dvh; color: var(--muted); }
.st-grow { flex: 1; }

.st-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 8px 14px; border-radius: 9px; border: 1px solid var(--accent); background: var(--accent-fill);
  color: var(--on-accent); font: inherit; font-size: var(--fs-sm); font-weight: 600; cursor: pointer;
}
.st-btn:hover { background: var(--accent-fill-hover); border-color: var(--accent-hover); }
.st-btn:disabled { opacity: .55; cursor: default; }
.st-problem { margin: 8px 16px; color: var(--critical); font-size: var(--fs-sm); }
.st-note { margin: 10px 20px; color: var(--muted); font-size: var(--fs-sm); line-height: 1.5; }
.st-centre { margin: auto; text-align: center; }
.st-head {
  margin: 14px 0 4px; padding: 0 14px; font-size: var(--fs-xs); font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
}

/* ---- the bar across the top: the term, the scheme, and adding ---- */
.st-top {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; padding: 10px 20px;
  border-block-end: 1px solid var(--border); background: var(--surface-2);
}
.st-term { font-weight: 700; font-size: var(--fs-md); }

/* ---- the headline (16 September 2026) ----
   Student's dark band: the date, the one sentence, and what to look at next. Drawn from the text
   and background tokens inverted, so it stays a dark band on the light theme and a light one on
   the dark theme rather than disappearing into either. */
.st-hero { padding: 20px 22px; border-radius: 14px; background: var(--text); color: var(--bg); }
.st-hero-meta {
  margin: 0; font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: .12em; text-transform: uppercase;
  opacity: .8;
}
.st-hero h1 { margin: 8px 0 0; max-inline-size: 36ch; font-size: clamp(18px, 4.2cqi, 26px); line-height: 1.3; font-weight: 700; }
.st-hero h1 u { text-decoration: none; border-block-end: 3px solid var(--ok); padding-block-end: 1px; }
.st-hero h1 u.st-bad { border-block-end-color: var(--critical); }
.st-hero-acts { display: flex; flex-wrap: wrap; gap: 8px; margin-block-start: 16px; }
.st-hero-acts button {
  display: inline-flex; align-items: center; gap: 8px; padding: 7px 12px; border-radius: 9px;
  border: 1px solid color-mix(in srgb, var(--bg) 30%, transparent); background: transparent; color: inherit;
  font: inherit; font-size: var(--fs-sm); text-align: start; cursor: pointer;
}
.st-hero-acts button:hover { background: color-mix(in srgb, var(--bg) 12%, transparent); }
.st-hero-acts i { inline-size: 7px; block-size: 7px; border-radius: 50%; flex: none; }
.st-tone-urgent { background: var(--critical); }
.st-tone-watch { background: var(--warning); }
.st-tone-good { background: var(--ok); }

/* ---- the figures across ---- */
.st-strip {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); border: 1px solid var(--border);
  border-radius: 12px; overflow: hidden; background: var(--surface);
}
.st-strip > div { padding: 12px 16px; border-inline-start: 1px solid var(--border); margin-inline-start: -1px; }
.st-strip h2 {
  margin: 0 0 6px; font-family: var(--mono); font-size: var(--fs-micro); font-weight: 500; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted);
}
.st-big {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 6px; margin: 0; font-size: 26px; font-weight: 700;
  line-height: 1.1; font-variant-numeric: tabular-nums;
}
.st-big s { text-decoration: none; font-size: var(--fs-xs); font-weight: 500; color: var(--muted); }
.st-delta { display: inline-block; margin-block-start: 6px; font-family: var(--mono); font-size: var(--fs-xs); }
.st-delta-up { color: var(--ok-fg); }
.st-delta-down { color: var(--critical); }
.st-delta-flat { color: var(--muted); }

/* ---- getting to the goal ---- */
.st-solver { display: grid; gap: 12px; }
.st-solver-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; }
.st-solver-head h3 { flex: 1; margin: 0; font-size: var(--fs-md); }
.st-goalset { display: inline-flex; align-items: center; gap: 8px; color: var(--muted); font-size: var(--fs-sm); }
.st-goalbar { position: relative; block-size: 26px; margin-block-start: 16px; border-radius: 8px; background: var(--surface-3); }
.st-goalbar i { position: absolute; inset-block: 0; inset-inline-start: 0; border-radius: 8px; background: var(--accent); transition: inline-size .45s var(--ease); }
.st-goalbar u {
  position: absolute; inset-block: 0; text-decoration: none;
  background: repeating-linear-gradient(45deg, transparent 0 4px, color-mix(in srgb, var(--accent) 35%, transparent) 4px 8px);
}
.st-goalbar b { position: absolute; inset-block: -5px; inline-size: 3px; margin-inline-start: -1px; border-radius: 2px; background: var(--text); }
.st-goalbar b::after {
  content: attr(data-l); position: absolute; inset-block-end: calc(100% + 2px); inset-inline-start: 50%;
  transform: translateX(-50%); white-space: nowrap; font-family: var(--mono); font-size: var(--fs-micro); color: var(--muted);
}
.st-goalbar b.st-hang-start::after { transform: none; inset-inline-start: 0; }
.st-goalbar b.st-hang-end::after { transform: none; inset-inline-start: auto; inset-inline-end: 0; }
.st-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; color: var(--muted); font-size: var(--fs-xs); }
.st-legend span { display: inline-flex; align-items: center; gap: 6px; }
.st-legend b { color: var(--text); }
.st-legend i { display: inline-block; inline-size: 9px; block-size: 9px; border-radius: 3px; }
.st-legend-now { background: var(--accent); }
.st-legend-proj { background: repeating-linear-gradient(45deg, transparent 0 2px, var(--accent) 2px 4px); }
.st-legend-goal { background: var(--text); }

/* ---- the dashboard (16 September 2026) ----
   Student's own page: sections one under another, with a rail beside them once the window is wide
   enough to hold one. It scrolls inside the window, so the top bar stays where it is. */
.st-scroll { flex: 1; min-block-size: 0; overflow-y: auto; }
.st-dash { display: grid; gap: 18px; padding: 16px 20px 32px; max-inline-size: 1180px; margin-inline: auto; }
.st-cols { display: grid; grid-template-columns: minmax(0, 1fr); gap: 18px; align-items: start; }
.st-main { display: grid; gap: 26px; min-inline-size: 0; }
.st-sec { display: grid; gap: 10px; min-inline-size: 0; scroll-margin-block-start: 12px; }
.st-sec-head { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px 12px; }
.st-sec-head > div:first-child { flex: 1; min-inline-size: 220px; }
.st-sec-head h2 { margin: 0; font-size: var(--fs-lg); }
.st-sec-head p { margin: 3px 0 0; color: var(--muted); font-size: var(--fs-sm); line-height: 1.5; }
.st-sec-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.st-card {
  padding: 14px 16px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface);
  min-inline-size: 0;
}
.st-notice { margin: 8px 20px 0; color: var(--ok-fg); font-size: var(--fs-sm); }
.st-num { font-variant-numeric: tabular-nums; }
.st-link-quiet { color: var(--muted); }
.st-sec-head p.st-field-note { color: var(--accent); font-size: var(--fs-xs); }

/* ---- the engine's sentence, in the colour of how things stand ---- */
.st-verdict {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; margin: 0; padding: 9px 12px;
  border-radius: 10px; border: 1px solid var(--border); background: var(--surface-2);
  font-size: var(--fs-sm); line-height: 1.5;
}
.st-verdict > span { flex: 1; min-inline-size: 12ch; }
.st-verdict .st-link { font-weight: 700; color: inherit; white-space: nowrap; }
.st-v-ok { background: var(--ok-bg); border-color: color-mix(in srgb, var(--ok) 35%, transparent); color: var(--ok-fg); }
.st-v-stretch { background: var(--warning-bg); border-color: color-mix(in srgb, var(--warning) 45%, transparent); color: var(--warning-fg); }
.st-v-unreachable { background: var(--critical-bg); border-color: color-mix(in srgb, var(--critical) 40%, transparent); color: var(--critical); }
.st-v-empty { background: var(--surface-2); border-style: dashed; color: var(--muted); }

/* ---- the cheapest wins ---- */
.st-wins { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }
.st-win {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 12px; align-items: center;
  padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface);
}
.st-win p { margin: 0; font-size: var(--fs-sm); line-height: 1.5; }
.st-win small { display: block; color: var(--muted); font-size: var(--fs-xs); }
.st-win-settled { background: var(--surface-2); border-style: dashed; opacity: .7; }
.st-rank {
  inline-size: 22px; block-size: 22px; border-radius: 50%; display: grid; place-items: center;
  background: var(--text); color: var(--bg); font: 700 var(--fs-micro) var(--mono);
}
.st-win-settled .st-rank { background: var(--muted); }
.st-gain { font-family: var(--mono); font-size: var(--fs-sm); color: var(--accent); white-space: nowrap; font-weight: 600; }

/* ---- a course card ---- */
.st-courses { display: grid; gap: 11px; }
.st-cc { display: grid; gap: 12px; }
.st-cc-top { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 12px; }
.st-swatch { inline-size: 10px; block-size: 36px; border-radius: 4px; flex: none; }
.st-cc-name { flex: 1; min-inline-size: 150px; }
.st-cc-name h3 { margin: 0; font-size: var(--fs-md); font-weight: 700; }
.st-cc-name p { margin: 2px 0 0; color: var(--muted); font-size: var(--fs-xs); }
.st-cc-name b { font-family: var(--mono); font-weight: 600; color: var(--text); }
.st-cc-grades { display: flex; align-items: flex-start; gap: 14px; }
.st-gfield { display: grid; justify-items: end; gap: 2px; text-align: end; }
.st-glabel { font-size: var(--fs-micro); letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.st-gval { font-size: 20px; font-weight: 700; line-height: 1.15; font-variant-numeric: tabular-nums; }
.st-gp { color: var(--muted); font-size: var(--fs-xs); min-block-size: 1.2em; }
.st-target {
  inline-size: 64px; padding: 4px 8px; border: 1px dashed var(--border-strong); border-radius: 7px;
  background: var(--surface); color: var(--text); font: inherit; font-size: 16px; font-weight: 600;
  text-align: end; font-variant-numeric: tabular-nums;
}
.st-target:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-style: solid; }

/* The bar: the grade so far in the course's colour, and a mark where the target is. */
.st-track { position: relative; block-size: 8px; margin-block-start: 14px; border-radius: 6px; background: var(--surface-3); }
.st-track i { position: absolute; inset-block: 0; inset-inline-start: 0; border-radius: 6px; transition: inline-size .4s var(--ease); }
.st-track b {
  position: absolute; inset-block-start: -4px; inline-size: 2px; block-size: 16px; margin-inline-start: -1px;
  border-radius: 2px; background: var(--text);
}
.st-track b::after {
  content: attr(data-l); position: absolute; inset-block-end: 18px; inset-inline-start: 50%;
  transform: translateX(-50%); white-space: nowrap; font-family: var(--mono); font-size: var(--fs-micro); color: var(--muted);
}
.st-track b.st-hang-start::after { transform: none; inset-inline-start: 0; }
.st-track b.st-hang-end::after { transform: none; inset-inline-start: auto; inset-inline-end: 0; }
[dir="rtl"] .st-track b.st-hang-centre::after { transform: translateX(50%); }

.st-breakdown { display: grid; }
.st-brow {
  display: grid; grid-template-columns: minmax(0, 1fr) 54px 84px; gap: 10px; align-items: center;
  padding: 5px 0; border-block-start: 1px solid var(--border); font-size: var(--fs-sm);
}
.st-brow:first-child { border-block-start: 0; }
.st-bw { font-family: var(--mono); font-size: var(--fs-xs); color: var(--muted); text-align: end; }
.st-brow-pending .st-bn { color: var(--muted); }
.st-score {
  inline-size: 100%; padding: 4px 8px; border: 1px solid var(--border); border-radius: 7px;
  background: var(--surface-2); color: var(--text); font: inherit; font-size: var(--fs-sm);
  text-align: end; font-variant-numeric: tabular-nums;
}
.st-score:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; background: var(--surface); }
.st-cc-foot {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; color: var(--muted); font-size: var(--fs-xs);
}
.st-cc .st-problem { margin: 0; }

@container (max-width: 700px) {
  .st-dash { padding-inline: 14px; }
}
@container (max-width: 460px) {
  .st-cc-grades { inline-size: 100%; justify-content: space-between; }
  .st-gfield { justify-items: start; text-align: start; }
}

.st-scheme { color: var(--muted); font-size: var(--fs-xs); }

/* ---- Student: buttons beyond the one ---- */
.st-btn-sm { padding: 5px 10px; font-size: var(--fs-xs); }
.st-btn-ghost { background: transparent; color: var(--text); border-color: var(--border-strong); }
.st-btn-ghost:hover { background: var(--surface-3); border-color: var(--border-strong); }
.st-btn-danger { background: var(--critical); border-color: var(--critical); color: #fff; }
.st-btn-danger:hover { background: var(--critical); border-color: var(--critical); filter: brightness(.92); }
.st-btn-danger-text { color: var(--critical); }
.st-muted { color: var(--muted); font-size: var(--fs-xs); }

/* ---- Student: a form over the dashboard, or in place of it (17 September 2026) ----
   THE CLIENT'S RULE: a pop-up at normal widths, the full-pane panel below about 700px — which is
   what Student's smallest desktop window (680px) needs. The width that decides is the WINDOW'S:
   .st-mount is an inline-size container, so a narrow window on a wide monitor gets the pane, which
   a media query would have got wrong.

   THE SETUP WIZARD IS THE ONE EXCEPTION, AT EVERY WIDTH (18 September 2026). Rule 1 of the
   reference outranks the width rule: never a form before the product. The reference's own PHONE
   screen 04 keeps the wizard floating over the dimmed dashboard at 390px, and a brand-new person
   meeting a full-pane setup in a 680px window is exactly what rule 1 forbids. So the dialog is
   written here once and applies always; below 700px .st-scrim-pane — every form EXCEPT the
   wizard — falls back to the panel this file used to draw, and only then is the dashboard behind
   it hidden.

   While a dialog is up the rest of the window is inert (student.tsx) and the page behind it does
   not scroll: a dashboard that slides about under a modal is one somebody thinks they are using. */
.st-scrim {
  position: fixed; inset: 0; z-index: 80; display: flex; align-items: center; justify-content: center;
  padding: 20px; overflow-y: auto; overscroll-behavior: contain;
  background: color-mix(in srgb, #0a0f0c 52%, transparent); backdrop-filter: blur(3px);
}
.st-modal {
  position: relative; display: flex; flex-direction: column; inline-size: min(560px, 100%);
  min-inline-size: 0; max-block-size: 88vh;
  border: 1px solid var(--border); border-radius: 16px; background: var(--surface);
  box-shadow: 0 24px 60px rgb(0 0 0 / .34); overflow: hidden;
}
/* The editor is the one the reference gives more room to: a spreadsheet of weightings. */
.st-modal-wide { inline-size: min(1100px, 100%); block-size: 88vh; }

/* Header and footer stay put; only the questions between them scroll — which is what keeps the ×
   and "Not now" reachable without scrolling on a 390px screen. */
.st-modal > .st-editor {
  flex: 1; min-block-size: 0; max-inline-size: none; overflow-y: auto; overscroll-behavior: contain;
  align-content: start; padding: 0 22px; gap: 15px;
}
.st-modal > .st-editor > .st-editor-head {
  position: sticky; inset-block-start: 0; z-index: 2; margin-inline: -22px;
  padding: 20px 52px 14px 22px; background: var(--surface);
}
.st-modal > .st-editor > .st-editor-foot {
  position: sticky; inset-block-end: 0; z-index: 2; margin-inline: -22px;
  padding: 14px 22px; background: var(--surface-2); border-block-start: 1px solid var(--border);
}
/* The × is pinned to the modal, which does not scroll — only its body does. */
.st-modal-x { position: absolute; inset-block-start: 12px; inset-inline-end: 12px; z-index: 3; }

/* The dashboard does not move under a dialog. Below 700px a pane form IS the page, so it scrolls. */
.st-scroll-locked { overflow: hidden; }

@container (min-width: 700px) {
  .st-scroll-paneopen { overflow: hidden; }
}

@container (max-width: 699px) {
  .st-dash-behind { display: none; }

  .st-scrim-pane {
    position: static; display: block; padding: 0; overflow: visible; overscroll-behavior: auto;
    background: none; backdrop-filter: none;
  }
  .st-scrim-pane > .st-modal {
    display: block; inline-size: 100%; max-block-size: none; block-size: auto;
    border: 0; border-radius: 0; background: none; box-shadow: none; overflow: visible;
  }
  .st-scrim-pane > .st-modal > .st-editor {
    display: grid; overflow: visible; max-inline-size: 760px; padding: 18px 20px 24px; gap: 14px;
  }
  .st-scrim-pane > .st-modal > .st-editor > .st-editor-head {
    position: static; margin-inline: 0; padding: 0 44px 0 0; background: none;
  }
  .st-scrim-pane > .st-modal > .st-editor > .st-editor-foot {
    position: static; margin-inline: 0; padding: 12px 0 0; background: none;
  }
  .st-scrim-pane .st-modal-x { inset-block-start: 16px; inset-inline-end: 16px; }
}

/* ---- Student: the course editor (16 September 2026) ---- */
.st-editor { display: grid; gap: 14px; padding: 18px 20px 24px; max-inline-size: 760px; }
.st-editor-head h2 { margin: 0; font-size: var(--fs-lg); }
.st-editor-head p { margin: 4px 0 0; color: var(--muted); font-size: var(--fs-sm); }
.st-row3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.st-field { display: grid; gap: 5px; min-inline-size: 0; font-size: var(--fs-xs); color: var(--muted); }
.st-field em { font-style: normal; color: var(--faint); }
.st-field input, .st-field select, .st-field textarea {
  inline-size: 100%; padding: 7px 10px; border: 1px solid var(--border-strong); border-radius: 9px;
  background: var(--surface); color: var(--text); font: inherit; font-size: var(--fs-sm);
}
.st-field input:focus-visible, .st-field select:focus-visible, .st-field textarea:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}
.st-set { margin: 0; padding: 0; border: 0; gap: 8px; }
.st-set legend { padding: 0; margin-block-end: 6px; font-size: var(--fs-xs); color: var(--muted); }
.st-set legend em { font-style: normal; color: var(--faint); }

.st-daypick { display: flex; flex-wrap: wrap; gap: 5px; }
.st-daypick button {
  min-inline-size: 44px; padding: 7px 8px; border-radius: 9px; border: 1px solid var(--border-strong);
  background: var(--surface); color: var(--text); font: inherit; font-size: var(--fs-xs); cursor: pointer;
}
.st-daypick button[aria-pressed="true"] { background: var(--accent-fill); border-color: var(--accent); color: var(--on-accent); font-weight: 700; }

.st-wrows { display: grid; gap: 6px; }
.st-wrow {
  display: grid; grid-template-columns: minmax(0, 1fr) 96px 32px; gap: 8px; align-items: center;
  padding: 5px 6px 5px 10px; border-radius: 9px; background: var(--surface-2); border: 1px solid var(--border);
}
.st-wrow > input {
  inline-size: 100%; padding: 4px 0; border: 0; border-radius: 0; background: transparent; color: var(--text);
  font: inherit; font-size: var(--fs-sm);
}
.st-wrow > input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.st-pct {
  display: flex; align-items: center; gap: 3px; padding: 3px 7px; border: 1px solid var(--border-strong);
  border-radius: 7px; background: var(--surface);
}
.st-pct input {
  inline-size: 100%; min-inline-size: 0; padding: 2px 0; border: 0; border-radius: 0; background: transparent; color: var(--text);
  font: inherit; font-size: var(--fs-sm); text-align: end; font-variant-numeric: tabular-nums;
}
.st-pct input:focus-visible { outline: none; }
.st-pct:focus-within { outline: 2px solid var(--accent); outline-offset: 1px; }
.st-pct span { color: var(--muted); font-size: var(--fs-xs); font-family: var(--mono); }
.st-x {
  inline-size: 28px; block-size: 28px; border: 0; border-radius: 7px; background: transparent;
  color: var(--muted); font-size: 18px; line-height: 1; cursor: pointer;
}
.st-x:hover { background: var(--surface-3); color: var(--text); }

.st-wtotal {
  display: flex; flex-wrap: wrap; gap: 4px 12px; margin: 2px 0 0; font-family: var(--mono);
  font-size: var(--fs-xs); color: var(--muted);
}
.st-wtotal-good { color: var(--ok-fg); }
.st-wtotal-over { color: var(--critical); }
.st-presets { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.st-chip {
  padding: 4px 11px; border-radius: 999px; border: 1px solid var(--border-strong); background: var(--surface);
  color: var(--text); font: inherit; font-size: var(--fs-xs); cursor: pointer;
}
.st-chip:hover { background: var(--surface-3); }
.st-chip[aria-pressed="true"] { background: var(--text); border-color: var(--text); color: var(--bg); font-weight: 600; }

.st-editor-foot {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding-block-start: 12px;
  border-block-start: 1px solid var(--border);
}
.st-confirm { color: var(--critical); font-size: var(--fs-sm); }
.st-editor .st-problem { margin: 0; }

/* ---- Student: setting up (16 September 2026) ---- */
.st-wizard { margin-inline: auto; }
.st-link {
  padding: 0; border: 0; background: none; color: var(--accent); font: inherit; font-size: inherit;
  text-decoration: underline; cursor: pointer;
}
.st-steps { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; margin: 12px 0 0; padding: 0; list-style: none; }
.st-steps li { block-size: 4px; border-radius: 4px; background: var(--surface-3); }
.st-steps li.st-step-on { background: var(--accent); }
.st-stepname {
  margin: 0; font-size: var(--fs-xs); font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
}
.st-flush { margin: 0; }
.st-goalrow { display: flex; align-items: center; gap: 16px; }
.st-goalrow input[type="range"] { flex: 1; padding: 0; border: 0; accent-color: var(--accent); }
.st-goalbig { font-size: 30px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; min-inline-size: 3.2ch; }
.st-checklist { display: grid; gap: 10px; margin: 0; padding: 0; list-style: none; counter-reset: st-check; }
.st-checklist li {
  display: grid; grid-template-columns: 26px minmax(0, 1fr); column-gap: 10px; counter-increment: st-check;
  padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface-2);
}
.st-checklist li::before {
  content: counter(st-check); grid-row: span 2; inline-size: 24px; block-size: 24px; border-radius: 50%;
  display: grid; place-items: center; background: var(--accent-fill); color: var(--on-accent); font-size: var(--fs-xs); font-weight: 700;
}
.st-checklist b { font-size: var(--fs-sm); }
.st-checklist span { color: var(--muted); font-size: var(--fs-xs); }

@container (max-width: 520px) {
  .st-row3 { grid-template-columns: minmax(0, 1fr); }
  .st-wrow { grid-template-columns: minmax(0, 1fr) 84px 30px; }
}

/* ---- Student: what's due (16 September 2026) ---- */
.st-eyebrow { font-family: var(--mono); font-size: var(--fs-xs); color: var(--muted); }
.st-addbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 6px 8px;
  border: 1px solid var(--border); border-radius: 11px; background: var(--surface);
}
.st-addbar input, .st-addbar select {
  min-inline-size: 0; padding: 6px 8px; border: 1px solid transparent; border-radius: 8px;
  background: var(--surface-2); color: var(--text); font: inherit; font-size: var(--fs-sm);
}
.st-addbar input:focus-visible, .st-addbar select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.st-addbar-title { flex: 1 1 200px; }
.st-addbar select { flex: 0 1 auto; max-inline-size: 180px; }
.st-addbar-weight { display: inline-flex; align-items: center; gap: 3px; color: var(--muted); font-size: var(--fs-xs); }
.st-addbar-weight input { inline-size: 58px; text-align: end; }

.st-tasks { display: grid; gap: 7px; margin: 0; padding: 0; list-style: none; }
.st-task {
  display: grid; grid-template-columns: 20px minmax(0, 1fr) auto auto; gap: 6px 12px; align-items: center;
  padding: 10px 14px; border: 1px solid var(--border); border-radius: 11px; background: var(--surface);
}
.st-task > input[type="checkbox"] { inline-size: 17px; block-size: 17px; accent-color: var(--accent); cursor: pointer; }
.st-task-title { margin: 0; font-size: var(--fs-md); font-weight: 600; overflow-wrap: anywhere; }
.st-task-sub { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin: 2px 0 0; color: var(--muted); font-size: var(--fs-xs); }
.st-tag {
  padding: 0 6px; border: 1px solid var(--border-strong); border-inline-start-width: 4px; border-radius: 5px;
  font-family: var(--mono); color: var(--text);
}
.st-when { display: grid; justify-items: end; font-family: var(--mono); font-size: var(--fs-xs); white-space: nowrap; }
.st-when b { font-size: var(--fs-sm); font-weight: 600; }
.st-when span { color: var(--muted); }
.st-when-now { color: var(--critical); }
.st-when-soon { color: var(--warning-fg); }
.st-when-far { color: var(--muted); }
.st-task-acts { display: flex; flex-wrap: wrap; align-items: center; justify-content: end; gap: 6px 10px; font-size: var(--fs-xs); }
.st-task-done { opacity: .55; }
.st-task-done .st-task-title { text-decoration: line-through; }
.st-task-editing { display: block; }
.st-task-editor { display: flex; flex-wrap: wrap; align-items: end; gap: 8px 10px; }
.st-task-editor .st-grow { flex: 1 1 220px; }
.st-field-narrow { inline-size: 120px; }
.st-task-editor .st-problem { flex-basis: 100%; margin: 0; }
.st-task-editor-foot { display: flex; gap: 6px; margin-inline-start: auto; }

@container (max-width: 560px) {
  .st-task { grid-template-columns: 20px minmax(0, 1fr) auto; }
  .st-task-acts { grid-column: 2 / -1; justify-content: start; }
  .st-addbar select { max-inline-size: none; flex: 1 1 40%; }
}

/* ---- Student: the rail — focus time and weekly goals (16 September 2026) ----
   Beside the dashboard once the window is wide enough to hold both; after it otherwise. */
@container (min-width: 1200px) {
  .st-cols { grid-template-columns: minmax(0, 1fr) 300px; }
  .st-rail { position: sticky; inset-block-start: 12px; }
}
.st-rail { display: grid; gap: 12px; min-inline-size: 0; scroll-margin-block-start: 12px; }
.st-rail-card { display: grid; gap: 10px; }
.st-rail-card h2 {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 8px; margin: 0;
  font-size: var(--fs-sm); font-weight: 700;
}
.st-rail-card h2 span { color: var(--muted); font-weight: 500; font-size: var(--fs-xs); }
.st-rail-card select {
  inline-size: 100%; padding: 6px 8px; border: 1px solid var(--border-strong); border-radius: 8px;
  background: var(--surface); color: var(--text); font: inherit; font-size: var(--fs-sm);
}

.st-timer { text-align: center; }
.st-timer-clock { margin: 0; font-size: 44px; font-weight: 700; line-height: 1; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.st-timer-sub { margin: 6px 0 0; color: var(--muted); font-size: var(--fs-xs); }
.st-timer-controls { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 6px; }
.st-timer-controls.st-confirm { justify-content: start; }

.st-chart {
  --st-goal-y: 55%;
  position: relative; display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px; align-items: end;
  block-size: 92px;
}
.st-chart::before {
  content: ""; position: absolute; inset-inline: 0; inset-block-end: var(--st-goal-y);
  border-block-start: 1px dashed var(--border-strong);
}
.st-bar { display: flex; align-items: end; block-size: 100%; }
.st-bar i { display: block; inline-size: 100%; border-radius: 4px 4px 2px 2px; background: color-mix(in srgb, var(--accent) 55%, transparent); }
.st-bar-today i { background: var(--accent); }
.st-bar-empty i { background: var(--surface-3); }
.st-chart-x { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px; }
.st-chart-x span { font-family: var(--mono); font-size: var(--fs-micro); color: var(--muted); text-align: center; line-height: 1.4; }
.st-chart-x span.st-bar-today { color: var(--text); font-weight: 600; }
.st-chart-legend {
  display: flex; justify-content: space-between; padding-block-start: 10px; border-block-start: 1px solid var(--border);
  color: var(--muted); font-size: var(--fs-xs);
}
.st-chart-legend b { color: var(--text); font-family: var(--mono); }

.st-goals { display: grid; margin: 0; padding: 0; list-style: none; }
.st-goal {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 8px; align-items: center;
  padding: 5px 0; border-block-start: 1px solid var(--border); font-size: var(--fs-sm);
}
.st-goal:first-child { border-block-start: 0; }
.st-goal label { display: flex; align-items: center; gap: 9px; min-inline-size: 0; cursor: pointer; }
.st-goal input { inline-size: 16px; block-size: 16px; flex: none; accent-color: var(--accent); }
.st-goal-done label span { color: var(--muted); text-decoration: line-through; }
.st-streak { font-family: var(--mono); font-size: var(--fs-xs); color: var(--warning-fg); white-space: nowrap; }
.st-streak-cold { color: var(--muted); }
.st-goal .st-x { inline-size: 24px; block-size: 24px; font-size: 16px; }
.st-goal-add { padding: 4px 6px; }

/* ---- Student: settings, and the classes day view (16 September 2026) ---- */
.st-row2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.st-honest {
  margin: 0 0 4px; padding: 8px 10px; border-radius: 9px; border: 1px dashed var(--border-strong);
  background: var(--surface-2); color: var(--text); font-size: var(--fs-xs); line-height: 1.5;
}
.st-field select:disabled { opacity: .55; }

.st-daytabs { display: flex; gap: 5px; overflow-x: auto; padding-block-end: 2px; }
.st-daytabs button {
  display: grid; justify-items: center; min-inline-size: 58px; padding: 7px 10px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--surface); color: var(--text); font: inherit; line-height: 1.25; cursor: pointer;
}
.st-daytabs b { font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: .08em; text-transform: uppercase; color: var(--muted); font-weight: 500; }
.st-daytabs s { text-decoration: none; font-size: 16px; font-weight: 700; }
.st-daytabs i { font-style: normal; font-family: var(--mono); font-size: var(--fs-micro); color: var(--muted); }
.st-daytabs button[aria-pressed="true"] { background: var(--text); border-color: var(--text); color: var(--bg); }
.st-daytabs button[aria-pressed="true"] b, .st-daytabs button[aria-pressed="true"] i { color: inherit; opacity: .8; }

.st-classlist { padding: 0; }
.st-classlist > .st-note { padding: 12px 16px; }
.st-classrow {
  display: grid; grid-template-columns: 70px 5px minmax(0, 1fr) auto; gap: 12px; align-items: center;
  padding: 11px 16px; border-block-start: 1px solid var(--border);
}
.st-classrow:first-child { border-block-start: 0; }
.st-classrow time { font-family: var(--mono); font-size: var(--fs-xs); line-height: 1.35; }
.st-classrow time small { display: block; color: var(--muted); }
.st-classcolour { align-self: stretch; min-block-size: 30px; border-radius: 3px; }
.st-classname p { margin: 0; font-size: var(--fs-sm); font-weight: 600; overflow-wrap: anywhere; }
.st-classname p.st-muted { font-weight: 400; }
.st-chip-static { cursor: default; }

@container (max-width: 520px) {
  .st-row2 { grid-template-columns: minmax(0, 1fr); }
  .st-classrow { grid-template-columns: 58px 5px minmax(0, 1fr); }
  .st-classrow > :last-child { grid-column: 3; justify-self: start; }
}

/* ---- Student: pasting a syllabus, a new term, and the record (16 September 2026) ---- */
.st-termlinks { display: inline-flex; flex-wrap: wrap; gap: 4px 10px; font-size: var(--fs-xs); }
.st-field textarea { resize: vertical; font-family: var(--mono); font-size: var(--fs-xs); line-height: 1.5; }
.st-reading { display: grid; gap: 10px; }
.st-readfields {
  display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 4px 14px; margin: 0; font-size: var(--fs-sm);
}
.st-readfields dt { color: var(--muted); }
.st-readfields dd { margin: 0; font-weight: 600; overflow-wrap: anywhere; }
.st-readparts { display: grid; margin: 0; padding: 0; list-style: none; }
.st-readparts li {
  display: flex; justify-content: space-between; gap: 12px; padding: 5px 0; border-block-start: 1px solid var(--border);
  font-size: var(--fs-sm);
}
.st-readparts li:first-child { border-block-start: 0; }

.st-carry { display: grid; gap: 6px; margin: 0; padding: 0; list-style: none; }
.st-carry label {
  display: grid; grid-template-columns: 18px minmax(0, 1fr) auto; gap: 10px; align-items: center; padding: 8px 10px;
  border: 1px solid var(--border); border-radius: 9px; background: var(--surface); color: var(--text);
  font-size: var(--fs-sm); cursor: pointer;
}
.st-carry input { accent-color: var(--accent); }
.st-carry small { display: block; color: var(--muted); font-size: var(--fs-xs); }

.st-termcard { display: grid; gap: 4px; }
.st-termcard h3 { display: flex; justify-content: space-between; gap: 10px; margin: 0 0 4px; font-size: var(--fs-md); }
.st-termrow { display: flex; justify-content: space-between; gap: 12px; margin: 0; font-size: var(--fs-sm); }
.st-termrow span:first-child { overflow-wrap: anywhere; }
.st-termfoot { margin-block-start: 6px; padding-block-start: 6px; border-block-start: 1px solid var(--border); color: var(--muted); }
.st-history .st-editor-foot a.st-btn { text-decoration: none; }
.st-termlist { display: grid; gap: 12px; }

/* ---- Student: the left rail (17 September 2026) ----
   The reference's §2 rail — 214px, full height, brand, mono section labels, nav items with a glyph
   and a count, the setup button, who is signed in, and two underlined links — drawn in NETFIVE's
   tokens rather than the reference's cream and deep green, which waits for the portal's theme.

   Below 900px of WINDOW it is a drawer behind the ☰ in Student's own top bar. Container, not media:
   Student's smallest desktop window is 680px, and the rail has to be out of the way there whatever
   the monitor is doing. */
.st-side {
  flex: none; inline-size: 214px; display: flex; flex-direction: column; min-block-size: 0;
  padding-block-start: 14px; overflow-y: auto;
  background: var(--surface-2); border-inline-end: 1px solid var(--border);
}
.st-side-brand {
  display: flex; align-items: center; gap: 9px; margin: 0; padding: 0 16px 12px;
  font-size: var(--fs-md); font-weight: 700;
}
.st-side-brand i {
  inline-size: 26px; block-size: 26px; border-radius: 7px; flex: none; display: grid; place-items: center;
  background: var(--accent-fill); color: var(--on-accent); font-style: normal; font-size: 10px; font-weight: 700;
}
.st-side-brand small {
  margin-inline-start: 2px; font-family: var(--mono); font-size: var(--fs-micro); font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
}
.st-side-label {
  margin: 12px 0 3px; padding: 0 16px; font-family: var(--mono); font-size: var(--fs-micro);
  letter-spacing: .14em; text-transform: uppercase; color: var(--faint);
}
.st-nav {
  display: flex; align-items: center; gap: 10px; inline-size: 100%; padding: 7px 16px;
  border: 0; border-inline-start: 3px solid transparent; background: none; color: var(--muted);
  font: inherit; font-size: var(--fs-sm); text-align: start; cursor: pointer;
}
.st-nav:hover { background: var(--surface-3); color: var(--text); }
.st-nav[aria-current="true"] {
  background: var(--accent-soft); border-inline-start-color: var(--accent); color: var(--accent); font-weight: 600;
}
.st-nav em { flex: none; inline-size: 15px; font-style: normal; font-size: 12px; text-align: center; opacity: .9; }
.st-nav-txt { flex: 1; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.st-nav s { text-decoration: none; font-family: var(--mono); font-size: var(--fs-micro); }

.st-side-foot {
  margin-block-start: auto; padding: 14px 16px 16px; border-block-start: 1px solid var(--border);
}
.st-setup { inline-size: 100%; }
.st-side-me { display: flex; align-items: center; gap: 9px; margin: 12px 0 0; min-inline-size: 0; }
.st-side-me i {
  inline-size: 28px; block-size: 28px; border-radius: 50%; flex: none; display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent); font-style: normal; font-size: var(--fs-xs); font-weight: 700;
}
.st-side-me > span { min-inline-size: 0; }
.st-side-me b { display: block; font-size: var(--fs-xs); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.st-side-me small { display: block; color: var(--muted); font-size: var(--fs-micro); }
.st-side-out {
  display: block; margin-block-start: 9px; padding: 0; border: 0; background: none; color: var(--muted);
  font: inherit; font-size: var(--fs-xs); text-align: start; text-decoration: underline; cursor: pointer;
}
.st-side-out:hover { color: var(--text); }

.st-navbtn { display: none; }
.st-side-dim { display: none; }

@container (max-width: 899px) {
  .st-navbtn {
    display: inline-flex; align-items: center; justify-content: center; inline-size: 34px; block-size: 34px;
    flex: none; border: 1px solid var(--border-strong); border-radius: 9px; background: var(--surface);
    color: var(--text); font: inherit; font-size: 15px; line-height: 1; cursor: pointer;
  }
  .st-navbtn:hover { background: var(--surface-3); }
  .st-side {
    position: absolute; inset-block: 0; inset-inline-start: 0; z-index: 90;
    transform: translateX(-100%); transition: transform var(--dur-2) var(--ease);
    box-shadow: 0 0 40px rgb(0 0 0 / .32);
  }
  [dir="rtl"] .st-side { transform: translateX(100%); }
  .st-side-open { transform: none; }
  .st-side-dim { display: block; position: absolute; inset: 0; z-index: 89; border: 0; background: rgb(0 0 0 / .35); }
}

/* ============================================================================
   STUDENT: PROJECTS AND STUDY GROUPS (17 September 2026)  ——  NEW BLOCK, APPENDED
   Screens 14, 22, 23, 30–35, 37 and 40 of the reference. Another session owns every .st- rule above
   this line; nothing here changes one of them.

   NETFIVE's tokens throughout — Syed's decision of 17 September: the reference's structure, ids,
   wording and behaviour apply, its cream-and-green palette and its Google fonts do not. So a project
   is --accent and a study group is --ok, both already in the theme and both already right in dark.

   Container queries, like the rest of Student: this lives in a desktop window far more often than it
   fills a screen, so a 600px window on a big monitor must lay out like a phone.
   ============================================================================ */

/* ---- the two big create buttons, and the list under them (screen 14) ---- */
.st-pj-make { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; }

@container (min-width: 680px) {
  .st-pj-make { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.st-pj-makebtn {
  display: flex; align-items: flex-start; gap: 12px; padding: 14px 16px; text-align: start;
  border: 1px solid var(--border); border-radius: 12px; background: var(--surface); color: var(--text);
  font: inherit; cursor: pointer; min-inline-size: 0;
}
.st-pj-makebtn:hover { background: var(--surface-2); border-color: var(--border-strong); }
.st-pj-makebtn > span:last-child { display: grid; gap: 3px; min-inline-size: 0; }
.st-pj-makebtn b { font-size: var(--fs-sm); }
.st-pj-makebtn small { color: var(--muted); font-size: var(--fs-xs); line-height: 1.5; }

.st-pj-makemark {
  flex: none; inline-size: 30px; block-size: 30px; border-radius: 9px;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
}
.st-pj-makemark.st-pj-study { background: color-mix(in srgb, var(--ok) 20%, transparent); }

.st-pj-list { display: grid; gap: 2px; padding: 6px 8px; }

.st-pj-row {
  display: flex; align-items: center; gap: 12px; padding: 10px 8px; border-radius: 10px; min-inline-size: 0;
}
.st-pj-row + .st-pj-row { border-block-start: 1px solid var(--border); border-radius: 0; }
.st-pj-row:hover { background: var(--surface-2); }
.st-pj-rowmain { flex: 1; min-inline-size: 0; }
.st-pj-rowname {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin: 0;
  font-size: var(--fs-sm); font-weight: 600;
}
.st-pj-rowmeta { margin: 3px 0 0; color: var(--muted); font-size: var(--fs-xs); line-height: 1.5; }
.st-pj-rowmeta em { font-style: normal; color: var(--accent); }

.st-pj-dot { flex: none; inline-size: 4px; align-self: stretch; min-block-size: 30px; border-radius: 3px; background: var(--accent); }
.st-pj-dot.st-pj-study { background: var(--ok); }

.st-pj-pill {
  padding: 2px 8px; border-radius: 999px; font-family: var(--mono); font-size: 10px; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; white-space: nowrap;
  background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent);
}
.st-pj-pill.st-pj-study { background: color-mix(in srgb, var(--ok) 18%, transparent); color: var(--ok-fg); }

/* ---- forms ----
   NO SCRIM OR MODAL RULES HERE, deliberately. A first pass of this feature grew its own
   .st-pj-scrim / .st-pj-dialog, which was Syed's width rule implemented a second time. Forms in
   this feature now use .st-scrim / .st-scrim-pane / .st-modal / .st-editor-* above, through
   student/Panel.tsx — one mechanism, one width rule, one place to change it. */
/* ---- fields, shared by every form here ---- */
.st-pj-field { display: grid; gap: 5px; min-inline-size: 0; }
.st-pj-field > span { font-size: var(--fs-xs); font-weight: 600; color: var(--text); }
/* The hint after a label is a separate remark, not part of the label: "Course · optional", never "Course optional". */
.st-pj-field > span i { font-style: normal; font-weight: 400; color: var(--muted); }
.st-pj-field > span i::before { content: "· "; }
.st-pj-field input, .st-pj-field select, .st-pj-field textarea {
  inline-size: 100%; padding: 8px 10px; border: 1px solid var(--border-strong); border-radius: 9px;
  background: var(--surface); color: var(--text); font: inherit; font-size: var(--fs-sm);
}
.st-pj-two { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; }

@container (min-width: 560px) {
  .st-pj-two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.st-pj-parts { display: grid; gap: 6px; }
.st-pj-partrow { display: flex; align-items: center; gap: 6px; }
.st-pj-partrow input { flex: 1; min-inline-size: 0; }

.st-pj-chipbox {
  display: flex; flex-wrap: wrap; align-items: center; gap: 5px; padding: 6px;
  border: 1px solid var(--border-strong); border-radius: 9px; background: var(--surface);
}
.st-pj-chipbox input { flex: 1; min-inline-size: 12ch; border: 0; padding: 4px 6px; background: transparent; }
.st-pj-chipbox input:focus { outline: none; }

.st-pj-echip {
  display: inline-flex; align-items: center; gap: 4px; padding: 3px 4px 3px 9px; border-radius: 999px;
  background: var(--surface-3); font-size: var(--fs-xs); overflow-wrap: anywhere;
}
.st-pj-echip.st-pj-bad { background: var(--critical-bg, color-mix(in srgb, var(--critical) 14%, transparent)); color: var(--critical); }
.st-pj-echip button {
  inline-size: 18px; block-size: 18px; border: 0; border-radius: 50%; background: transparent;
  color: inherit; font: inherit; line-height: 1; cursor: pointer;
}
.st-pj-echip button:hover { background: color-mix(in srgb, var(--text) 14%, transparent); }

.st-pj-linkrow { display: flex; align-items: center; gap: 8px; }
.st-pj-linkrow code {
  flex: 1; min-inline-size: 0; padding: 7px 9px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface-2); font-family: var(--mono); font-size: var(--fs-xs); overflow-wrap: anywhere;
}
.st-pj-joinline { margin: 0; font-size: var(--fs-sm); }

/* ---- the room (screens 30–34, 37) ---- */
.st-pj-room { display: grid; gap: 14px; padding: 16px 20px 32px; max-inline-size: 1180px; margin-inline: auto; }
.st-pj-back { justify-self: start; }

.st-pj-head { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 10px 16px; }
.st-pj-headmain { flex: 1; min-inline-size: 220px; }
.st-pj-headmain h1 { margin: 0; font-size: var(--fs-xl, 22px); overflow-wrap: anywhere; }
.st-pj-sub {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; margin: 5px 0 0;
  color: var(--muted); font-size: var(--fs-xs);
}
.st-pj-team { color: var(--accent); font-weight: 600; }
.st-pj-headside { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }

.st-pj-faces { display: flex; }
.st-pj-faces i {
  inline-size: 26px; block-size: 26px; margin-inline-end: -7px; border-radius: 50%; border: 2px solid var(--surface);
  display: grid; place-items: center; color: #fff; font-style: normal; font-size: 10px; font-weight: 700;
}

.st-pj-tabs {
  display: flex; gap: 2px; overflow-x: auto; border-block-end: 1px solid var(--border); scrollbar-width: none;
}
.st-pj-tabs::-webkit-scrollbar { display: none; }
.st-pj-tabs button {
  display: inline-flex; align-items: center; gap: 6px; padding: 9px 12px; border: 0;
  border-block-end: 2px solid transparent; background: transparent; color: var(--muted);
  font: inherit; font-size: var(--fs-sm); white-space: nowrap; cursor: pointer;
}
.st-pj-tabs button:hover { color: var(--text); }
.st-pj-tabs button[aria-selected="true"] { color: var(--text); font-weight: 700; border-block-end-color: var(--accent); }
.st-pj-ct {
  padding: 1px 6px; border-radius: 999px; background: var(--surface-3); color: var(--muted);
  font-family: var(--mono); font-size: 10px;
}

/* ---- the board (screen 30) ---- */
.st-pj-bar-card { display: grid; gap: 8px; }
.st-pj-bar-top { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 6px 12px; }
.st-pj-bar-top h3 { margin: 0; font-size: var(--fs-sm); overflow-wrap: anywhere; }
.st-pj-bar-top span { color: var(--muted); font-size: var(--fs-xs); }
.st-pj-bar-foot { display: flex; justify-content: space-between; gap: 12px; margin: 0; color: var(--muted); font-size: var(--fs-xs); }

.st-pj-bar { display: block; block-size: 7px; border-radius: 999px; background: var(--surface-3); overflow: hidden; }
.st-pj-bar i { display: block; block-size: 100%; background: var(--accent); }

.st-pj-parts-list { display: grid; gap: 8px; }
.st-pj-part { padding: 10px 12px; }
.st-pj-part.st-pj-done .st-pj-part-title { text-decoration: line-through; color: var(--muted); }
.st-pj-part.st-pj-open { border-style: dashed; }

.st-pj-part-main { display: flex; align-items: center; gap: 10px; min-inline-size: 0; }
.st-pj-part-text { flex: 1; min-inline-size: 0; }
.st-pj-part-title { margin: 0; font-size: var(--fs-sm); overflow-wrap: anywhere; }
.st-pj-part-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; margin: 2px 0 0;
  color: var(--muted); font-size: var(--fs-xs);
}
.st-pj-part-act { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 6px; }

.st-pj-chip {
  padding: 2px 9px; border-radius: 999px; font-size: var(--fs-xs); white-space: nowrap;
  background: var(--surface-3); color: var(--muted);
}
.st-pj-chip.st-pj-doing { background: var(--warn-bg, color-mix(in srgb, var(--warn, #9A6712) 16%, transparent)); color: var(--warn-fg, #9A6712); }
.st-pj-chip.st-pj-done { background: var(--ok-bg); color: var(--ok-fg); }

.st-pj-av {
  flex: none; inline-size: 26px; block-size: 26px; border-radius: 50%; display: grid; place-items: center;
  background: var(--surface-3); color: #fff; font-size: 10px; font-weight: 700;
}
/* Nobody has claimed it: an outline rather than a filled circle, so an empty seat does not read as a person. */
.st-pj-av.st-pj-nobody { background: transparent; border: 1px dashed var(--border-strong); color: var(--muted); }

.st-pj-thread { margin-block-start: 10px; padding-block-start: 10px; border-block-start: 1px solid var(--border); display: grid; gap: 8px; }
.st-pj-comment { display: flex; gap: 8px; }
.st-pj-comment p { margin: 0; font-size: var(--fs-sm); line-height: 1.5; overflow-wrap: anywhere; }
.st-pj-comment small { color: var(--muted); font-size: var(--fs-xs); }

.st-pj-addbar { display: flex; gap: 8px; align-items: center; }
.st-pj-addbar input {
  flex: 1; min-inline-size: 0; padding: 8px 10px; border: 1px solid var(--border-strong); border-radius: 9px;
  background: var(--surface); color: var(--text); font: inherit; font-size: var(--fs-sm);
}

/* ---- chat (screen 31) ---- */
.st-pj-chat { display: flex; flex-direction: column; gap: 10px; min-block-size: 320px; }
.st-pj-msgs { flex: 1; display: grid; align-content: start; gap: 12px; max-block-size: 52vh; overflow-y: auto; }
.st-pj-msg { display: flex; gap: 9px; min-inline-size: 0; }
.st-pj-msg > div { min-inline-size: 0; }
.st-pj-msg-who { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; margin: 0; font-size: var(--fs-xs); }
.st-pj-msg-who small { color: var(--muted); font-family: var(--mono); }
.st-pj-msg-body {
  margin: 3px 0 0; padding: 7px 10px; border-radius: 10px; background: var(--surface-2);
  font-size: var(--fs-sm); line-height: 1.5; overflow-wrap: anywhere;
}
.st-pj-msg.st-pj-mine .st-pj-msg-body { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.st-pj-topart { margin-block-start: 4px; font-size: var(--fs-xs); }
.st-pj-chatnote { margin: 0; }

/* ---- files (screen 32) ---- */
.st-pj-files { display: grid; gap: 2px; }
.st-pj-file { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; padding: 9px 2px; }
.st-pj-file + .st-pj-file { border-block-start: 1px solid var(--border); }
.st-pj-file-text { flex: 1; min-inline-size: 140px; }
.st-pj-file-name { margin: 0; font-size: var(--fs-sm); overflow-wrap: anywhere; }
.st-pj-file-meta { margin: 2px 0 0; color: var(--muted); font-size: var(--fs-xs); }
.st-pj-fico {
  flex: none; padding: 4px 7px; border-radius: 7px; background: var(--surface-3); color: var(--muted);
  font-family: var(--mono); font-size: 10px; font-weight: 600;
}
.st-pj-hint { margin: 0; font-size: var(--fs-xs); }
.st-pj-drop {
  padding: 18px; border: 1px dashed var(--border-strong); border-radius: 12px; background: var(--surface-2);
  color: var(--muted); font-size: var(--fs-sm); text-align: center;
}

/* ---- notes (screen 33) ---- */
.st-pj-notesplit { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; align-items: start; }

@container (min-width: 760px) {
  .st-pj-notesplit { grid-template-columns: 230px minmax(0, 1fr); }
}

.st-pj-notelist { display: grid; gap: 2px; padding: 6px; align-content: start; }
.st-pj-noteitem {
  display: grid; gap: 2px; padding: 8px 10px; border: 0; border-radius: 9px; background: transparent;
  color: inherit; font: inherit; text-align: start; cursor: pointer;
}
.st-pj-noteitem:hover { background: var(--surface-2); }
.st-pj-noteitem.st-pj-on { background: var(--surface-3); }
.st-pj-noteitem b { font-size: var(--fs-sm); overflow-wrap: anywhere; }
.st-pj-noteitem small { color: var(--muted); font-family: var(--mono); font-size: var(--fs-xs); }

.st-pj-noteedit { display: grid; gap: 9px; align-content: start; min-block-size: 300px; }
.st-pj-notetitle {
  inline-size: 100%; padding: 6px 8px; border: 1px solid transparent; border-radius: 8px; background: transparent;
  color: var(--text); font: inherit; font-size: var(--fs-md); font-weight: 700;
}
.st-pj-notetitle:hover, .st-pj-notetitle:focus { border-color: var(--border-strong); background: var(--surface); }
.st-pj-noteedit textarea {
  inline-size: 100%; min-block-size: 220px; padding: 10px 12px; border: 1px solid var(--border-strong);
  border-radius: 10px; background: var(--surface-2); color: var(--text); font: inherit; font-size: var(--fs-sm);
  line-height: 1.6; resize: vertical;
}

/* ---- people (screen 34) ---- */
.st-pj-people { display: grid; gap: 2px; }
.st-pj-member { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; padding: 9px 2px; }
.st-pj-member + .st-pj-member { border-block-start: 1px solid var(--border); }
.st-pj-member.st-pj-pending { opacity: .72; }
.st-pj-member-text { flex: 1; min-inline-size: 140px; }
.st-pj-member-text p { margin: 0; font-size: var(--fs-sm); overflow-wrap: anywhere; }
.st-pj-member-text small { color: var(--muted); font-size: var(--fs-xs); }
.st-pj-member-act { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 6px; }
.st-pj-member-act select {
  padding: 5px 8px; border: 1px solid var(--border-strong); border-radius: 8px; background: var(--surface);
  color: var(--text); font: inherit; font-size: var(--fs-xs);
}
.st-pj-role { font-family: var(--mono); font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .05em; }

.st-pj-manage { display: grid; gap: 10px; }
.st-pj-manage h3 { margin: 0; font-size: var(--fs-sm); }
.st-pj-manage-act { display: flex; flex-wrap: wrap; gap: 6px; }

/* ---- the rail (deadline, contributions, recent) ---- */
.st-pj-bignum { margin: 0; font-size: 38px; font-weight: 700; letter-spacing: -.03em; line-height: 1; }
.st-pj-contrib { display: flex; align-items: center; gap: 8px; }
.st-pj-contrib .st-pj-bar { flex: 1; }
.st-pj-feed { display: flex; gap: 8px; align-items: flex-start; }
.st-pj-feed + .st-pj-feed { margin-block-start: 9px; padding-block-start: 9px; border-block-start: 1px solid var(--border); }
.st-pj-feed p { margin: 0; color: var(--muted); font-size: var(--fs-xs); line-height: 1.5; overflow-wrap: anywhere; }
.st-pj-feed b { color: var(--text); }
.st-pj-feed small { display: block; margin-block-start: 2px; font-family: var(--mono); }

/* Nothing scrolls sideways at 390px, and every tap target stays reachable. */
@container (max-width: 480px) {
  .st-pj-room { padding-inline: 14px; }
  .st-pj-headside { inline-size: 100%; }
  .st-pj-part-main { flex-wrap: wrap; }
  .st-pj-part-act { inline-size: 100%; justify-content: flex-start; }
  .st-pj-msgs { max-block-size: none; }
}

/* ---- Student: the bell and its panel (the reference's screen 16, 18 September 2026) -------------------
   Added at the END of the st-* block, as a marked run, so what this night added can be read in one
   piece and reverted in one piece. Colours are NETFIVE's tokens throughout (Syed, 18 September): the
   reference's cream-and-green palette is not applied here, only its layout and its element ids. */
.st-bellbox { position: relative; display: flex; align-items: center; }
.st-bell {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  inline-size: 34px; block-size: 34px; padding: 0;
  border: 1px solid var(--border); border-radius: 9px;
  background: var(--surface); color: var(--text); cursor: pointer;
}
.st-bell:hover { background: var(--surface-2); }
.st-bell:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.st-bell-ico { inline-size: 18px; block-size: 18px; }

/* The dot is always in the markup and hidden when there is nothing — the reference toggles a class on
   an element its checks look up by id, so removing it would take #bellDot off the page. */
.st-bell-dot {
  position: absolute; inset-block-start: 5px; inset-inline-end: 5px;
  inline-size: 8px; block-size: 8px; border-radius: 50%;
  background: var(--critical); box-shadow: 0 0 0 2px var(--surface);
}
.st-bell-dot[hidden] { display: none; }

.st-notifpanel {
  position: absolute; inset-block-start: calc(100% + 8px); inset-inline-end: 0; z-index: 40;
  inline-size: min(370px, calc(100cqi - 28px));
  max-block-size: 60vh; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border); border-radius: 13px;
  box-shadow: 0 12px 34px rgb(0 0 0 / 22%);
}
.st-notif-head {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 15px; border-block-end: 1px solid var(--border); font-size: var(--fs-sm);
}
.st-notif-head b { flex: 1; }
.st-notif-head button:disabled { opacity: .45; cursor: default; text-decoration: none; }
.st-notif-empty { margin: 0; padding: 22px 18px; color: var(--muted); font-size: var(--fs-sm); line-height: 1.6; text-align: center; }

.st-notif-list { display: flex; flex-direction: column; }
.st-nrow {
  display: grid; grid-template-columns: 10px 1fr; gap: 11px; align-items: start;
  padding: 11px 15px; border: 0; border-block-start: 1px solid var(--border);
  background: none; color: inherit; font: inherit; text-align: start; cursor: pointer;
}
.st-nrow:first-child { border-block-start: 0; }
.st-nrow:hover { background: var(--surface-2); }
.st-nrow:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Unread carries the accent tint, not --surface-2: on the light theme --surface-2 sits a shade off
   white, and a row that differs from its neighbour by a shade nobody can see is not marked at all. */
.st-nrow-unread { background: var(--accent-soft); }
.st-nrow-unread:hover { background: var(--accent-soft); box-shadow: inset 0 0 0 99em rgb(0 0 0 / 4%); }

/* The mark carries the CATEGORY, not the wording — see toneOf in student/bell.ts. */
.st-nmark { inline-size: 8px; block-size: 8px; margin-block-start: 6px; border-radius: 50%; background: var(--muted); }
.st-nmark-invite { background: #5F4FA8; }
.st-nmark-claim { background: #9A6712; }
.st-nmark-done { background: #1F6B45; }
.st-nmark-comment { background: var(--accent); }
.st-nmark-due { background: var(--critical); }

.st-nbody { display: block; min-inline-size: 0; font-size: var(--fs-sm); line-height: 1.5; }
.st-nbody b { display: block; overflow-wrap: anywhere; }
.st-nbody span { display: block; margin-block-start: 2px; color: var(--muted); overflow-wrap: anywhere; }
.st-nbody time { display: block; margin-block-start: 3px; font-family: var(--mono); font-size: 10px; color: var(--muted); }

/* At 390px the panel is the width of the window less its gutters, and still never scrolls sideways. */
@container (max-width: 480px) {
  .st-notifpanel { inline-size: min(340px, calc(100cqi - 24px)); }
}
/* ---- end of the Student bell block ------------------------------------------------------------------ */

/* ============================================================================
   CALENDAR — rebuilt to Syed's calendar-kit (18 September 2026)
   Week / Month / Year under one toolbar and one legend. The kit's ten tokens are mapped onto the house
   theme rather than shipped as a second palette (its README asks for exactly that): tasks take the
   accent, so they follow whichever accent somebody has chosen; classes and Microsoft 365 meetings have
   a colour each, light and dark, so the legend's three swatches can never be two of the same.

   Three ink levels (the kit's seventh fix), and secondary days told apart by their GROUND, never by
   greying their numbers: weekends take --ca-we, the days a month borrows take --ca-out. Grey text on
   white reads as disabled; full-contrast text on a tinted day reads as secondary.

   Today is a filled chip in all three views (the fourth fix) — the one rule, .ca-today, below.
   ============================================================================ */
/* Calendar is one of the desk's basic apps, so it takes the shared stone palette and type too (Syed, 19 September:
   netfive-layout-fix's tokens.css), here in its own page rather than from the desk's .dk: the product's names are
   given his values inside the calendar only, and the rest of the product keeps its own. */
.ca-mount {
  --surface: #FFFFFF;
  --surface-2: #FAFAF9;
  --surface-3: #F0EFED;
  --surface-4: #E7E5E4;
  --border: #E7E5E4;
  --border-strong: #D6D3D1;
  --text: #1C1917;
  --muted: #78716C;
  --font: 'Instrument Sans', ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, "Cascadia Mono", Consolas, monospace;
  --ca-ink:        var(--text);
  --ca-ink-2:      color-mix(in srgb, var(--text) 80%, var(--surface));
  --ca-ink-3:      var(--muted);
  --ca-line:       var(--border);
  --ca-line-strong:var(--border-strong);
  --ca-we:         var(--surface-2);
  --ca-out:        var(--surface-3);
  --ca-task:       var(--accent);
  --ca-task-soft:  var(--accent-soft);
  --ca-klass:      #8a5000;
  --ca-klass-soft: #faeed8;
  --ca-evt:        #0b6157;
  --ca-evt-soft:   #dcf0ec;
  --ca-now:        #c2372c;

  /* Our own events, apart from Microsoft own (21 September 2026), and the hint an empty hour gives. */
  --ca-mine:       #3f4fbf;
  --ca-mine-soft:  #e7eaff;
  --ca-add-hint:   color-mix(in srgb, var(--accent) 7%, transparent);

  container-type: inline-size; min-block-size: 100dvh;
  background: var(--surface); color: var(--ca-ink); font-family: var(--font);
}
/* Dark: the house's dark surfaces run DOWN from the card, so the tints step up instead, or a weekend would be the
   same black as a weekday. */
[data-theme="dark"] .ca-mount {
  --surface: #1C1917;
  --surface-2: #231F1D;
  --surface-3: #2A2624;
  --surface-4: #332F2C;
  --border: #332F2C;
  --border-strong: #44403C;
  --text: #FAFAF9;
  --muted: #A8A29E;
  --ca-ink-2:      color-mix(in srgb, var(--text) 78%, var(--surface));
  --ca-we:         var(--surface-3);
  --ca-out:        var(--surface-4);
  --ca-klass:      #f0b25c;
  --ca-klass-soft: #302514;
  --ca-evt:        #5fcfbd;
  --ca-evt-soft:   #12302c;
  --ca-now:        #ff6b5c;
  --ca-mine:       #9aa6ff;
  --ca-mine-soft:  #1d2350;
  --ca-add-hint:   color-mix(in srgb, var(--accent) 12%, transparent);
}

.ca-app { display: flex; flex-direction: column; block-size: 100dvh; min-block-size: 0; }
.ca-grow { flex: 1; }
.ca-flip { transform: scaleX(-1); }
[dir="rtl"] .ca-navgroup svg { transform: scaleX(-1); }
[dir="rtl"] .ca-navgroup .ca-flip { transform: none; }

/* ---- toolbar: the period on the left, the view and the arrows on the right ---- */
.ca-toolbar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 13px 16px; border-block-end: 1px solid var(--ca-line);
}
.ca-period { display: flex; flex-direction: column; min-inline-size: 170px; }
.ca-period-main {
  margin: 0; font-size: var(--fs-xl); font-weight: 600; letter-spacing: -.01em; line-height: 1.2;
  font-variant-numeric: tabular-nums;
}
.ca-period-sub { font-size: var(--fs-xs); color: var(--ca-ink-3); }

.ca-segmented { display: inline-flex; gap: 2px; padding: 2px; border-radius: 8px; background: var(--surface-3); }
.ca-segmented button {
  border: 0; background: transparent; cursor: pointer; padding: 6px 15px; border-radius: 6px;
  font: inherit; font-size: var(--fs-md); font-weight: 500; color: var(--ca-ink-2);
}
.ca-segmented button:hover { color: var(--ca-ink); }
/* The chosen one stays chosen under the pointer: this rule outranks the hover above. */
.ca-segmented button[aria-pressed="true"] {
  background: var(--surface); color: var(--ca-ink); font-weight: 600; box-shadow: 0 1px 2px rgba(18, 24, 32, .12);
}

.ca-navgroup { display: inline-flex; border: 1px solid var(--ca-line); border-radius: 8px; overflow: hidden; }
.ca-navgroup button {
  display: grid; place-items: center; border: 0; background: var(--surface); cursor: pointer;
  padding: 6px 10px; font: inherit; font-size: var(--fs-md); color: var(--ca-ink-2);
}
.ca-navgroup button:hover { background: var(--surface-2); color: var(--ca-ink); }
.ca-navgroup .ca-nav-today {
  padding-inline: 14px; font-weight: 600; color: var(--ca-ink);
  border-inline: 1px solid var(--ca-line);
}

/* ---- legend: what is on screen, and Microsoft 365's state with its exit ---- */
.ca-legend {
  display: flex; align-items: center; gap: 8px 18px; flex-wrap: wrap;
  padding: 10px 16px; border-block-end: 1px solid var(--ca-line); background: var(--surface-2);
}
.ca-legend-item { display: inline-flex; align-items: center; gap: 7px; font-size: var(--fs-sm); color: var(--ca-ink-2); }
.ca-count { font-family: var(--mono); font-size: var(--fs-xs); color: var(--ca-ink-3); font-variant-numeric: tabular-nums; }
.ca-legend-note { font-size: var(--fs-xs); color: var(--ca-ink-3); }
.ca-swatch { flex: none; inline-size: 9px; block-size: 9px; border-radius: 3px; }
.ca-swatch-task { background: var(--ca-task); }
.ca-swatch-class { background: var(--ca-klass); }
.ca-swatch-event { background: var(--ca-evt); }
/* Present, not yet showing anything: an outline where the colour will be. */
.ca-swatch-waiting { background: transparent; border: 1.5px dashed var(--ca-line-strong); }
.ca-connect {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  padding: 4px 11px; border-radius: 7px; border: 1px solid var(--accent-line); background: transparent;
  font: inherit; font-size: var(--fs-sm); font-weight: 600; color: var(--accent);
}
.ca-connect:hover { background: var(--accent-soft); }

.ca-viewport { flex: 1; min-block-size: 0; overflow: auto; padding: 16px; background: var(--surface); }
/* The week and the month fill the window: the week scrolls its hours, not the page, so the day names and the Due row
   stay put; the month shares the height between six equal rows, so it is the same height every month. */
.ca-viewport-fill { display: flex; flex-direction: column; }

/* ---- the one empty state, and the one trouble state ---- */
.ca-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  min-block-size: 100%; padding: 56px 24px; text-align: center;
}
.ca-empty-ring {
  display: grid; place-items: center; inline-size: 44px; block-size: 44px; border-radius: 12px;
  background: var(--surface-3); color: var(--ca-ink-3);
}
.ca-empty h2 { margin: 0; font-size: var(--fs-lg); font-weight: 600; }
.ca-empty p { margin: 0; max-inline-size: 42ch; font-size: var(--fs-md); color: var(--ca-ink-2); }
.ca-empty-btn {
  margin-block-start: 4px; cursor: pointer; padding: 8px 16px; border: 0; border-radius: 7px;
  background: var(--accent-fill); color: var(--on-accent); font: inherit; font-size: var(--fs-md); font-weight: 600;
}
.ca-empty-btn:hover { background: var(--accent-fill-hover); }

/* Nothing in the period on screen: one line above the calendar, never in its place (Syed, 19 September). */
.ca-empty-note {
  display: flex; align-items: center; gap: 12px; flex-shrink: 0; margin-block-end: 12px; padding: 10px 14px;
  border: 1px solid var(--ca-line); border-radius: 8px; background: var(--surface-2);
}
.ca-empty-note .ca-empty-ring { inline-size: 32px; block-size: 32px; border-radius: 9px; flex-shrink: 0; }
.ca-empty-words { flex: 1; min-inline-size: 0; display: grid; gap: 2px; }
.ca-empty-note h2 { margin: 0; font-size: var(--fs-md); font-weight: 600; }
.ca-empty-note p { margin: 0; font-size: var(--fs-sm); color: var(--ca-ink-2); }
.ca-empty-note .ca-empty-btn { margin: 0; flex-shrink: 0; padding: 6px 12px; font-size: var(--fs-sm); }

/* ---- a thing on a day: a small bar in its kind's colour ---- */
.ca-chip {
  display: flex; align-items: center; gap: 5px; min-inline-size: 0;
  padding: 2px 5px; border-radius: 4px; border-inline-start: 2.5px solid currentColor;
  font-size: var(--fs-xs); line-height: 1.35; text-decoration: none;
}
.ca-chip-t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ca-ink); }
.ca-chip-time { flex: none; font-family: var(--mono); font-size: 10px; color: var(--ca-ink-3); }
.ca-chip-task { color: var(--ca-task); background: var(--ca-task-soft); }
.ca-chip-class { color: var(--ca-klass); background: var(--ca-klass-soft); }
.ca-chip-event { color: var(--ca-evt); background: var(--ca-evt-soft); }
a.ca-chip:hover .ca-chip-t { text-decoration: underline; }

/* ---- YEAR: twelve cards, four by three, every card six rows of seven ---- */
.ca-year { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 11px; }
.ca-y-card {
  display: flex; flex-direction: column; gap: 5px; padding: 9px 9px 7px;
  border: 1px solid var(--ca-line); border-radius: 8px; background: var(--surface);
}
/* The eye lands on the right card before it looks for the right day. */
.ca-y-current { border-color: var(--accent-line); background: var(--accent-soft); }
.ca-y-title { display: flex; align-items: baseline; gap: 6px; margin: 0; font-size: var(--fs-sm); font-weight: 600; }
.ca-y-name {
  margin-inline-start: -4px; padding: 1px 4px; border: 0; border-radius: 5px; background: transparent; cursor: pointer;
  font: inherit; color: var(--ca-ink);
}
.ca-y-name:hover { background: var(--surface-3); }
.ca-y-count { margin-inline-start: auto; font-family: var(--mono); font-size: 10px; font-weight: 500; color: var(--ca-ink-3); }
.ca-y-dow, .ca-y-days { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.ca-y-dow span {
  text-align: center; font-family: var(--mono); font-size: 9.5px; font-weight: 500;
  letter-spacing: .04em; text-transform: uppercase; color: var(--ca-ink-3);
}
.ca-y-cell {
  position: relative; display: grid; place-items: center; block-size: 23px; padding: 0;
  border: 0; border-radius: 5px; background: transparent; cursor: pointer;
  font-family: var(--mono); font-size: 11.5px; font-variant-numeric: tabular-nums; color: var(--ca-ink-2);
}
.ca-y-cell.ca-we { color: var(--ca-ink-3); }
.ca-y-cell:hover { background: var(--surface-3); }
.ca-y-blank { color: transparent; cursor: default; }
.ca-y-blank:hover { background: transparent; }
.ca-dot { position: absolute; inset-block-end: 2.5px; inset-inline-start: 50%; translate: -50% 0; border-radius: 99px; opacity: .7; }
[dir="rtl"] .ca-dot { translate: 50% 0; }
.ca-dot-full { opacity: 1; }
.ca-dot-task { background: var(--ca-task); }
.ca-dot-class { background: var(--ca-klass); }
.ca-dot-event { background: var(--ca-evt); }

/* ---- MONTH: always 42 days, so paging never changes its height ---- */
.ca-month {
  flex: 1; display: flex; flex-direction: column; min-block-size: calc(6 * 92px + 32px);
  border: 1px solid var(--ca-line); border-radius: 8px; overflow: hidden;
}
.ca-m-head {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  background: var(--surface-2); border-block-end: 1px solid var(--ca-line);
}
.ca-m-head span {
  padding: 7px 9px; font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ca-ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Six equal rows whatever is in them. A busy day never stretches its row: what does not fit is "+n more". */
.ca-m-body {
  flex: 1; min-block-size: 0; display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr)); grid-template-rows: repeat(6, minmax(0, 1fr));
}
.ca-m-cell {
  display: flex; flex-direction: column; gap: 3px; min-inline-size: 0; min-block-size: 0; overflow: hidden;
  padding: 5px 6px; cursor: pointer;
  border-inline-end: 1px solid var(--ca-line); border-block-end: 1px solid var(--ca-line);
}
.ca-m-cell:nth-child(7n) { border-inline-end: 0; }
.ca-m-body .ca-m-cell:nth-last-child(-n + 7) { border-block-end: 0; }
.ca-m-cell.ca-we { background: var(--ca-we); }
.ca-m-cell.ca-out { background: var(--ca-out); }
.ca-m-num {
  align-self: flex-start; padding: 1px 5px; border: 0; border-radius: 99px; background: transparent; cursor: pointer;
  font-family: var(--mono); font-size: 12px; font-weight: 500; line-height: 1.5; font-variant-numeric: tabular-nums;
  color: var(--ca-ink-2);
}
.ca-m-num:hover { background: var(--surface-3); color: var(--ca-ink); }
.ca-m-cell.ca-out .ca-m-num { color: var(--ca-ink-3); }
.ca-m-cell.ca-today { background: var(--accent-soft); }
.ca-more {
  align-self: flex-start; padding: 0 5px; border: 0; background: transparent; cursor: pointer; white-space: nowrap;
  font: inherit; font-size: 11px; font-weight: 500; color: var(--ca-ink-3);
}
.ca-more:hover { color: var(--ca-ink); text-decoration: underline; }

/* ---- WEEK: a Due row above an hour grid ---- */
.ca-week {
  flex: 1; min-block-size: 0; display: flex; flex-direction: column;
  border: 1px solid var(--ca-line); border-radius: 8px; overflow: hidden;
}
.ca-w-head, .ca-w-allday, .ca-w-grid { display: grid; grid-template-columns: 58px repeat(7, minmax(0, 1fr)); }
.ca-w-head { background: var(--surface-2); border-block-end: 1px solid var(--ca-line); }
.ca-w-corner { border-inline-end: 1px solid var(--ca-line); }
.ca-w-day {
  display: flex; align-items: baseline; gap: 7px; min-inline-size: 0; padding: 7px 9px;
  border-inline-end: 1px solid var(--ca-line);
}
.ca-w-day:last-child { border-inline-end: 0; }
.ca-w-wd { font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--ca-ink-3); }
.ca-w-num { font-family: var(--mono); font-size: 15px; font-weight: 500; font-variant-numeric: tabular-nums; color: var(--ca-ink); }
.ca-w-day.ca-today .ca-w-wd { color: var(--accent); }
/* A day with a pile of things due scrolls its own row rather than pushing the hours off the window. */
.ca-w-allday {
  min-block-size: 34px; max-block-size: 132px; overflow-y: auto;
  border-block-end: 1px solid var(--ca-line); background: var(--surface);
}
.ca-w-lbl {
  display: flex; align-items: center; justify-content: flex-end; padding: 0 7px;
  border-inline-end: 1px solid var(--ca-line);
  font-size: 10.5px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; color: var(--ca-ink-3);
}
.ca-w-slot {
  display: flex; flex-direction: column; gap: 3px; min-inline-size: 0; padding: 4px 5px;
  border-inline-end: 1px solid var(--ca-line);
}
.ca-w-slot:last-child { border-inline-end: 0; }
.ca-w-scroll { flex: 1; min-block-size: 0; overflow-y: auto; }
.ca-w-grid { position: relative; }
.ca-w-gutter { border-inline-end: 1px solid var(--ca-line); }
.ca-w-hour {
  position: relative; block-size: 48px; padding-inline-end: 7px; text-align: end;
  font-family: var(--mono); font-size: 10.5px; color: var(--ca-ink-3);
}
.ca-w-hour span { position: relative; inset-block-start: -7px; }
.ca-w-hour:first-child span { inset-block-start: 0; }
.ca-w-col { position: relative; min-inline-size: 0; border-inline-end: 1px solid var(--ca-line); }
.ca-w-col:last-child { border-inline-end: 0; }
.ca-w-col.ca-we { background: var(--ca-we); }
.ca-w-col.ca-w-todaycol { background: var(--accent-soft); }
.ca-w-line { block-size: 48px; border-block-end: 1px solid var(--ca-line); }
.ca-w-evt {
  position: absolute; display: flex; flex-direction: column; overflow: hidden;
  padding: 3px 6px; border-radius: 5px; border-inline-start: 3px solid currentColor; text-decoration: none;
}
.ca-w-evt-class { color: var(--ca-klass); background: var(--ca-klass-soft); }
.ca-w-evt-event { color: var(--ca-evt); background: var(--ca-evt-soft); }
.ca-w-evt-task { color: var(--ca-task); background: var(--ca-task-soft); }
.ca-w-evt-t {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-xs); font-weight: 600; line-height: 1.25; color: var(--ca-ink);
}
.ca-w-evt-s {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--mono); font-size: 10px; color: var(--ca-ink-2);
}
a.ca-w-evt:hover .ca-w-evt-t { text-decoration: underline; }
.ca-nowline { position: absolute; inset-inline: 0; z-index: 3; block-size: 0; border-block-start: 2px solid var(--ca-now); pointer-events: none; }
.ca-nowline::before {
  content: ""; position: absolute; inset-inline-start: -4px; inset-block-start: -4.5px;
  inline-size: 7px; block-size: 7px; border-radius: 99px; background: var(--ca-now);
}


/* ---- the calendar's own events, and what you can do to a day (Syed, 21 September 2026) ----------------------- */

/* Our own events, told apart from Microsoft's by colour as well as by the legend. */
.ca-swatch-mine { background: var(--ca-mine); }
.ca-chip-mine { color: var(--ca-mine); background: var(--ca-mine-soft); }
.ca-w-evt-mine { color: var(--ca-mine); background: var(--ca-mine-soft); }

/* New event: the thing this calendar could not do until today, so it is a real button and not a quiet one. */
.ca-new {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border: 0; border-radius: 8px;
  background: var(--accent-fill); color: var(--on-accent);
  font: inherit; font-size: var(--fs-md); font-weight: 600; cursor: pointer;
}
.ca-new:hover { filter: brightness(1.06); }
.ca-new svg { inline-size: 14px; block-size: 14px; }

/* Twelve hours or twenty-four. Narrow, because it says one short word. */
.ca-clock {
  padding: 6px 9px; border: 1px solid var(--ca-line); border-radius: 8px;
  background: var(--surface); color: var(--ca-ink-2);
  font: inherit; font-size: var(--fs-sm); font-weight: 600; cursor: pointer;
}
.ca-clock:hover { color: var(--ca-ink); }
.ca-clock[aria-pressed="true"] { border-color: var(--ca-line-strong); color: var(--ca-ink); }

/* A legend word that leads somewhere: Tasks to Tasks, Classes to where classes are actually made. */
.ca-legend-go { text-decoration: none; border-radius: 6px; padding: 2px 5px; margin-inline: -5px; }
.ca-legend-go:hover { background: var(--surface-2); color: var(--ca-ink); }

/*
 * EMPTY SPACE IS A BUTTON. Behind everything in its cell and carrying no ink of its own, so an hour looks exactly
 * as it did and a chip's own press is never swallowed by the day's.
 */
.ca-w-add {
  position: absolute; inset: 0; z-index: 0;
  padding: 0; border: 0; background: transparent; cursor: pointer;
  appearance: none; font: inherit; color: inherit;
}
.ca-w-add:hover { background: var(--ca-add-hint); }
.ca-w-add:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* The hour lines keep their border and become the button. */
.ca-w-line.ca-w-add { position: absolute; inset-inline: 0; }
.ca-w-slot { position: relative; }
.ca-w-slot > .ca-chip, .ca-w-slot > .ca-chip-doing { position: relative; z-index: 1; }

/* The now line says what time it is, rather than leaving somebody to count rows. */
.ca-nowtime {
  position: absolute; inset-inline-start: 2px; inset-block-start: -9px;
  padding: 0 4px; border-radius: 4px;
  background: var(--ca-now); color: #fff;
  font-size: var(--fs-micro); font-weight: 700; line-height: 1.5; white-space: nowrap;
}

/* A task on the calendar: ticked and renamed where it stands. */
.ca-chip-doing { display: flex; align-items: center; gap: 6px; }
.ca-chip-tick { flex: none; inline-size: 13px; block-size: 13px; margin: 0; accent-color: var(--ca-task); cursor: pointer; }
.ca-chip-open {
  flex: 1; min-inline-size: 0; display: flex; align-items: center; gap: 6px;
  padding: 0; border: 0; background: transparent; color: inherit;
  font: inherit; text-align: start; cursor: pointer;
}
.ca-chip-name {
  flex: 1; min-inline-size: 0; padding: 0 3px;
  border: 1px solid var(--ca-line-strong); border-radius: 4px;
  background: var(--surface); color: var(--ca-ink);
  font: inherit; font-size: inherit;
}

/*
 * THE SCROLLBAR, SEEN (Syed, 21 September 2026: "not clearly visible until we click it").
 *
 * And its width RESERVED above, which is the other half of the same bug: only the hour grid scrolls, so on any
 * platform whose scrollbar takes room the grid is that many pixels narrower than the two rows above it and all
 * seven columns drift out of line. --ca-sb is measured in the browser and set on .ca-app, because no stylesheet
 * can know what a platform's scrollbar costs.
 */
.ca-w-head, .ca-w-allday { padding-inline-end: var(--ca-sb, 0px); }
.ca-w-scroll { scrollbar-color: var(--ca-line-strong) transparent; scrollbar-width: auto; }
.ca-w-scroll::-webkit-scrollbar { inline-size: 12px; }
.ca-w-scroll::-webkit-scrollbar-track { background: var(--surface-2); }
.ca-w-scroll::-webkit-scrollbar-thumb {
  background: var(--ca-line-strong); border-radius: 99px;
  border: 3px solid var(--surface-2);
}
.ca-w-scroll::-webkit-scrollbar-thumb:hover { background: var(--ca-ink-3); }

/* ---- making and changing an event -------------------------------------------------------------------------- */

/* POSITIONED, AND THAT IS NOT DECORATION: .palette-back is absolutely positioned with no z-index, so it paints
   over any static sibling however late that sibling comes in the markup. A static box here left the backdrop
   lying across the form - every field reachable by keyboard and nothing clickable, Save included. */
/* THE SAVE BUTTON USED TO SIT OUTSIDE THE WINDOW (Syed, 23 September 2026, found in the bug sweep:
   pressing Save, Cancel or Delete did nothing at all - no request, no message, the dialog just sat there).

   Nothing was wrong with the form. Enter in the name field saved perfectly and always had; it was only
   the MOUSE that was locked out, because the buttons were not on screen to be clicked. .palette
   reserves 12vh above the panel and .palette-box is overflow: hidden with no cap, so in a Calendar
   window 444px tall the panel ran 53px to 535px and the button row at 484-517 was simply past the
   bottom edge, with nothing anywhere able to scroll to it. Maximising the window was the only way to
   reach it.

   max-block-size: 100% rather than a vh figure, because the panel is a flex item of .palette and a
   percentage there resolves against the space actually left INSIDE that 12vh padding. A guess like
   88vh ignores the padding and is wrong by exactly the amount that caused this.

   The button row then sticks to the bottom of the scroller, so it is reachable at any height rather
   than merely reachable after scrolling - a dialog whose buttons scroll away is the same complaint in
   a politer form. */
.ca-dlg { position: relative; display: grid; gap: 8px; inline-size: min(460px, 92vw); padding: 16px; }
.palette-box.ca-dlg { max-block-size: 100%; overflow-y: auto; overscroll-behavior: contain; }
.ca-dlg h2 { margin: 0 0 2px; font-size: var(--fs-lg); }
.ca-dlg-lab { font-size: var(--fs-sm); font-weight: 600; color: var(--ca-ink-2); }
.ca-dlg-row { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }
.ca-dlg-field { display: grid; gap: 4px; flex: 1; min-inline-size: 120px; }
.ca-dlg-check { display: inline-flex; align-items: center; gap: 7px; font-size: var(--fs-sm); white-space: nowrap; padding-block-end: 7px; }
.ca-dlg-hint { font-size: var(--fs-xs); color: var(--ca-ink-3); }

.ca-dlg-quick { display: flex; gap: 6px; flex-wrap: wrap; }
.ca-dlg-pill {
  padding: 5px 11px; border: 1px solid var(--ca-line); border-radius: 99px;
  background: var(--surface); color: var(--ca-ink-2); font: inherit; font-size: var(--fs-sm); cursor: pointer;
}
.ca-dlg-pill[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); font-weight: 600; }

.ca-dlg-days { display: flex; gap: 4px; }
.ca-dlg-day {
  inline-size: 34px; block-size: 34px; border: 1px solid var(--ca-line); border-radius: 8px;
  background: var(--surface); color: var(--ca-ink-2); font: inherit; font-size: var(--fs-sm); cursor: pointer;
}
.ca-dlg-day[aria-pressed="true"] { background: var(--accent-fill); border-color: var(--accent-fill); color: var(--on-accent); font-weight: 700; }

.ca-dlg-bad { margin: 0; font-size: var(--fs-sm); color: var(--critical); }
.ca-dlg-do {
  display: flex; align-items: center; gap: 8px; margin-block-start: 4px;

  /* Sticky against the form s own bottom padding, with the panel background behind it so the fields
     scrolling underneath do not show through. */
  position: sticky; inset-block-end: -16px; z-index: 1;
  background: var(--surface); padding-block: 8px; margin-block-end: -8px;
}
.ca-dlg-save {
  padding: 7px 16px; border: 0; border-radius: 8px;
  background: var(--accent-fill); color: var(--on-accent); font: inherit; font-weight: 600; cursor: pointer;
}
.ca-dlg-quiet, .ca-dlg-del {
  padding: 7px 12px; border: 1px solid var(--ca-line); border-radius: 8px;
  background: var(--surface); color: var(--ca-ink-2); font: inherit; cursor: pointer;
}
.ca-dlg-del { color: var(--critical); border-color: color-mix(in srgb, var(--critical) 35%, var(--surface)); }
.ca-dlg-del:hover { background: var(--critical-bg); }
.ca-dlg-save:disabled, .ca-dlg-quiet:disabled, .ca-dlg-del:disabled { opacity: .6; cursor: not-allowed; }

/* ---- today: a filled chip, in every view ---- */
.ca-y-cell.ca-today,
.ca-m-cell.ca-today .ca-m-num,
.ca-w-day.ca-today .ca-w-num { background: var(--accent-fill); color: var(--on-accent); font-weight: 600; }
.ca-w-day.ca-today .ca-w-num { padding: 1px 7px; border-radius: 99px; }
.ca-y-cell.ca-today .ca-dot { background: var(--on-accent); opacity: .85; }

/* ---- narrower windows: the year folds to two columns, then one; the week keeps its seven days ---- */
@container (max-width: 900px) {
  .ca-year { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ca-w-head, .ca-w-allday, .ca-w-grid { grid-template-columns: 44px repeat(7, minmax(0, 1fr)); }
}
@container (max-width: 560px) {
  .ca-year { grid-template-columns: minmax(0, 1fr); }
  .ca-period { min-inline-size: 0; }
  .ca-viewport { padding: 10px; }
  .ca-month { min-block-size: calc(6 * 70px + 28px); }
  .ca-m-cell { padding: 3px; }
  .ca-m-head span { padding: 6px 3px; font-size: 9px; letter-spacing: 0; }
  .ca-chip { padding: 1px 3px; border-inline-start-width: 2px; }
  .ca-chip-time { display: none; }
  .ca-more { padding: 0 1px; font-size: 10px; }
  .ca-w-head, .ca-w-allday, .ca-w-grid { grid-template-columns: 34px repeat(7, minmax(0, 1fr)); }
  .ca-w-day { flex-direction: column; align-items: center; gap: 0; padding: 5px 2px; }
  .ca-w-wd { font-size: 9px; letter-spacing: 0; }
  .ca-w-num { font-size: 13px; }
  .ca-w-lbl { padding: 0 3px; font-size: 8.5px; letter-spacing: 0; }
  .ca-w-slot { padding: 3px 2px; }
  .ca-w-hour { padding-inline-end: 3px; font-size: 9px; }
  .ca-w-evt { padding: 2px 3px; }
  .ca-w-evt-s { display: none; }
}

/* ============================================================================
   TASKS V2 — Syed's atrium-tasks-V2 kit (18 September 2026)
   One accent for actions and the chosen filter, red for overdue and nothing else, "Add task" the only
   filled button. The kit's stone palette is mapped onto the desktop's own (--dk-*) with the house tokens
   behind it, so Tasks follows the desktop in light and dark and whichever accent somebody chose.

   The root is `.nf-app.tk`: .nf-app still carries the variables the Plan sheet, the undo toast and
   "keep as written" are drawn with. Every rule here is `.tk .tk-…` because `.nf-app button` would
   otherwise outrank a bare class and strip the buttons back to text.
   ============================================================================ */
.tk {
  --tk-surface:      var(--dk-surface, var(--surface));
  --tk-surface-2:    var(--dk-surface-2, var(--surface-2));
  --tk-surface-3:    var(--dk-surface-3, var(--surface-3));
  --tk-field:        var(--dk-field, var(--border-strong));
  --tk-border:       var(--dk-line, var(--border-strong));
  --tk-border-soft:  var(--dk-line-soft, var(--border));
  --tk-divider:      color-mix(in srgb, var(--tk-border-soft) 55%, var(--tk-surface));
  --tk-text:         var(--dk-ink, var(--text));
  --tk-text-2:       var(--dk-ink-2, var(--muted));
  --tk-text-3:       var(--dk-ink-3, var(--faint));
  --tk-text-4:       color-mix(in srgb, var(--tk-text-3) 72%, var(--tk-surface));
  --tk-accent:       var(--accent);
  --tk-accent-fill:  var(--accent-fill);
  --tk-accent-hover: var(--accent-fill-hover);
  --tk-accent-tint:  var(--accent-soft);
  --tk-accent-tint-2:color-mix(in srgb, var(--accent-soft) 55%, var(--tk-surface));
  --tk-accent-line:  var(--accent-line);
  --tk-accent-deep:  color-mix(in srgb, var(--accent) 80%, var(--tk-text));
  --tk-on-accent:    var(--on-accent);
  --tk-danger:       var(--dk-crit, var(--critical));
  --tk-danger-tint:  color-mix(in srgb, var(--tk-danger) 14%, var(--tk-surface));
  --tk-danger-wash:  color-mix(in srgb, var(--tk-danger) 5%, var(--tk-surface));
  --tk-danger-rule:  color-mix(in srgb, var(--tk-danger) 30%, var(--tk-surface));
  --tk-shadow-sm:    var(--dk-shadow-1, 0 1px 2px rgba(0, 0, 0, .12));
  --tk-shadow-menu:  var(--dk-shadow-2, 0 12px 30px rgba(0, 0, 0, .2));

  background: var(--tk-surface); color: var(--tk-text);
}
.tk .tk-spacer { flex: 1 1 auto; }

/* ---- toolbar: search, the four filters, then the order and the planner ---- */
.tk .tk-toolbar {
  display: flex; align-items: center; gap: 8px; flex: 0 0 auto; min-block-size: 56px; padding: 10px 14px;
  border-block-end: 1px solid var(--tk-surface-3); background: var(--tk-surface);
}
.tk .tk-search { position: relative; display: flex; align-items: center; flex: 1 1 180px; max-inline-size: 300px; min-inline-size: 140px; }
.tk .tk-search-icon { position: absolute; inset-inline-start: 10px; color: var(--tk-text-3); pointer-events: none; }
.tk .tk-search-input {
  inline-size: 100%; block-size: 36px; padding-block: 0; padding-inline: 32px 12px;
  border: 1px solid var(--tk-field); border-radius: 8px; background: var(--tk-surface); color: var(--tk-text); font-size: 13px;
}
.tk .tk-search-input::placeholder { color: var(--tk-text-3); }
.tk .tk-search-input:focus { outline: none; border-color: var(--tk-accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--tk-accent) 18%, transparent); }

.tk .tk-filters {
  display: flex; align-items: center; gap: 2px; flex: 0 0 auto; block-size: 36px; padding: 3px;
  border: 1px solid var(--tk-border-soft); border-radius: 8px; background: var(--tk-surface-2);
}
.tk .tk-filter {
  display: flex; align-items: center; gap: 5px; block-size: 28px; padding: 0 10px; border-radius: 6px;
  font-size: 12.5px; font-weight: 500; color: var(--tk-text-2); white-space: nowrap;
}
.tk .tk-filter:hover { color: var(--tk-text); }
.tk .tk-filter--zero { color: var(--tk-text-3); }
.tk .tk-filter[aria-pressed="true"] { background: var(--tk-surface); color: var(--tk-text); box-shadow: var(--tk-shadow-sm); }
.tk .tk-badge { font-size: 11px; font-weight: 600; color: var(--tk-text-3); font-variant-numeric: tabular-nums; }
.tk .tk-filter--zero .tk-badge { color: var(--tk-text-4); }
.tk .tk-badge--active { padding: 0 5px; border-radius: 999px; background: var(--tk-accent-tint); color: var(--tk-accent-deep); }
.tk .tk-badge--danger { padding: 0 5px; border-radius: 999px; background: var(--tk-danger-tint); color: var(--tk-danger); }

.tk .tk-labelfilter {
  display: inline-flex; align-items: center; gap: 4px; block-size: 28px; padding: 0 10px; border-radius: 999px;
  background: var(--tk-accent-tint); color: var(--tk-accent-deep); font-size: 12px; font-weight: 600;
}
.tk .tk-labelfilter:hover { background: color-mix(in srgb, var(--tk-accent) 22%, var(--tk-surface)); }

.tk .tk-btn {
  display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto; block-size: 36px; padding: 0 11px;
  border: 1px solid transparent; border-radius: 8px; font-size: 13px; font-weight: 500; white-space: nowrap;
}
.tk .tk-btn--secondary { border-color: var(--tk-border); background: var(--tk-surface); color: var(--tk-text); }
.tk .tk-btn--secondary:hover { background: var(--tk-surface-2); }
.tk .tk-btn--primary { background: var(--tk-accent-fill); color: var(--tk-on-accent); font-weight: 600; }
.tk .tk-btn--primary:hover:not(:disabled) { background: var(--tk-accent-hover); }
.tk .tk-btn--primary:disabled { opacity: .45; cursor: default; }
.tk .tk-btn--lg { block-size: 38px; padding: 0 15px; gap: 7px; border-radius: 9px; font-size: 13.5px; }

.tk .tk-menu-anchor { position: relative; flex: 0 0 auto; }
.tk .tk-menu {
  position: absolute; inset-block-start: calc(100% + 6px); inset-inline-end: 0; z-index: 20;
  display: flex; flex-direction: column; gap: 1px; min-inline-size: 170px; padding: 5px;
  background: var(--tk-surface); border: 1px solid var(--tk-border-soft); border-radius: 10px; box-shadow: var(--tk-shadow-menu);
}
.tk .tk-menu h6 { margin: 4px 9px 2px; font-size: 10.5px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--tk-text-3); }
.tk .tk-menu-item {
  display: flex; align-items: center; gap: 8px; block-size: 32px; padding: 0 9px; border-radius: 7px;
  font-size: 12.5px; color: var(--tk-text); text-align: start; white-space: nowrap;
}
.tk .tk-menu-item:hover, .tk .tk-menu-item:focus-visible { background: var(--tk-accent-tint-2); }
.tk .tk-menu-item[aria-checked="true"] { background: var(--tk-accent-tint); color: var(--tk-accent-deep); font-weight: 500; }
.tk .tk-menu-item--danger { color: var(--tk-danger); }
.tk .tk-menu-sep { block-size: 1px; margin: 4px 2px; background: var(--tk-surface-3); }
.tk .tk-menu-date { padding: 4px 6px 6px; }
.tk .tk-menu-date input {
  inline-size: 100%; block-size: 30px; padding: 0 8px; border: 1px solid var(--tk-field); border-radius: 7px;
  background: var(--tk-surface); color: var(--tk-text); font-size: 12.5px;
}
.tk .tk-pri { inline-size: 8px; block-size: 8px; border-radius: 99px; background: var(--tk-text-3); }
.tk .tk-pri--high { background: var(--tk-accent); }
.tk .tk-pri--low { background: transparent; border: 1.5px solid var(--tk-text-3); }

/* ✨ Plan, drawn as the kit's planner button: the accent's tint, never a second filled button. */
.tk .nf-planbtn {
  block-size: 36px; padding: 0 12px; gap: 6px; border: 1px solid var(--tk-accent-line); border-radius: 8px;
  background: var(--tk-accent-tint-2); color: var(--tk-accent-deep); font-size: 13px; font-weight: 500;
}
.tk .nf-planbtn svg { color: var(--tk-accent); }
.tk .nf-planbtn:hover, .tk .nf-planbtn[aria-expanded="true"] { border-color: var(--tk-accent); background: var(--tk-accent-tint); color: var(--tk-accent-deep); }
.tk .nf-planmenu {
  inset-block-start: calc(100% + 6px); min-inline-size: 230px; padding: 5px;
  background: var(--tk-surface); border-color: var(--tk-border-soft); border-radius: 10px; box-shadow: var(--tk-shadow-menu);
}
.tk .nf-planmenu button { border-radius: 7px; font-size: 12.5px; }
.tk .nf-planmenu button:hover, .tk .nf-planmenu button:focus-visible { background: var(--tk-accent-tint-2); }

/* ---- the list: Overdue, Today, Upcoming, No date, Completed ---- */
.tk .tk-list { position: relative; flex: 1 1 auto; min-block-size: 0; overflow-y: auto; background: var(--tk-surface); }
.tk .tk-alert { margin: 10px 14px; padding: 8px 12px; border-radius: 8px; background: var(--tk-danger-tint); color: var(--tk-danger); font-size: 12.5px; }
.tk .tk-group + .tk-group .tk-section { margin-block-start: 4px; }
.tk .tk-section { display: flex; align-items: center; gap: 9px; block-size: 30px; margin: 0; padding: 0 14px; font: inherit; }
.tk .tk-section--overdue { background: var(--tk-danger-wash); }
.tk .tk-section-label { font-size: 10.5px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--tk-text-2); }
.tk .tk-section-count { font-size: 11px; font-weight: 600; color: var(--tk-text-3); font-variant-numeric: tabular-nums; }
.tk .tk-section-rule { flex: 1 1 auto; block-size: 1px; background: var(--tk-surface-3); }
.tk .tk-section--overdue .tk-section-label, .tk .tk-section--overdue .tk-section-count { color: var(--tk-danger); }
.tk .tk-section--overdue .tk-section-rule { background: var(--tk-danger-rule); }

.tk .tk-row {
  display: flex; align-items: center; gap: 11px; min-block-size: 44px; padding: 0 14px;
  border-block-end: 1px solid var(--tk-divider);
}
.tk .tk-row:hover, .tk .tk-row--menu { background: var(--tk-surface-2); }
.tk .tk-row--overdue { background: color-mix(in srgb, var(--tk-surface-2) 60%, var(--tk-surface)); }
.tk .tk-check { inline-size: 16px; block-size: 16px; margin: 0; flex: 0 0 auto; accent-color: var(--tk-accent); cursor: pointer; }
.tk .tk-title {
  min-inline-size: 0; flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 13.5px; color: var(--tk-text); cursor: text; border-radius: 4px;
}
.tk .tk-row--done .tk-title { color: var(--tk-text-3); text-decoration: line-through; }
.tk .tk-edit {
  flex: 1 1 auto; min-inline-size: 0; block-size: 30px; padding: 0 8px; border: 1px solid var(--tk-accent);
  border-radius: 7px; background: var(--tk-surface); color: var(--tk-text); font-size: 13.5px;
}
.tk .tk-edit:focus { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--tk-accent) 18%, transparent); }
.tk .tk-pinmark { display: inline-flex; color: var(--tk-accent); }
.tk .tk-pinmark svg { inline-size: 12px; block-size: 12px; }

.tk .tk-chip {
  flex: 0 0 auto; padding: 1px 7px; border-radius: 999px; background: var(--tk-surface-3);
  font-size: 11px; font-weight: 500; line-height: 1.5; color: var(--tk-text-2);
}
.tk button.tk-chip:hover { background: color-mix(in srgb, var(--tk-text) 12%, var(--tk-surface)); color: var(--tk-text); }
.tk .tk-chip--accent { background: var(--tk-accent-tint); color: var(--tk-accent-deep); font-weight: 600; }
.tk .tk-chip--danger { background: var(--tk-danger-tint); color: var(--tk-danger); font-weight: 600; }
.tk .tk-chip--low { background: transparent; border: 1px solid var(--tk-border-soft); color: var(--tk-text-3); }
.tk .tk-row--done .tk-chip { opacity: .7; }

/* Edit, delete and "more" wait for the pointer (or the keyboard) on their row. */
.tk .tk-actions { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }
.tk .tk-rowbtn {
  display: none; align-items: center; justify-content: center; inline-size: 28px; block-size: 28px; padding: 0;
  border: 1px solid var(--tk-border-soft); border-radius: 6px; background: var(--tk-surface); color: var(--tk-text-2);
  box-shadow: var(--tk-shadow-sm);
}
.tk .tk-rowbtn:hover { color: var(--tk-text); border-color: var(--tk-border); }
.tk .tk-rowbtn--danger { color: var(--tk-danger); }
.tk .tk-rowbtn--danger:hover { color: var(--tk-danger); }
.tk .tk-row:hover .tk-rowbtn, .tk .tk-row:focus-within .tk-rowbtn, .tk .tk-row--menu .tk-rowbtn { display: flex; }

.tk .tk-when { position: relative; display: flex; justify-content: flex-end; flex: 0 0 104px; }
.tk .tk-when .tk-menu { min-inline-size: 190px; }
.tk .tk-due { font-size: 12.5px; color: var(--tk-text-2); text-align: end; white-space: nowrap; font-variant-numeric: tabular-nums; }
.tk .tk-due--overdue { font-weight: 600; color: var(--tk-danger); }
.tk .tk-row--done .tk-due { color: var(--tk-text-3); }
.tk .tk-setdate {
  display: inline-flex; align-items: center; gap: 4px; block-size: 22px; padding: 0 8px;
  border: 1px dashed var(--tk-border); border-radius: 999px; font-size: 11.5px; color: var(--tk-text-3);
}
.tk .tk-setdate svg { inline-size: 12px; block-size: 12px; }
.tk .tk-setdate:hover { border-color: var(--tk-accent); color: var(--tk-accent); }

/* ---- the one empty state ---- */
.tk .tk-state {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  min-block-size: 100%; padding: 32px 24px; text-align: center;
}
.tk .tk-state-badge {
  display: grid; place-items: center; inline-size: 60px; block-size: 60px; margin-block-end: 14px;
  border-radius: 16px; background: var(--tk-accent-tint); color: var(--tk-accent);
}
.tk .tk-state-badge svg { inline-size: 26px; block-size: 26px; }
.tk .tk-state-title { margin: 0; font-size: 17px; font-weight: 600; color: var(--tk-text); }
.tk .tk-state-text { max-inline-size: 340px; margin: 0 0 20px; font-size: 13px; line-height: 1.5; color: var(--tk-text-2); }
.tk .tk-state-actions { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 9px; }

/* ---- adding: the field, "Add task", and what it will create ---- */
.tk .tk-addbar { flex: 0 0 auto; padding: 12px 14px; background: var(--tk-surface-2); border-block-start: 1px solid var(--tk-surface-3); }
.tk .tk-addrow { display: flex; align-items: center; gap: 8px; margin: 0; }
.tk .tk-addfield { position: relative; display: flex; align-items: center; flex: 1 1 auto; min-inline-size: 0; }
.tk .tk-addicon { position: absolute; inset-inline-start: 11px; color: var(--tk-text-3); pointer-events: none; }
.tk .tk-addfield--active .tk-addicon { color: var(--tk-accent); }
.tk .tk-addinput {
  inline-size: 100%; block-size: 40px; padding-block: 0; padding-inline: 34px 12px;
  border: 1px solid var(--tk-field); border-radius: 9px; background: var(--tk-surface); color: var(--tk-text); font-size: 13.5px;
}
.tk .tk-addinput::placeholder { color: var(--tk-text-3); }
.tk .tk-addinput:focus { outline: none; border-color: var(--tk-accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--tk-accent) 18%, transparent); }
.tk .tk-addrow .tk-btn--primary { block-size: 40px; padding: 0 16px; border-radius: 9px; font-size: 13.5px; }
.tk .tk-preview { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; margin-block-start: 10px; }
.tk .tk-preview-label { font-size: 11.5px; color: var(--tk-text-3); }
.tk .tk-preview-title {
  max-inline-size: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: 2px 8px; border: 1px solid var(--tk-border-soft); border-radius: 999px; background: var(--tk-surface);
  font-size: 11.5px; font-weight: 500; color: var(--tk-text);
}
.tk .tk-preview-chip {
  display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border-radius: 999px;
  background: var(--tk-surface-3); font-size: 11.5px; font-weight: 500; color: var(--tk-text-2);
}
.tk .tk-preview-chip svg { inline-size: 12px; block-size: 12px; }
.tk .tk-preview-chip--accent { background: var(--tk-accent-tint); color: var(--tk-accent-deep); }
.tk .tk-preview-chip--accent svg { color: var(--tk-accent); }
.tk .tk-preview-chip--danger { background: var(--tk-danger-tint); color: var(--tk-danger); }
.tk .tk-preview-chip .nf-keepword { margin-inline-start: 2px; }
.tk .tk-hints { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 12px; margin-block-start: 9px; font-size: 11px; color: var(--tk-text-3); }
.tk .tk-hints b { font-weight: 700; color: var(--tk-text-2); }

/* ---- status: how far through, and the one shortcut worth knowing ---- */
.tk .tk-statusbar {
  display: flex; align-items: center; gap: 10px; flex: 0 0 auto; min-block-size: 32px; padding: 0 14px;
  background: var(--tk-surface-2); border-block-start: 1px solid var(--tk-surface-3); font-size: 11.5px; color: var(--tk-text-3);
}
.tk .tk-progress { inline-size: 132px; block-size: 5px; flex: 0 0 auto; border-radius: 999px; background: var(--tk-border-soft); overflow: hidden; }
.tk .tk-progress-fill { block-size: 5px; border-radius: 999px; background: var(--tk-accent); transition: width .2s ease; }
.tk .tk-quiet { padding: 2px 6px; border-radius: 6px; font-size: 11.5px; color: var(--tk-text-2); }
.tk .tk-quiet:hover { background: var(--tk-surface-3); color: var(--tk-text); }
@media (prefers-reduced-motion: reduce) { .tk .tk-progress-fill { transition: none; } }

/* ---- a narrow window: the toolbar wraps rather than squeezing; "more" stays in reach without a pointer ---- */
@container (max-width: 760px) {
  .tk .tk-toolbar { flex-wrap: wrap; }
  .tk .tk-search { flex-basis: 100%; max-inline-size: none; }
  .tk .tk-when { flex-basis: 84px; }
  .tk .tk-hints > :last-child { display: none; }
}
@container (max-width: 480px) {
  .tk .tk-filter { padding: 0 7px; font-size: 12px; }
  .tk .tk-progress { inline-size: 64px; }
  .tk .tk-statusbar > :last-child { display: none; }
}
@media (hover: none) {
  .tk .tk-rowbtn[aria-expanded] { display: flex; }
}

/* ============================================================================
   SHEETS: THE LIST — Syed's sheets-kit V2, and his Sheets feature additions (19 September 2026)
   A header with search, Import and New; one five-column grid shared by the header row and every file row, so the
   columns line up without a <table> and a whole row can be what opens; a "…" on every row; and a footer with
   Trash and Templates. In Sheets' own colours and IBM Plex Sans (the additions' design tokens), which apply to
   Sheets alone: the rest of the desk keeps the stone theme. Dark follows the desk's own dark tokens.
   ============================================================================ */
@font-face {
  font-family: 'IBM Plex Sans'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('../fonts/ibm-plex-sans-400.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Sans'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('../fonts/ibm-plex-sans-500.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Sans'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('../fonts/ibm-plex-sans-600.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Sans'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('../fonts/ibm-plex-sans-700.woff2') format('woff2');
}

.sx {
  --sx-ink: #1B2230; --sx-ink-2: #4A5466; --sx-muted: #5A6474; --sx-faint: #A3ABB8;
  --sx-line: #DDE2EA; --sx-line-soft: #EDF0F4; --sx-sep: #E4E8EE; --sx-control: #C9D0DB; --sx-field-line: #D5DAE3;
  --sx-chrome: #F4F6F9; --sx-surface: #FFFFFF;
  --sx-primary: #2B3FC4; --sx-primary-hover: #2334A8; --sx-on-primary: #FFFFFF;
  --sx-row-hover: #F7F9FC; --sx-row-on: #F1F4FD; --sx-btn-on: #DDE3F6; --sx-hover: #F0F3F9; --sx-ib-hover: #E4E8EF;
  --sx-danger: #B42318; --sx-danger-soft: #FDECEA; --sx-star: #B7791F;
  --sx-shadow: 0 10px 28px rgba(20, 30, 50, .16);

  container: sx / inline-size; position: relative;
  block-size: 100%; min-block-size: 0; display: flex; flex-direction: column;
  background: var(--sx-surface); color: var(--sx-ink);
  font-family: 'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif; font-size: 13px;
}
[data-theme="dark"] .sx {
  --sx-ink: var(--dk-ink); --sx-ink-2: var(--dk-ink-2); --sx-muted: var(--dk-ink-2); --sx-faint: var(--dk-ink-3);
  --sx-line: var(--dk-line); --sx-line-soft: var(--dk-line-soft); --sx-sep: var(--dk-line); --sx-control: var(--dk-line-strong);
  --sx-field-line: var(--dk-line); --sx-chrome: var(--dk-surface-2); --sx-surface: var(--dk-surface);
  --sx-primary: var(--accent-fill); --sx-primary-hover: var(--accent-fill-hover, var(--accent-fill)); --sx-on-primary: var(--on-accent, #fff);
  --sx-row-hover: var(--dk-surface-2); --sx-row-on: color-mix(in srgb, var(--accent) 12%, var(--dk-surface));
  --sx-btn-on: color-mix(in srgb, var(--accent) 24%, var(--dk-surface)); --sx-hover: var(--dk-surface-2); --sx-ib-hover: var(--dk-surface-3);
  --sx-danger: #F2A097; --sx-danger-soft: color-mix(in srgb, #F2A097 14%, var(--dk-surface)); --sx-star: #E3B341;
  --sx-shadow: var(--dk-shadow-2, 0 12px 30px rgba(0, 0, 0, .5));
}
.sx button, .sx input { font-family: inherit; }
.sx-grow { flex: 1; }
.sx-state { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; color: var(--sx-muted); }

/* ---- header: title, search, Import, New ---- */
.sx-head {
  flex: none; display: flex; align-items: center; gap: 10px; flex-wrap: nowrap;
  min-block-size: 64px; padding: 0 20px; border-block-end: 1px solid var(--sx-line);
}
.sx-head h2 { flex: none; margin: 0; font-size: 16px; font-weight: 600; white-space: nowrap; }
.sx-search {
  display: flex; align-items: center; gap: 8px; flex: 0 1 280px; min-inline-size: 150px; block-size: 36px; padding: 0 12px;
  background: var(--sx-chrome); border: 1px solid var(--sx-field-line); border-radius: 8px; color: var(--sx-muted);
}
.sx-search:focus-within { border-color: var(--sx-primary); background: var(--sx-surface); }
.sx-search input {
  flex: 1; min-inline-size: 0; border: 0; outline: none; background: transparent; color: var(--sx-ink); font-size: 13px;
}

/* ---- buttons: primary (New), secondary (Import, Templates, Restore), quiet, danger ---- */
.sx-primary, .sx-secondary, .sx-ghost, .sx-danger, .sx-quiet {
  display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; cursor: pointer; border-radius: 8px;
  font-size: 13px; font-weight: 600;
}
.sx-primary { block-size: 36px; padding: 0 16px; border: 0; background: var(--sx-primary); color: var(--sx-on-primary); }
.sx-primary:hover:not(:disabled) { background: var(--sx-primary-hover); }
.sx-secondary { block-size: 36px; padding: 0 14px; border: 1px solid var(--sx-control); background: var(--sx-surface); color: var(--sx-ink); }
.sx-secondary:hover:not(:disabled) { background: var(--sx-chrome); }
.sx-primary:disabled, .sx-secondary:disabled { opacity: .55; cursor: default; }
.sx-ghost { block-size: 30px; padding: 0 12px; border: 1px solid var(--sx-control); background: var(--sx-surface); color: var(--sx-ink-2); font-weight: 500; }
.sx-danger { block-size: 30px; padding: 0 12px; border: 0; background: var(--sx-danger); color: #fff; }
[data-theme="dark"] .sx-danger { color: #1b0d0b; }
.sx-quiet { block-size: 32px; padding: 0 10px; border: 0; background: transparent; color: var(--sx-ink-2); font-weight: 500; }
.sx-quiet:hover { background: var(--sx-ib-hover); color: var(--sx-ink); }
.sx-quiet.is-danger { color: var(--sx-danger); }
.sx-quiet.is-danger:hover { background: var(--sx-danger-soft); }
.sx-link {
  padding: 0; border: 0; background: transparent; color: var(--sx-primary); cursor: pointer;
  font-size: inherit; font-weight: 600; text-decoration: underline; text-underline-offset: 2px;
}
.sx-ib {
  display: inline-grid; place-items: center; inline-size: 28px; block-size: 28px; padding: 0; border: 0; border-radius: 6px;
  background: transparent; color: var(--sx-ink-2); cursor: pointer;
}
.sx-ib:hover { background: var(--sx-ib-hover); }
.sx-gridmark { display: inline-flex; color: var(--sx-primary); }

.sx-note {
  display: flex; align-items: center; gap: 10px; margin: 10px 20px 0; padding: 8px 12px; border-radius: 8px;
  background: var(--sx-chrome); color: var(--sx-ink-2); font-size: 12.5px;
}
.sx-note.is-problem { background: var(--sx-danger-soft); color: var(--sx-danger); }
.sx-notex { margin-inline-start: auto; border: 0; background: transparent; color: inherit; font-size: 16px; line-height: 1; cursor: pointer; }

/* ---- the grid every row shares: name, contents, size, changed, the row's "…" ---- */
.sx-table { flex: 1; min-block-size: 0; overflow-y: auto; display: flex; flex-direction: column; }
/* THE LIST'S SCROLLBAR, VISIBLE (Syed, 22 September: "scroll bar not visible"). Third time for this same fault, after
   the desk rail and Notes, and the cause is always the same: `scrollbar-color` is set once at :root from the SITE
   palette, and a var() there resolves THERE, so every scroller in the app inherits a colour mixed for the site's
   background and not for the one it is actually sitting on. The list is dark, the thumb was the site's, and there was
   nothing to see.

   Both standard properties go back to `auto` first. That is not decoration: a REAL value on either of them makes the
   ::-webkit rules below inert, while `auto` cancels what was inherited and leaves them working. Measured, headed, in
   dark - headless Chromium draws no scrollbars at all and reports zero width for every one, so it cannot answer this. */
.sx-table { scrollbar-width: auto; scrollbar-color: auto; }
.sx-table::-webkit-scrollbar { inline-size: 10px; }
.sx-table::-webkit-scrollbar-track { background: transparent; }
.sx-table::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--sx-ink) 30%, transparent); border-radius: 5px; }
.sx-table::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--sx-ink) 46%, transparent); }
.sx-tr {
  position: relative; display: grid; align-items: center; gap: 12px; min-block-size: 42px; padding: 0 20px;
  grid-template-columns: minmax(140px, 1.15fr) minmax(0, 1fr) 80px 150px 44px;
  border-block-end: 1px solid var(--sx-line-soft); cursor: pointer; outline: none;
}
.sx-tr:hover { background: var(--sx-row-hover); }
.sx-tr.is-menu { background: var(--sx-row-on); }
.sx-tr:focus-visible { box-shadow: inset 0 0 0 2px var(--sx-primary); }
.sx-tr-head {
  position: sticky; inset-block-start: 0; z-index: 2; min-block-size: 36px; cursor: default;
  background: var(--sx-chrome); border-block-end-color: var(--sx-line);
}
.sx-tr-head:hover { background: var(--sx-chrome); }
.sx-th {
  display: inline-flex; align-items: center; gap: 5px; padding: 0; border: 0; background: transparent; text-align: start;
  font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--sx-muted);
}
.sx-sort { cursor: pointer; }
.sx-sort:hover, .sx-sort.is-active { color: var(--sx-ink); }
.sx-sort.is-right { justify-self: end; }
.sx-group-label {
  flex: none; block-size: 30px; display: flex; align-items: flex-end; padding: 0 20px 6px;
  font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--sx-muted);
}
.sx-td { min-inline-size: 0; }
.sx-td-name { display: flex; align-items: center; gap: 10px; font-size: 13px; font-weight: 500; color: var(--sx-ink); }
.sx-sheetmark { flex: none; display: inline-flex; color: var(--sx-primary); }
.sx-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sx-starmark { flex: none; display: inline-flex; color: var(--sx-star); }
/* Shared with the team (Syed, 22 September 2026). In the ink rather than the accent: it is a fact about the file,
   not a warning, and the star beside it already owns the one colour that shouts. */
.sx-sharemark { flex: none; display: inline-flex; color: var(--sx-muted); }
.sx-td-preview { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: var(--sx-muted); }
.sx-td-size { text-align: end; white-space: nowrap; font-family: 'IBM Plex Mono', ui-monospace, monospace; font-size: 12px; color: var(--sx-ink); }
.sx-unit { font-size: 10px; color: var(--sx-muted); }
.sx-td-date { white-space: nowrap; font-size: 12px; color: var(--sx-ink-2); }
.sx-td-sub { display: block; margin-block-start: 2px; font-size: 11px; color: var(--sx-muted); }
.sx-td-actions { position: relative; display: flex; justify-content: flex-end; }
.sx-rename {
  inline-size: 100%; padding: 3px 6px; border: 1px solid var(--sx-primary); border-radius: 6px; outline: none;
  background: var(--sx-surface); color: var(--sx-ink); font-size: 13px; font-weight: 500;
}

/* The "…" is always there (feature additions), and lit while its menu is open. */
.sx-rowbtn {
  display: grid; place-items: center; inline-size: 28px; block-size: 28px; padding: 0; border: 0; border-radius: 6px;
  background: transparent; color: var(--sx-ink-2); cursor: pointer;
}
.sx-rowbtn:hover { background: var(--sx-ib-hover); }
.sx-tr.is-menu .sx-rowbtn { background: var(--sx-btn-on); color: var(--sx-primary); }
.sx-rowmenu {
  position: absolute; z-index: 20; inline-size: 210px;   /* placed from its "…" by the list (top, right) */
  display: flex; flex-direction: column; padding: 6px; cursor: default;
  background: var(--sx-surface); border: 1px solid var(--sx-field-line); border-radius: 10px; box-shadow: var(--sx-shadow);
}
.sx-rowmenu > button, .sx-rowmenu > a {
  display: flex; align-items: center; gap: 10px; inline-size: 100%; block-size: 34px; padding: 0 12px;
  border: 0; border-radius: 6px; background: transparent; text-align: start; text-decoration: none; cursor: pointer;
  color: var(--sx-ink); font-size: 13px; font-weight: 400;
}
.sx-rowmenu > button:hover, .sx-rowmenu > a:hover { background: var(--sx-hover); }
.sx-rowmenu svg { flex: none; }
.sx-rowmenu > .is-danger { color: var(--sx-danger); }

/* A menu item that cannot be used, and the line under it saying why (Syed, 24 September 2026: grey
   Share out with the reason rather than hiding it). Dimmed rather than removed, so somebody looking
   for the control finds it and learns something; a control that vanishes teaches nothing. */
.sx-rowmenu > button:disabled { opacity: .45; cursor: not-allowed; }
.sx-rowmenu > button:disabled:hover { background: transparent; }
.sx-rowmenu-why {
  margin: 0; padding: 2px 12px 8px; max-inline-size: 260px;
  font-size: var(--fs-xs); line-height: 1.45; color: var(--faint);
}
.sx-rowmenu > .is-danger:hover { background: var(--sx-danger-soft); }
.sx-rowmenu-sep { block-size: 1px; margin: 6px 4px; background: var(--sx-sep); }

.sx-empty { display: flex; flex-direction: column; align-items: center; gap: 7px; padding: 48px 20px; text-align: center; }
.sx-empty h4 { margin: 0; font-size: 14px; font-weight: 600; color: var(--sx-ink); }
.sx-empty p { margin: 0; max-inline-size: 38ch; font-size: 12.5px; color: var(--sx-ink-2); }
.sx-empty-mark {
  display: grid; place-items: center; inline-size: 46px; block-size: 46px; margin-block-end: 6px; border-radius: 12px;
  background: color-mix(in srgb, var(--sx-primary) 12%, var(--sx-surface)); color: var(--sx-primary);
}

/* ---- footer: the count, Trash, Templates ---- */
.sx-foot {
  flex: none; display: flex; align-items: center; gap: 8px; min-block-size: 48px; padding: 0 20px;
  background: var(--sx-chrome); border-block-start: 1px solid var(--sx-line); font-size: 12px; color: var(--sx-muted);
}
.sx-foot .sx-secondary { block-size: 32px; padding: 0 12px; }

/* ---- Templates and Trash, each in place of the list ---- */
.sx-sub .sx-head { gap: 8px; }
.sx-subhint { margin: 14px 20px 0; font-size: 12.5px; color: var(--sx-muted); }
.sx-cards {
  flex: 1; min-block-size: 0; overflow-y: auto; align-content: start;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; padding: 14px 20px 20px;
}
.sx-card {
  position: relative; display: flex; flex-direction: column; gap: 8px; padding: 14px; cursor: pointer; text-align: start;
  border: 1px solid var(--sx-line); border-radius: 10px; background: var(--sx-surface); color: var(--sx-ink); font-size: 13px;
}
.sx-card:hover:not(:disabled) { border-color: var(--sx-primary); box-shadow: 0 2px 10px rgba(20, 30, 50, .08); }
.sx-card:focus-visible { outline: 2px solid var(--sx-primary); outline-offset: 1px; }
.sx-card:disabled { opacity: .6; cursor: default; }
.sx-card-top { display: flex; align-items: center; gap: 8px; font-size: 14px; }
.sx-card-top b { font-weight: 600; }
.sx-card-desc { font-size: 12.5px; line-height: 1.45; color: var(--sx-ink-2); }
.sx-card-cols {
  padding-block-start: 8px; border-block-start: 1px solid var(--sx-line-soft);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11.5px; color: var(--sx-muted);
}
.sx-card-busy { position: absolute; inset-block-start: 14px; inset-inline-end: 14px; font-size: 11.5px; color: var(--sx-primary); }
.sx-tr-trash { grid-template-columns: minmax(0, 1fr) 210px 80px auto; cursor: default; min-block-size: 52px; }
.sx-trash-actions { display: flex; justify-content: flex-end; align-items: center; gap: 6px; }
.sx-trash-actions .sx-secondary { block-size: 30px; padding: 0 10px; font-weight: 500; }
.sx-confirm-inline { display: inline-flex; align-items: center; gap: 8px; font-size: 12px; color: var(--sx-ink); }

/* A narrow window keeps the name and the date; contents and size wait for room. The trash keeps its date under
   the name, since when a sheet goes for good is the point of that screen. */
@container sx (max-width: 600px) {
  .sx-tr { grid-template-columns: minmax(0, 1fr) 118px 36px; }
  .sx-td-preview, .sx-td-size, .sx-tr-head > :nth-child(2), .sx-tr-head > :nth-child(3) { display: none; }
  .sx-head { padding-block: 10px; flex-wrap: wrap; }
  .sx-head h2 { inline-size: 100%; }
  .sx-sub .sx-head h2 { inline-size: auto; }
  .sx-search { flex: 1; inline-size: auto; min-inline-size: 0; }
  .sx-tr-trash { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "name act" "date act"; row-gap: 2px; padding-block: 8px; }
  .sx-tr-trash .sx-td-name { grid-area: name; }
  .sx-tr-trash .sx-td-date { grid-area: date; display: block; }
  .sx-tr-trash .sx-td-size { display: none; }
  .sx-tr-trash .sx-trash-actions { grid-area: act; }
  .sx-confirm-inline > span { display: none; }
}

/* ---- Documents and Slides: the same list in their mockups' three columns (feature additions, 19 September) ----
   Name with the extension muted after it, Type, and Changed written out. Sheets' own rows are untouched: every rule
   here is under .is-files. */
.sx.is-files .sx-tr:not(.sx-tr-trash) { grid-template-columns: minmax(0, 1fr) 150px 210px 52px; }
.sx-fullname { display: flex; min-inline-size: 0; }
.sx-ext { flex: none; color: #6B7486; font-weight: 400; }
[data-theme="dark"] .sx-ext { color: var(--dk-ink-3); }
.sx-td-type { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: var(--sx-ink-2); }
@container sx (max-width: 600px) {
  .sx.is-files .sx-tr:not(.sx-tr-trash) { grid-template-columns: minmax(0, 1fr) 146px 36px; }
  .sx.is-files .sx-td-type { display: none; }
  .sx.is-files .sx-tr-head > :nth-child(3) { display: inline-flex; }
}

/* ---- files on a message ----
   ROOMS LEFT CHAT ON 19 SEPTEMBER AND TOOK THEIR MARKUP WITH THEM: the hash a channel was drawn
   with, and the sender's name above a bubble that only ever appeared in a room. The rules for them
   were deleted with this restyle rather than left behind, because a rule nothing in the markup can
   ever match is a rule the next person has to read and then disprove. */
.ch-picked {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  padding: 8px 16px 0; font-size: var(--fs-xs); background: var(--ch-card);
}
.ch-file-chip {
  display: inline-flex; align-items: center; gap: 6px; padding: 4px 6px 4px 10px;
  border: 1px solid var(--ch-border); border-radius: 999px; background: var(--ch-field);
  color: var(--ch-text-2); max-inline-size: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ch-file-chip button {
  border: 0; background: none; color: var(--ch-muted); font: inherit; font-size: var(--fs-md);
  line-height: 1; cursor: pointer; padding: 0 2px;
}
.ch-file-chip button:hover { color: var(--ch-danger); }

/* A FILE STILL GOING UP. Syed, 24 September 2026: "it is not showing upload bar that how much the
   file is uploaded how many percent". Wider than a finished chip because it carries a bar and a
   number as well as a name, and the name is allowed to run out of room before they do. */
.ch-file-rising { max-inline-size: 340px; padding-inline-end: 10px; }
.ch-file-rising .ch-file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-inline-size: 0; }
.ch-file-bar {
  flex: none; inline-size: 72px; block-size: 4px; border-radius: 999px;
  background: var(--ch-border); overflow: hidden;
}
.ch-file-bar-fill {
  display: block; block-size: 100%; inline-size: 0; border-radius: inherit;
  background: var(--ch-primary); transition: inline-size .15s linear;
}
/* Tabular figures, so the chip does not twitch as the number goes 8 -> 88 -> 100. */
.ch-file-percent { flex: none; color: var(--ch-muted); font-variant-numeric: tabular-nums; }

@media (prefers-reduced-motion: reduce) {
  .ch-file-bar-fill { transition: none; }
}

/* Attach is a teal-outlined circle rather than a filled one: it sits beside a filled teal Send, and
   two solid teal discs at either end of the composer would each make the other harder to find. */
.ch-attach {
  display: grid; place-items: center; inline-size: 38px; block-size: 38px; flex: none;
  border: 1px solid var(--ch-attach-ring); border-radius: 50%; background: var(--ch-card);
  color: var(--ch-primary); font-size: var(--fs-xl); line-height: 1; cursor: pointer;
}
.ch-attach:hover { background: var(--ch-primary-soft-2); border-color: var(--ch-primary-line); }
.ch-attach input { position: absolute; inline-size: 1px; block-size: 1px; opacity: 0; pointer-events: none; }
.ch-attach:focus-within { outline: 2px solid var(--ch-focus); outline-offset: 1px; }

/* A file in a bubble is a card, not an underlined link: the design gives it a ground of its own so
   that a message which is a file and a sentence reads as two things rather than one long line. */
.ch-file {
  display: block; margin-block-end: 4px; padding: 9px 11px; border-radius: 10px;
  background: var(--ch-field); border: 1px solid var(--ch-bubble-theirs-line);
  color: var(--ch-text); font-size: var(--fs-sm); font-weight: 600; text-decoration: none;
  overflow-wrap: anywhere;
}
.ch-file:hover { border-color: var(--ch-primary-line); color: var(--ch-primary-text); }

/* ---- what the search found in messages ---- */
.ch-found-head {
  margin: 10px 14px 2px; font-size: var(--fs-micro); font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ch-muted);
}
.ch-found { list-style: none; margin: 0; padding: 0 8px; display: grid; gap: 1px; }
.ch-found button {
  inline-size: 100%; display: grid; gap: 2px; padding: 7px 10px; border: 0; border-radius: 10px;
  background: none; color: inherit; font: inherit; text-align: start; cursor: pointer;
}
.ch-found button:hover { background: var(--ch-field); }
.ch-found-where {
  display: flex; gap: 8px; align-items: baseline; font-size: var(--fs-xs); font-weight: 700; color: var(--ch-muted);
}
.ch-found-when { margin-inline-start: auto; font-weight: 400; }
/* Two lines of the message and no more: a result list is for recognising, not for reading. */
.ch-found-text {
  font-size: var(--fs-sm); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ================================================================================================
   CHAT V2, PHASE 2 — reactions, pins and saved messages
   ================================================================================================ */

/* ---- reactions: the pills under a message, and the five behind the React button ----
   The pills sit inside the bubble, under the time, so one can never drift away from the message it
   belongs to when the thread reflows. Each is a button, because the obvious thing to do with "👍 2"
   is to press it and make it three — and aria-pressed is what says which way that will go. */
.ch-reacts { display: flex; flex-wrap: wrap; gap: 4px; margin-block-start: 5px; }
.ch-react {
  display: inline-flex; align-items: center; gap: 4px; min-block-size: 24px; padding: 1px 8px;
  border: 1px solid var(--ch-bubble-theirs-line); border-radius: 999px; background: var(--ch-card);
  color: var(--ch-text-2); font: inherit; font-size: var(--fs-xs); line-height: 1.6; cursor: pointer;
}
.ch-react:hover { background: var(--ch-field); }
.ch-react:disabled { cursor: default; opacity: .6; }
.ch-react-count { font-weight: 700; font-variant-numeric: tabular-nums; }

/* THE ONE YOU PRESSED, told twice over: a teal pill AND a ring. Colour alone would leave the
   difference between "two people agreed" and "two people agreed, including me" invisible to anybody
   who cannot separate these two hues — and it is the one fact that changes what the button does.
   Written once for both sides of the thread rather than twice: a white pill on a mint bubble and a
   white pill on a white one are equally legible, so your own messages need no second set of rules. */
.ch-react[aria-pressed="true"] {
  background: var(--ch-primary-soft); border-color: var(--ch-primary);
  box-shadow: inset 0 0 0 1px var(--ch-primary); color: var(--ch-primary-text); font-weight: 700;
}

/* The five, which replace the React button in the tools row while they are open. A group rather than
   a popover: nothing has to be positioned, nothing can be clipped by the scroller, and the tab order
   is already where the reader was. */
.ch-react-pick { display: inline-flex; align-items: center; gap: 2px; }
.ch-react-choice { padding: 2px 7px; font-size: var(--fs-sm); line-height: 1.3; }
.ch-react-choice[aria-pressed="true"] {
  border-color: var(--ch-primary); background: var(--ch-primary-soft); color: var(--ch-primary-text);
}

/* ---- pinned messages: a strip under the header, not a third column ----
   This window is often 570px wide, where a panel beside the thread is the conversation's own width
   again. Closed, the bar is one line: how many, and the newest one. Open, it is the list, each entry
   a button that takes you to the message. */
/* THE THREAD'S GRID HAS TO GROW A ROW, and this is the whole of it.

   .ch-thread is `auto minmax(0, 1fr) auto`: the header, the messages, the composer — and the rule
   that actually matters is that the MESSAGE LIST is the 1fr row and everything else sizes to its
   content. Putting the pinned bar above the messages made the bar the 1fr row: it stretched to half
   the window, the thread became auto-sized, and the composer fell into an implicit row below the fold.
   Nothing errored; the screenshot is how it was found.

   Scoped with :has() rather than changing the rule above, so a conversation with nothing pinned keeps
   exactly the layout it has today and this cannot regress it. */
.ch-thread:has(.ch-pinned) { grid-template-rows: auto auto minmax(0, 1fr); }
.ch-pinned { border-block-end: 1px solid var(--ch-border); background: var(--ch-card); }
.ch-pinned-top {
  display: flex; align-items: center; gap: 8px; inline-size: 100%; min-inline-size: 0;
  padding: 8px 16px; border: 0; background: transparent; color: var(--ch-text-2); font: inherit;
  font-size: var(--fs-xs); text-align: start; cursor: pointer;
}
.ch-pinned-top:hover { background: var(--ch-field); }
.ch-pin-mark { flex: none; font-size: var(--fs-sm); line-height: 1; }
.ch-pinned-count { flex: none; font-weight: 700; color: var(--ch-text); }
/* The newest pin, cut rather than wrapped: the bar is one line, and a line that grows pushes the
   conversation down every time somebody pins a long message. */
.ch-pinned-peek { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ch-muted); }

.ch-pinned-list {
  list-style: none; margin: 0; padding: 0 10px 8px; display: grid; gap: 1px;
  max-block-size: 40dvh; overflow-y: auto;
}
/* Capped, because a row that runs the width of a 1440px window leaves Unpin stranded a foot away
   from the message it belongs to. It reads as a list this way rather than as a table. */
.ch-pinned-row { display: flex; align-items: center; gap: 6px; max-inline-size: 78ch; }
.ch-pinned-go {
  display: grid; gap: 1px; flex: 1; min-inline-size: 0; padding: 7px 9px; border: 0; border-radius: 9px;
  background: transparent; color: var(--ch-text); font: inherit; font-size: var(--fs-xs);
  text-align: start; cursor: pointer;
}
.ch-pinned-go:hover { background: var(--ch-field); }
.ch-pinned-who { font-weight: 700; color: var(--ch-muted); }
.ch-pinned-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The mark on the message itself. A bar that lists a message and a thread that does not mention it
   leaves the reader matching the two up by eye. */
.ch-pin-tag { float: inline-end; margin-inline-start: 6px; font-size: var(--fs-xs); line-height: 1.2; }

/* WHERE YOU WERE TAKEN. A jump that only scrolled leaves somebody looking at a wall of messages with
   no idea which one they asked for. It fades by itself — it says "this one", then stops saying it.
   A ring rather than a background, so it reads the same on a mint bubble and on a white one, and in
   the mid teal rather than the deep one, which on a mint bubble would read as a border it had grown. */
.ch-msg-found .ch-bubble {
  box-shadow: 0 0 0 2px var(--ch-primary-line); animation: ch-found-fade 2.4s ease-out forwards;
}
@keyframes ch-found-fade { 0%, 60% { box-shadow: 0 0 0 2px var(--ch-primary-line); } 100% { box-shadow: 0 0 0 0 transparent; } }
@media (prefers-reduced-motion: reduce) {
  /* No fade, and no flash either: the ring simply stands until the class is taken off. */
  .ch-msg-found .ch-bubble { animation: none; }
}

/* ---- saved messages: one person's own list, over everything else ----
   An overlay rather than a column, because this list crosses conversations and pressing an entry
   changes which one is open — a panel living inside the current conversation would be sawing off its
   own branch. Absolute inside .ch-app, never fixed: inside a desktop window a fixed element pins
   itself to the frame's viewport rather than to the app. */
/* ---- friend groups (Chat V2, 2.4) ------------------------------------------------------------ */

/* A ROUNDED SQUARE, NOT A CIRCLE. The shape is what says "several people" before the name has been
   read, and it is the one difference the design asks for between a group row and a person's. */
.ch-face-group :is(.ch-avatar, img) { border-radius: 10px; }

/* THE ROUND TEAL +, which is what the design puts at the top of the list and the only way in to a
   FIRST group. Teal because this one is Chat doing something of its own, unlike the door to Rooms
   below, which is deliberately not. */
/* The muted mark, beside the unread count and never instead of it: muting stops the interrupting,
   not the counting. Quiet on purpose — it is a state, not a number to read. */
.ch-muted-mark { display: inline-flex; font-size: var(--fs-xs); opacity: .55; }

/* A call's record, which is a notice with one more thing to say: it is about these two people
   rather than about the conversation, so it carries the teal a call is drawn in. */
.ch-notice-call { border-color: var(--ch-primary-line); color: var(--ch-primary-text); font-weight: 600; }

/* The two that ring somebody, beside the one that starts a meeting: the same quiet button, and
   the words on them are what tells the three apart. */
.ch-ring { white-space: nowrap; }

/* ---- a call (Chat V2, 2.5) -------------------------------------------------------------------- */

/*
  A STRIP ABOVE THE THREAD, NOT A SCREEN OF ITS OWN.

  Chat is a window on a desk, often 570px wide. A call that covered the conversation would hide the
  thing people reach for most DURING one — what was said a minute ago — and the handoff's own call
  layout is explicit that it must not copy the full-screen dark call with round buttons.
*/
.ch-call-strip {
  flex: none; display: grid; gap: 8px; padding: 10px 12px;
  background: var(--ch-primary-soft); border-block-end: 1px solid var(--ch-primary-line);
}

.ch-call-strip .ch-call-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* The one thing somebody looks at: who, or how long. It takes the room, and the buttons take the end. */
.ch-call-what {
  flex: 1; min-inline-size: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-sm); font-weight: 600; color: var(--ch-primary-text);
  font-variant-numeric: tabular-nums;
}

/* An incoming call is the one thing here that should catch an eye across a room. */
.ch-call-strip[data-phase="incoming"] { background: var(--ch-primary); }
.ch-call-strip[data-phase="incoming"] .ch-call-what { color: var(--ch-on-primary); }

.ch-call-them, .ch-call-me { border-radius: 10px; background: #000; object-fit: cover; }
.ch-call-them { inline-size: 100%; max-block-size: 320px; }

/* Your own picture, small and in the corner of theirs: it is a check that the camera is on, not
   something to watch. */
.ch-call-me { position: absolute; inset-block-end: 12px; inset-inline-end: 12px; inline-size: 96px; }

.ch-call-strip { position: relative; }

/* ---- people from your own contacts (Chat V2, 2.3) --------------------------------------------- */

.ch-import {
  position: absolute; inset-block-start: 50%; inset-inline-start: 50%; translate: -50% -50%;
  inline-size: min(480px, calc(100% - 32px)); max-block-size: min(680px, calc(100% - 32px));
  display: flex; flex-direction: column; z-index: 41;
  background: var(--ch-card); border: 1px solid var(--ch-border); border-radius: 16px;
  box-shadow: 0 18px 48px rgb(0 0 0 / .22); overflow: hidden;
}

.ch-import-body { padding: var(--sp-4); display: grid; gap: 10px; overflow: auto; }
.ch-import-says { margin: 0; color: var(--ch-text-2); font-size: var(--fs-sm); }

.ch-import-ways { display: flex; gap: 8px; }
.ch-import-ways .ch-btn { flex: 1; justify-content: center; }

/* A label that looks like a button, because a file input cannot be styled and a button cannot open
   a file dialog. The input itself is inside it and invisible. */
.ch-import-file { position: relative; overflow: hidden; text-align: center; cursor: pointer; }
.ch-import-file input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }

.ch-import-paste { display: grid; gap: 4px; font-size: var(--fs-xs); color: var(--ch-muted); }
.ch-import-paste textarea {
  inline-size: 100%; padding: 8px 10px; border: 1px solid var(--ch-input-border); border-radius: 10px;
  background: var(--ch-field); color: var(--ch-text); font: inherit; font-size: var(--fs-sm); resize: vertical;
}

.ch-import-count { margin: 0; font-size: var(--fs-sm); font-weight: 600; color: var(--ch-text); }

.ch-import-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 4px; }
.ch-import-list li { display: flex; align-items: center; gap: 10px; padding: 4px 0; }
.ch-import-name { flex: 1; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-import-asked { font-size: var(--fs-xs); font-weight: 600; color: var(--ch-success-text); }

.ch-import-note { margin: 0; color: var(--ch-muted); font-size: var(--fs-xs); line-height: 1.55; }

.ch-add-import { inline-size: 100%; justify-content: center; margin-block-start: 2px; }

/* ---- forwarding a message (Chat V2, 2.1) ------------------------------------------------------ */

.ch-forward {
  position: absolute; inset-block-start: 50%; inset-inline-start: 50%; translate: -50% -50%;
  inline-size: min(460px, calc(100% - 32px)); max-block-size: min(620px, calc(100% - 32px));
  display: flex; flex-direction: column; z-index: 41;
  background: var(--ch-card); border: 1px solid var(--ch-border); border-radius: 16px;
  box-shadow: 0 18px 48px rgb(0 0 0 / .22); overflow: hidden;
}

.ch-forward-body { padding: var(--sp-4); display: grid; gap: 10px; overflow: auto; }

/* What is being sent, quoted the way a reply quotes: it is the same act of carrying somebody's words
   somewhere, and seeing them first is what stops the wrong line going. */
.ch-forward-what {
  margin: 0; padding: 6px 10px; border-inline-start: 4px solid var(--ch-primary); border-radius: 6px;
  background: var(--ch-quote-bg); color: var(--ch-text-2); font-size: var(--fs-sm);
  max-block-size: 4.5em; overflow: hidden;
}

.ch-forward-files { margin: 0; color: var(--ch-muted); font-size: var(--fs-xs); }

.ch-forward-find {
  inline-size: 100%; padding: 8px 12px; border: 1px solid var(--ch-input-border); border-radius: 10px;
  background: var(--ch-field); color: var(--ch-text); font: inherit; font-size: var(--fs-sm);
}
.ch-forward-find:focus-visible { outline: 2px solid var(--ch-focus); outline-offset: 1px; }

.ch-forward-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 2px; }
.ch-forward-list button {
  inline-size: 100%; display: flex; align-items: center; gap: 10px; padding: 8px;
  border: 0; border-radius: 10px; background: none; color: var(--ch-text); font: inherit;
  text-align: start; cursor: pointer;
}
.ch-forward-list button:hover { background: var(--ch-field); }
.ch-forward-list button:disabled { opacity: .6; cursor: default; }
.ch-forward-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-sm); }

/* ---- replies: the quote in the bubble, and the bar above the composer (Chat V2, 2.1) ---------- */

/*
  THE QUOTE, INSIDE THE BUBBLE. A 4px bar on the leading edge and the quoted name in that person's
  own colour, as the handoff draws it. A button, because it goes to what was said — so it carries no
  button chrome at all and reads as part of the bubble until the pointer is over it.
*/
.ch-quote {
  display: block; inline-size: 100%; margin-block-end: 4px; padding: 4px 8px;
  border: 0; border-inline-start: 4px solid var(--ch-primary); border-radius: 6px;
  background: var(--ch-quote-bg); color: inherit; font: inherit; text-align: start; cursor: pointer;
}
.ch-quote:hover { background: var(--ch-quote-hover); }
.ch-quote:focus-visible { outline: 2px solid var(--ch-focus); outline-offset: 1px; }

.ch-quote-who { display: block; font-size: var(--fs-xs); font-weight: 700; }

/* One line, cut. A quote of six lines is the message above drawn twice, and the reply is what the
   reader came for. */
.ch-quote-text {
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-xs); color: var(--ch-text-2);
}

.ch-replying {
  display: flex; align-items: center; gap: 8px;
  inline-size: 100%; order: -1; margin-block-end: 6px; padding: 6px 10px;
  border-radius: 8px; background: var(--ch-quote-bg);
}
.ch-replying-bar { flex: none; inline-size: 4px; align-self: stretch; border-radius: 2px; background: var(--ch-primary); }
.ch-replying-who { flex: none; font-size: var(--fs-xs); font-weight: 700; color: var(--ch-text); }
.ch-replying-text {
  flex: 1; min-inline-size: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-xs); color: var(--ch-muted);
}
.ch-replying-drop {
  flex: none; padding: 0 4px; border: 0; background: none;
  color: var(--ch-muted); font: inherit; font-size: var(--fs-sm); cursor: pointer;
}
.ch-replying-drop:hover { color: var(--ch-text); }

/* ---- a group thread: who said it, and the lines nobody said (Chat V2, 2.1) -------------------- */

/* THE CONVERSATION TALKING ABOUT ITSELF. Centred, quiet, no bubble and no face: it is not somebody
   speaking, and drawn as a bubble it reads as a message from a person who has become invisible. */
.ch-notice {
  /* fit-content, or a block paragraph fills the thread and the pill is a stripe across it. */
  margin: 10px auto; padding: 4px 12px; inline-size: fit-content; max-inline-size: 70%;
  border: 1px solid var(--ch-border); border-radius: 999px; background: var(--ch-card);
  color: var(--ch-muted); font-size: var(--fs-xs); text-align: center;
}

/* The sender's name inside the first bubble of a run, in the colour their face wears — the two are
   the same identification, and a different colour in each would be two. */
.ch-said-by {
  display: block; margin-block-end: 2px; font-size: var(--fs-xs); font-weight: 700;
}

.ch-new-group {
  flex: none; display: grid; place-items: center;
  inline-size: 28px; block-size: 28px; padding: 0;
  border: 0; border-radius: 50%; background: var(--ch-primary); color: #fff;
  font: inherit; font-size: var(--fs-md); font-weight: 700; line-height: 1; cursor: pointer;
}
.ch-new-group:hover { filter: brightness(1.06); }
.ch-new-group:focus-visible { outline: 2px solid var(--ch-focus); outline-offset: 2px; }


.ch-newgroup {
  position: absolute; inset-block-start: 50%; inset-inline-start: 50%; translate: -50% -50%;
  inline-size: min(560px, calc(100% - 32px)); max-block-size: min(680px, calc(100% - 32px));
  display: flex; flex-direction: column;
  background: var(--ch-card); border: 1px solid var(--ch-border); border-radius: 16px;
  box-shadow: 0 24px 64px rgb(0 0 0 / .22);
  z-index: 41;
}
.ch-newgroup-body { flex: 1; min-block-size: 0; overflow-y: auto; padding: var(--sp-4); display: grid; gap: 10px; }
.ch-newgroup-field { display: grid; gap: 6px; }
.ch-newgroup-field span { font-size: var(--fs-sm); font-weight: 600; }
.ch-newgroup-field input {
  padding: 9px 12px; border: 1px solid var(--ch-input-border); border-radius: 10px;
  background: var(--ch-card); color: var(--ch-text); font: inherit; font-size: var(--fs-md);
}
.ch-newgroup-count { margin: 4px 0 0; font-size: var(--fs-sm); font-weight: 600; color: var(--ch-text); }

.ch-pick-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 6px; }
.ch-pick-list label {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px;
  border: 1px solid var(--ch-border); border-radius: 12px; cursor: pointer;
}
.ch-pick-list label:hover { border-color: var(--ch-input-border); }

/* The chosen row is tinted rather than only ticked: at four or five people the ticks are small and
   the answer to "who is in this" should be readable without hunting for them. */
.ch-pick-list label.is-on { border-color: var(--ch-primary-line); background: var(--ch-primary-soft-2); }
.ch-pick-name { flex: 1; min-inline-size: 0; font-size: var(--fs-md); font-weight: 600; }

/* The nudge towards a Room borrows Rooms' own colour, so it reads as a door to somewhere else
   rather than as part of Chat. */
.ch-rooms-nudge {
  margin: 4px 0 0; padding: 10px 12px; border-radius: 12px;
  border: 1px solid var(--ch-rooms-line); background: var(--ch-rooms-bg);
  color: var(--ch-text); font-size: var(--fs-sm); line-height: 1.5;
}
.ch-rooms-nudge a { color: var(--ch-rooms-link); font-weight: 700; }

.ch-newgroup-foot {
  display: flex; justify-content: flex-end; gap: 8px;
  padding: 12px var(--sp-4); border-block-start: 1px solid var(--ch-border);
}

/* ---- contacts (Chat V2, 2.3) ----------------------------------------------------------------- */

/* A panel over the app rather than a fourth column: it is a place you go to and come back from,
   and nobody keeps their contact list open while they chat. */
.ch-contacts {
  position: absolute; inset-block: 0; inset-inline-end: 0;
  inline-size: min(560px, 100%);
  display: flex; flex-direction: column;
  background: var(--ch-page); border-inline-start: 1px solid var(--ch-border);
  box-shadow: -18px 0 48px rgb(0 0 0 / .14);

  /* 41, the same as the saved-messages panel, and it has to be ABOVE the dimming button at 40.
     Below it the panel is perfectly visible and completely untouchable: every click lands on the
     scrim, which closes it. */
  z-index: 41;
}
.ch-contacts-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 14px var(--sp-4); border-block-end: 1px solid var(--ch-border); background: var(--ch-card);
}
.ch-contacts-head h2 { margin: 0; font-size: var(--fs-lg); font-weight: 700; }

.ch-add {
  display: grid; gap: 6px; padding: var(--sp-4);
  background: var(--ch-card); border-block-end: 1px solid var(--ch-border);
}
.ch-add label { font-size: var(--fs-sm); font-weight: 600; color: var(--ch-text); }
.ch-add-row { display: flex; align-items: center; gap: 8px; }
.ch-add-row input {
  flex: 1; min-inline-size: 0; padding: 9px 12px;
  border: 1px solid var(--ch-input-border); border-radius: 10px;
  background: var(--ch-card); color: var(--ch-text); font: inherit; font-size: var(--fs-md);
}
.ch-add-row input:focus { outline: 2px solid var(--ch-focus); outline-offset: 1px; }

/* The tag sits INSIDE the row beside the field, where somebody's eye already is, rather than under
   it where it would push the button around as it appears and disappears. */
.ch-add-tag {
  padding: 3px 9px; border-radius: 999px;
  background: var(--ch-primary-soft); color: var(--ch-primary-text);
  font-size: var(--fs-xs); font-weight: 700; white-space: nowrap;
}

.ch-add-problem { margin: 0; color: var(--ch-danger); font-size: var(--fs-sm); font-weight: 550; }
.ch-add-sent { margin: 0; color: var(--ch-success-text); font-size: var(--fs-sm); font-weight: 550; }
.ch-add-help { margin: 0; color: var(--ch-muted); font-size: var(--fs-xs); line-height: 1.5; }

.ch-tabs { display: flex; gap: 2px; padding: 0 var(--sp-4); background: var(--ch-card); border-block-end: 1px solid var(--ch-border); }
.ch-tab {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 10px 12px; border: 0; border-block-end: 2px solid transparent; background: none;
  color: var(--ch-muted); font: inherit; font-size: var(--fs-sm); font-weight: 600; cursor: pointer;
}
.ch-tab:hover { color: var(--ch-text); }
.ch-tab.is-on { color: var(--ch-text); border-block-end-color: var(--ch-primary); }

/* The count is red because it is somebody waiting on an answer, not a quantity of things. */
.ch-tab-count {
  min-inline-size: 18px; padding: 0 5px; border-radius: 999px;
  background: var(--ch-badge); color: #fff;
  font-size: var(--fs-xs); font-weight: 700; text-align: center;
}

.ch-contacts-body { flex: 1; min-block-size: 0; overflow-y: auto; padding: var(--sp-3) 0; }
.ch-contacts-empty { margin: 0; padding: var(--sp-4); color: var(--ch-muted); font-size: var(--fs-sm); }

.ch-contact-list { margin: 0; padding: 0; list-style: none; }
.ch-contact-list li {
  display: flex; align-items: center; gap: 10px;
  padding: 10px var(--sp-4); border-block-end: 1px solid var(--ch-divider);
}
.ch-contact-main { flex: 1; min-inline-size: 0; display: grid; }
.ch-contact-name {
  font-size: var(--fs-md); font-weight: 600; color: var(--ch-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ch-contact-standing { font-size: var(--fs-xs); color: var(--ch-muted); }

/* ---- the details panel's quick actions and shared files (Chat V2, 2.1) ----------------------- */

.ch-details-quick { display: flex; gap: 8px; padding: 0 var(--sp-4) var(--sp-3); }
.ch-details-quick .ch-btn { flex: 1; justify-content: center; }

.ch-details-files { padding: 0 var(--sp-4) var(--sp-3); border-block-start: 1px solid var(--ch-border); }
.ch-details-files h3 {
  margin: var(--sp-3) 0 var(--sp-2);
  font-size: var(--fs-sm); font-weight: 700; color: var(--ch-text);
}
.ch-details-files ul { margin: 0; padding: 0; list-style: none; display: grid; gap: 4px; }
.ch-details-files a {
  display: block; padding: 6px 8px; border-radius: 8px;
  color: var(--ch-text); font-size: var(--fs-sm); text-decoration: none;
}
.ch-details-files a:hover { background: var(--ch-field); }

/* Long file names are cut rather than wrapped: three lines of one name pushes the rest of a short
   list off the panel, and the part that identifies a file is usually its beginning. */
.ch-details-file-name {
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---- a group's details panel (Chat V2, 2.1) -------------------------------------------------- */

/* The big tile at the top of a group's panel: the same rounded square as its list row, at the size
   of a person's face. The shape is the whole distinction — a circle is somebody, a square is a
   several. */
.ch-face-big :is(.ch-avatar, img) { border-radius: 16px; }

.ch-details-members { padding: 0 var(--sp-4) var(--sp-3); border-block-start: 1px solid var(--ch-border); }
.ch-details-members h3 {
  display: flex; align-items: baseline; gap: 8px;
  margin: var(--sp-3) 0 var(--sp-2);
  font-size: var(--fs-sm); font-weight: 700; color: var(--ch-text);
}

/* "4 members · 2 online" beside the heading rather than under it: it is the heading's subject, and
   a line of its own would push the first name below the fold on a short panel. */
.ch-details-count { margin-inline-start: auto; font-weight: 500; font-size: var(--fs-xs); color: var(--ch-muted); }

.ch-member-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 2px; }
.ch-member-list li {
  display: flex; align-items: center; gap: 10px; padding: 6px 8px; border-radius: 8px;
}
.ch-member-name {
  flex: 1; min-inline-size: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-sm); color: var(--ch-text);
}
.ch-member-here { font-size: var(--fs-xs); font-weight: 600; color: var(--ch-success-text); }

.ch-details-add { inline-size: 100%; justify-content: center; margin-block-start: var(--sp-2); }

/*
  THE DOOR TO ROOMS, IN ROOMS' OWN INDIGO.

  The same decision as the nudge on the New group window: a way out of this app should not be
  painted in the colour of the app it is in, or it reads as one more thing Chat does.
*/
.ch-details-toroom {
  padding: var(--sp-3) var(--sp-4);
  border-block-start: 1px solid var(--ch-border);
}
.ch-details-toroom .ch-btn {
  inline-size: 100%; justify-content: center;
  border-color: var(--ch-rooms-line); background: var(--ch-rooms-bg); color: var(--ch-rooms-link);
}
.ch-details-toroom p {
  margin: 6px 0 0; color: var(--ch-muted); font-size: var(--fs-xs); line-height: 1.55;
}

/* Asked once, in the panel, rather than in a dialogue over the whole app. Leaving is not dangerous
   enough to interrupt the screen for, and it is not casual enough to happen on one click. */
.ch-details-sure {
  margin: 0 0 6px; font-size: var(--fs-sm); font-weight: 600; color: var(--ch-text);
}

/* ---- the list's chips, and the dot on a face (Chat V2, 2.1) ---------------------------------- */

.ch-filters { display: flex; gap: 6px; padding: 4px 12px 8px; }

.ch-filter {
  padding: 4px 12px; border: 1px solid var(--ch-border); border-radius: 999px;
  background: var(--ch-card); color: var(--ch-muted);
  font: inherit; font-size: var(--fs-sm); font-weight: 600; cursor: pointer;
}
.ch-filter:hover { color: var(--ch-text); border-color: var(--ch-input-border); }

/* The pressed chip is ink rather than teal. Teal is what this app SAYS things with — bubbles, Send,
   unread counts — and a teal chip beside a teal count reads as another count. */
.ch-filter.is-on { background: var(--ch-text); border-color: var(--ch-text); color: var(--ch-card); }
.ch-filter:focus-visible { outline: 2px solid var(--ch-focus); outline-offset: 2px; }

/* THE GREEN DOT SITS ON THE FACE, not beside the name, because it is a fact about the person and
   not about the conversation. The ring is the list's own background rather than white, so the dot
   reads as cut out of the avatar on a row that is highlighted as well as on one that is not. */
.ch-face { position: relative; display: inline-flex; }
.ch-face-online::after {
  content: '';
  position: absolute; inset-block-end: 0; inset-inline-end: 0;
  inline-size: 10px; block-size: 10px; border-radius: 50%;
  background: var(--ch-online);
  box-shadow: 0 0 0 2px var(--ch-page);
}

.ch-saved-open {
  display: flex; align-items: center; gap: 10px; margin: 2px 10px 6px; padding: 8px 10px;
  border: 0; border-radius: 12px; background: none;
  color: var(--ch-text); font: inherit; font-size: var(--fs-md); font-weight: 600; cursor: pointer;
}
.ch-saved-open:hover { background: var(--ch-field); }
/* The bookmark sits in a soft teal disc the size of the avatars below it, so the row it heads lines
   up with the conversations rather than floating above them as a stray piece of text. */
.ch-saved-mark {
  flex: none; display: grid; place-items: center; inline-size: 32px; block-size: 32px;
  border-radius: 50%; background: var(--ch-primary-soft); color: var(--ch-primary); font-size: var(--fs-md);
}

.ch-saved {
  position: absolute; inset-block: 0; inset-inline-end: 0; z-index: 41;
  inline-size: min(420px, 100%); display: grid; grid-template-rows: auto minmax(0, 1fr);
  background: var(--ch-card); border-inline-start: 1px solid var(--ch-border);
  box-shadow: -8px 0 24px rgb(0 0 0 / .18);
}
.ch-saved-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 12px 14px;
  border-block-end: 1px solid var(--ch-border); background: var(--ch-card);
}
.ch-saved-title { margin: 0; font-size: var(--fs-md); font-weight: 700; }
.ch-saved-none { padding: 22px 16px; }
.ch-saved-none-title { margin: 0 0 4px; font-weight: 700; }
/* align-content: start, which is not a nicety. This grid sits in the panel's 1fr row, so its rows
   stretch to fill the height by default — three saved messages spread evenly down 900px, with the
   gaps reading as missing entries. The screenshot is how it was found. */
.ch-saved-list {
  list-style: none; margin: 0; padding: 8px; display: grid; gap: 1px;
  align-content: start; overflow-y: auto;
}
.ch-saved-row {
  display: grid; gap: 2px; inline-size: 100%; min-inline-size: 0; padding: 9px 11px; border: 0;
  border-radius: 10px; background: transparent; color: var(--ch-text); font: inherit;
  font-size: var(--fs-sm); text-align: start; cursor: pointer;
}
.ch-saved-row:hover { background: var(--ch-field); }
.ch-saved-where { font-size: var(--fs-xs); font-weight: 700; color: var(--ch-muted); }
/* Two lines, then it trails off: this is a list of things to go back to, not a second thread. */
.ch-saved-text {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  overflow-wrap: anywhere;
}

/* The saved panel's own scrim covers the CONVERSATION LIST as well as the thread, unlike the
   drawer's — pressing an entry is about to change which conversation is open, so the list behind it
   is not somewhere to go next. */
/* Styled in full rather than by adding to .ch-scrim: that one is `display: none` until the narrow
   container query, because the drawer it belongs to only exists there. This one is shown at every
   width, so it carries its own position and its own ground. */
.ch-scrim-all {
  display: block; position: absolute; inset: 0; z-index: 40; padding: 0; border: 0;
  background: color-mix(in srgb, #000 38%, transparent); cursor: default;
}

/* ================================================================================================
   WORKFLOWS — the list and the builder (the Switchboard conversion)
   ================================================================================================ */

/* THE HUES THIS SCREEN HARD-CODED, and why they are tokens now (17 September).

   The conversion wrote the LIGHT accents straight into the rules — #0C7F62, #34478C, #B45A2B and
   a light amber #9C6600 — in sixteen places: a status word, a pill, a port, a group stripe, a
   log stripe, an issue stripe. On paper they are right. On a dark page they never were. As text,
   #0C7F62 was 2.77:1 on the old dark card and is 3.89:1 on the new one, and #9C6600 is 3.97:1 —
   both under the 4.5:1 body text needs; as a 3px stripe or a 14px port, #34478C is 2.23:1, under
   the 3:1 a meaningful graphic needs.

   Three of the four are already a pair in this file — --a-pine, --a-ink and --a-clay are declared
   once per theme near the top, and their LIGHT values are byte-for-byte the hexes that were here,
   so nothing on paper moves. The amber is the odd one out: it is not an accent, so it gets the one
   new token below. In dark the four become 8.7:1, 7.4:1, 7.2:1 and 11.6:1 on a card. */
:root               { --wf-warn: #9C6600; }
[data-theme="dark"] { --wf-warn: #FBBF24; }

.wf-mount {
  container-type: inline-size; min-block-size: 100dvh;
  background: var(--bg); color: var(--text); font-family: var(--font);
}
.wf-app { display: flex; flex-direction: column; block-size: 100dvh; min-block-size: 0; }
.wf-grow { flex: 1; }
.wf-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.wf-row-between { justify-content: space-between; }
.wf-note { margin: 8px 0; color: var(--muted); font-size: var(--fs-sm); }
.wf-hint { margin: 4px 0 0; color: var(--muted); font-size: var(--fs-xs); line-height: 1.45; }
.wf-problem { margin: 12px 16px; color: var(--critical); font-size: var(--fs-sm); }
.wf-warn {
  margin: 6px 0; padding: 8px 10px; border-radius: 8px; font-size: var(--fs-xs); line-height: 1.45;
  background: color-mix(in srgb, var(--critical) 9%, transparent); color: var(--text);
  border-inline-start: 3px solid var(--critical);
}

.wf-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-block-size: 34px;
  padding: 6px 12px; border-radius: 9px; border: 1px solid var(--border-strong); background: var(--surface);
  color: var(--text); font: inherit; font-size: var(--fs-sm); font-weight: 600; cursor: pointer; white-space: nowrap;
}
.wf-btn:hover { background: var(--surface-3); }
.wf-btn:disabled { opacity: .55; cursor: default; }
.wf-btn-primary { background: var(--accent-fill); border-color: var(--accent); color: var(--on-accent); }
.wf-btn-primary:hover { background: var(--accent); filter: brightness(1.08); }
.wf-btn-quiet { border-color: transparent; background: transparent; }
.wf-btn-danger { margin-block-start: 14px; border-color: color-mix(in srgb, var(--critical) 50%, var(--border)); color: var(--critical); }
.wf-btn[aria-pressed="true"] { background: var(--surface-3); border-color: var(--accent); }

.wf-input {
  inline-size: 100%; min-inline-size: 0; padding: 7px 9px; border-radius: 8px; border: 1px solid var(--border-strong);
  background: var(--surface); color: var(--text); font: inherit; font-size: var(--fs-sm);
}
.wf-input:focus { outline: 2px solid var(--accent); outline-offset: 0; border-color: var(--accent); }
.wf-textarea { min-block-size: 76px; resize: vertical; font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; font-size: var(--fs-xs); }
.wf-field { display: grid; gap: 4px; margin-block-start: 12px; font-size: var(--fs-xs); font-weight: 600; color: var(--muted); }

.wf-chip {
  display: inline-block; padding: 2px 8px; border-radius: 999px; background: var(--surface-3);
  color: var(--text); font-size: var(--fs-xs); font-weight: 600; white-space: nowrap;
}
.wf-chip-button { border: 1px solid var(--border); cursor: pointer; font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; font-weight: 500; }
.wf-chip-button:hover { border-color: var(--accent); }
.wf-pill { padding: 2px 10px; border-radius: 999px; background: var(--surface-3); color: var(--muted); font-size: var(--fs-xs); font-weight: 700; }
.wf-pill-on { background: color-mix(in srgb, var(--a-pine) 18%, transparent); color: var(--a-pine); }

.wf-status { font-weight: 700; }
.wf-status-succeeded { color: var(--a-pine); }
.wf-status-failed { color: var(--critical); }
.wf-status-sleeping, .wf-status-queued, .wf-status-running { color: var(--accent); }
.wf-status-parked { color: var(--wf-warn); }

/* ---- the list ---- */
.wf-list { padding: 20px 20px 32px; overflow: auto; min-block-size: 0; }
.wf-list-top { display: flex; align-items: flex-start; gap: 16px; justify-content: space-between; }
.wf-list-title { margin: 0; font-size: var(--fs-lg); font-weight: 700; }
.wf-list-intro { margin: 4px 0 0; max-inline-size: 60ch; color: var(--muted); font-size: var(--fs-sm); line-height: 1.5; }
.wf-new { display: flex; gap: 8px; margin-block-start: 16px; max-inline-size: 560px; }
.wf-cards { list-style: none; margin: 18px 0 0; padding: 0; display: grid; gap: 8px; }
.wf-card {
  inline-size: 100%; display: grid; grid-template-columns: minmax(0, 1fr) auto auto; grid-template-areas: "name triggers pill" "last last last";
  gap: 6px 12px; align-items: center; padding: 12px 14px; border-radius: 12px; border: 1px solid var(--border);
  background: var(--surface); color: var(--text); font: inherit; text-align: start; cursor: pointer;
}
.wf-card:hover { border-color: var(--accent); }
.wf-card-name { grid-area: name; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wf-card-triggers { grid-area: triggers; display: flex; gap: 4px; flex-wrap: wrap; justify-content: flex-end; }
.wf-card .wf-pill { grid-area: pill; }
.wf-card-last { grid-area: last; color: var(--muted); font-size: var(--fs-xs); }

/* ---- the builder ---- */
.wf-builder { display: flex; flex-direction: column; flex: 1; min-block-size: 0; }
.wf-bar {
  display: flex; align-items: center; gap: 8px; padding: 8px 12px; flex-wrap: wrap;
  border-block-end: 1px solid var(--border); background: var(--surface-2);
}
.wf-bar-name { inline-size: auto; flex: 0 1 280px; font-weight: 700; }
.wf-save { font-size: var(--fs-xs); color: var(--muted); }
.wf-save-failed { color: var(--critical); }
.wf-switch { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-xs); font-weight: 600; cursor: pointer; }
.wf-switch input { inline-size: 16px; block-size: 16px; accent-color: var(--a-pine); }

.wf-notice, .wf-problems {
  display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap; padding: 8px 14px; font-size: var(--fs-sm);
  border-block-end: 1px solid var(--border);
}
.wf-notice { background: color-mix(in srgb, var(--critical) 8%, var(--surface)); }
.wf-problems { background: color-mix(in srgb, var(--wf-warn) 9%, var(--surface)); }
.wf-notice ul, .wf-problems ul { margin: 0; padding-inline-start: 18px; flex-basis: 100%; }
.wf-notice .wf-btn { margin-inline-start: auto; min-block-size: 26px; padding: 2px 8px; }

.wf-body { flex: 1; min-block-size: 0; display: grid; grid-template-columns: 220px minmax(0, 1fr) 300px; }

.wf-palette { overflow: auto; padding: 10px; border-inline-end: 1px solid var(--border); background: var(--surface); }
.wf-palette-group + .wf-palette-group { margin-block-start: 12px; }
.wf-palette-head { margin: 0 0 6px; font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
.wf-palette-item {
  display: grid; gap: 1px; inline-size: 100%; margin-block-end: 4px; padding: 7px 9px; border-radius: 9px;
  border: 1px solid var(--border); border-inline-start: 4px solid var(--wf-group); background: var(--surface);
  color: var(--text); font: inherit; text-align: start; cursor: pointer;
}
.wf-palette-item:hover { background: var(--surface-3); }
.wf-palette-name { font-size: var(--fs-sm); font-weight: 600; }
.wf-palette-help { font-size: var(--fs-xs); color: var(--muted); line-height: 1.35; }

.wf-group-start { --wf-group: var(--a-pine); }
.wf-group-decide { --wf-group: var(--wf-warn); }
.wf-group-work { --wf-group: var(--a-ink); }
.wf-group-finish { --wf-group: var(--a-clay); }

/* The canvas: a dotted field that pans, with the links drawn under the blocks. */
.wf-canvas {
  position: relative; overflow: hidden; min-block-size: 0; cursor: grab; touch-action: none;
  background-color: var(--bg);
  background-image: radial-gradient(color-mix(in srgb, var(--text) 16%, transparent) 1px, transparent 1px);
  background-size: 20px 20px;
}
.wf-canvas:active { cursor: grabbing; }
.wf-layer { position: absolute; inset: 0; }
.wf-canvas-empty {
  position: absolute; inset-inline: 24px; inset-block-start: 40%; margin: 0 auto; max-inline-size: 44ch;
  text-align: center; color: var(--muted); font-size: var(--fs-sm); line-height: 1.5; pointer-events: none;
}
.wf-links { position: absolute; inset-block-start: 0; inset-inline-start: 0; inline-size: 1px; block-size: 1px; overflow: visible; pointer-events: none; }
.wf-link { fill: none; stroke: color-mix(in srgb, var(--text) 45%, transparent); stroke-width: 2; }
.wf-link-hit { fill: none; stroke: transparent; stroke-width: 14; pointer-events: stroke; cursor: pointer; }
.wf-link-error { stroke: var(--critical); }
.wf-link-filtered { stroke-dasharray: 6 4; }
.wf-link-on { stroke: var(--accent); stroke-width: 3; }
.wf-link-drawing { stroke: var(--accent); stroke-dasharray: 4 4; }

.wf-node {
  position: absolute; box-sizing: border-box; display: grid; align-content: center; gap: 1px; padding: 8px 12px;
  border-radius: 12px; border: 1px solid var(--border-strong); border-block-start: 4px solid var(--wf-group);
  background: var(--surface); color: var(--text); cursor: move; user-select: none;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .12);
}
.wf-node-on { outline: 2px solid var(--accent); outline-offset: 2px; }
.wf-node-other { border-style: dashed; }
.wf-node-kind { font-size: 10px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wf-node-title { font-size: var(--fs-sm); font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wf-node-sub { font-size: var(--fs-xs); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; }

.wf-port {
  position: absolute; inline-size: 14px; block-size: 14px; margin-inline-start: -7px; border-radius: 999px;
  border: 2px solid var(--surface); background: color-mix(in srgb, var(--text) 55%, transparent);
}
.wf-port-in { inset-block-start: -8px; inset-inline-start: 50%; background: var(--wf-group); pointer-events: none; }
.wf-port-out { inset-block-end: -8px; cursor: crosshair; }
.wf-port-out:hover { background: var(--accent); transform: scale(1.25); }
.wf-port-yes { background: var(--a-pine); }
.wf-port-no { background: var(--wf-warn); }
.wf-port-error { background: var(--critical); }
.wf-port-word {
  position: absolute; inset-block-start: 14px; inset-inline-start: 50%; transform: translateX(-50%);
  font-size: 10px; font-weight: 700; color: var(--muted); white-space: nowrap; pointer-events: none;
}

/* The side: a block's settings, a link's filter, or the runs. */
.wf-side { overflow: auto; padding: 12px 14px 24px; border-inline-start: 1px solid var(--border); background: var(--surface); }
.wf-side-head { margin: 14px 0 2px; font-size: var(--fs-sm); font-weight: 700; }
.wf-side-head:first-child { margin-block-start: 0; }
.wf-side-empty .wf-note { margin-block-start: 0; }
.wf-hook { margin-block-start: 12px; }
.wf-hook-address {
  display: block; margin: 6px 0; padding: 8px; border-radius: 8px; background: var(--surface-3);
  font-size: var(--fs-xs); overflow-wrap: anywhere; user-select: all;
}
.wf-refs-list { display: flex; flex-wrap: wrap; gap: 4px; margin-block-start: 6px; }

.wf-run-list { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 4px; }
.wf-run-row {
  inline-size: 100%; display: grid; gap: 2px; padding: 7px 9px; border-radius: 9px; border: 1px solid var(--border);
  background: var(--surface); color: var(--text); font: inherit; font-size: var(--fs-sm); text-align: start; cursor: pointer;
}
.wf-run-row-on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, var(--surface)); }
.wf-run-meta { font-size: var(--fs-xs); color: var(--muted); }
.wf-log { margin-block-start: 12px; }
.wf-log ol { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.wf-log-line {
  display: grid; gap: 1px; padding: 6px 8px; border-radius: 8px; background: var(--surface-2);
  border-inline-start: 3px solid var(--a-pine); font-size: var(--fs-xs);
}
.wf-log-bad { border-inline-start-color: var(--critical); }
.wf-log-title { font-weight: 700; }
.wf-log-text { overflow-wrap: anywhere; white-space: pre-wrap; font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; }

/* ---- phase 1: schedules ---- */
.wf-schedule .wf-row { align-items: center; }
.wf-schedule .wf-row .wf-input { inline-size: auto; flex: 1 1 0; min-inline-size: 0; }
.wf-days { display: flex; flex-wrap: wrap; gap: 4px; margin-block-start: 6px; }
.wf-day {
  min-inline-size: 42px; padding: 4px 8px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); color: var(--text); font: inherit; font-size: var(--fs-xs); cursor: pointer;
}
.wf-day-on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, var(--surface)); font-weight: 700; }

/* ---- phase 1: the functions a mapping field may call ---- */
.wf-functions { margin-block-start: 12px; }
.wf-functions > summary { cursor: pointer; list-style-position: inside; }
.wf-functions-group { margin-block-start: 8px; }
.wf-functions-head { font-size: var(--fs-xs); font-weight: 700; color: var(--muted); }

/* ---- phase 1: retry and cancel ---- */
.wf-status-cancelled { color: var(--muted); }
[data-testid="run-actions"] { margin-block: 8px; }

/* ---- phase 1: what a workflow keeps ---- */
.wf-kept-row { padding: 6px 8px; border: 1px solid var(--border); border-radius: 8px; display: grid; gap: 4px; }
.wf-kept-key { font-weight: 700; overflow-wrap: anywhere; }
.wf-kept-value {
  display: block; max-block-size: 7.5em; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere;
  font-size: var(--fs-xs); color: var(--muted);
}

/* ---- phase 1: pre-flight, and what a run log keeps ---- */
.wf-checks-count {
  display: inline-block; min-inline-size: 18px; margin-inline-start: 6px; padding: 0 5px; border-radius: 999px;
  font-size: var(--fs-xs); font-weight: 700; text-align: center; background: color-mix(in srgb, var(--a-pine) 16%, var(--surface));
}
.wf-checks-warn .wf-checks-count { background: color-mix(in srgb, var(--wf-warn) 22%, var(--surface)); }
.wf-checks-bad .wf-checks-count { background: color-mix(in srgb, var(--critical) 22%, var(--surface)); }
.wf-issue-list { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 6px; }
.wf-issue {
  display: grid; gap: 4px; padding: 6px 8px; border-radius: 8px; background: var(--surface-2);
  border-inline-start: 3px solid var(--wf-warn); font-size: var(--fs-xs);
}
.wf-issue-error { border-inline-start-color: var(--critical); }
.wf-issue-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.wf-issue-level { padding: 1px 8px; border-radius: 999px; font-weight: 700; background: color-mix(in srgb, var(--wf-warn) 18%, var(--surface)); }
.wf-issue-error .wf-issue-level { background: color-mix(in srgb, var(--critical) 18%, var(--surface)); }
.wf-issue-message { overflow-wrap: anywhere; }
.wf-issue-fix { overflow-wrap: anywhere; color: var(--muted); }
.wf-issue .wf-btn { min-block-size: 24px; padding: 1px 10px; }
.wf-node-shown { animation: wf-shown 1.2s ease-out 1; }
@keyframes wf-shown { from { box-shadow: 0 0 0 10px color-mix(in srgb, var(--accent) 45%, transparent); } to { box-shadow: 0 0 0 0 transparent; } }
@media (prefers-reduced-motion: reduce) { .wf-node-shown { animation: none; } }
.wf-log-more > summary { display: flex; flex-wrap: wrap; gap: 8px; cursor: pointer; color: var(--muted); }
.wf-log-more > summary::before { content: "\25B8"; }
.wf-log-more[open] > summary::before { content: "\25BE"; }
.wf-log-fact { font-variant-numeric: tabular-nums; }
.wf-log-ref { align-items: center; margin-block: 4px; }
.wf-log-ref code { overflow-wrap: anywhere; }
.wf-log-ref .wf-btn { min-block-size: 24px; padding: 1px 8px; }
.wf-log-data {
  margin: 4px 0 0; padding: 6px 8px; max-block-size: 18em; overflow: auto; border-radius: 6px; background: var(--surface);
  white-space: pre-wrap; overflow-wrap: anywhere; font-size: var(--fs-xs);
}

/* ---- phase 1: the list's actions, import, and searching ---- */
.wf-list-search { margin-block-start: 16px; max-inline-size: 360px; }
.wf-list-said { margin-block-start: 12px; max-inline-size: 640px; }
.wf-list-said ul { margin: 4px 0 0; padding-inline-start: 18px; }
.wf-import { display: grid; gap: 4px; margin-block-start: 12px; max-inline-size: 640px; }
.wf-card-item { display: grid; gap: 2px; }
.wf-card-actions { display: flex; flex-wrap: wrap; gap: 2px; padding-inline: 4px; }
.wf-card-actions .wf-btn { min-block-size: 28px; padding: 2px 8px; font-size: var(--fs-xs); }
.wf-card-actions .wf-card-delete { color: var(--critical); }
.wf-palette-search { margin-block-end: 10px; }
.wf-palette-none { margin: 0; }

/* ---- narrow: the palette becomes a strip, the side goes under the canvas ---- */
@container (max-width: 760px) {
  .wf-body { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(320px, 1fr) auto; }
  .wf-palette { display: flex; gap: 8px; overflow-x: auto; border-inline-end: 0; border-block-end: 1px solid var(--border); }
  .wf-palette-group { display: flex; gap: 4px; align-items: stretch; }
  .wf-palette-group + .wf-palette-group { margin-block-start: 0; }
  .wf-palette-head { display: none; }
  .wf-palette-item { inline-size: 150px; margin: 0; }
  .wf-palette-help { display: none; }
  .wf-palette-search { flex: 0 0 150px; margin: 0; }
  .wf-side { border-inline-start: 0; border-block-start: 1px solid var(--border); max-block-size: 45dvh; }
  .wf-bar-name { flex-basis: 100%; }
  .wf-card { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "name pill" "triggers triggers" "last last"; }
  .wf-card-triggers { justify-content: flex-start; }
  .wf-new { flex-wrap: wrap; }
}

/* ================================================================================================
   WORKFLOWS PHASE 2 (18 September) — connected apps, approvals, versions.
   Added at the END of the wf-* section, as its own marked block, so the phase 1 rules above it stay
   where anybody who learnt them expects to find them.
   ================================================================================================ */

/* ---- a block that acts through a connected app ---- */
.wf-app-settings {
  display: grid; gap: 10px; margin-block: 4px 12px;
  padding: 10px; border: 1px solid var(--border); border-radius: var(--r-md);
  background: color-mix(in srgb, var(--surface-2) 60%, transparent);
}
.wf-app-settings .wf-field { margin: 0; }
/* The marker is inline with the first word, never a flex item: a sentence long enough to wrap would
   otherwise leave the dot stranded on a line of its own. */
.wf-app-state { margin: 0; }
.wf-app-state[data-connected="yes"]::before { content: "● "; color: var(--ok); }
.wf-app-state[data-connected="no"]::before { content: "○ "; color: var(--wf-warn); }
.wf-app-link { color: var(--accent); text-decoration: underline; }

/* ---- a run waiting for a person to approve it ---- */
.wf-approval {
  display: grid; gap: 10px; margin-block: 10px;
  padding: 12px; border: 1px solid var(--accent); border-radius: var(--r-md);
  background: color-mix(in srgb, var(--accent) 8%, var(--surface));
}
.wf-approval .wf-field { margin: 0; }
.wf-approval-title { font-size: var(--fs-sm); }
.wf-approval-message { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
.wf-approval-note { min-block-size: 56px; }
/* Held-and-waiting reads as something to do, not as something that went wrong. */
.wf-status-parked.wf-status-awaiting { color: var(--accent); border-color: currentColor; }

/* ---- the drawings a workflow used to have ---- */
.wf-version { display: grid; gap: 4px; padding: 8px; border: 1px solid var(--border); border-radius: var(--r-md); }
.wf-version + .wf-version { margin-block-start: 8px; }
.wf-version[data-on="yes"] { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, transparent); }
.wf-version-number { font-size: var(--fs-sm); }
.wf-version .wf-row { margin-block-start: 4px; }

/* The banner that says the canvas is not showing what is saved. Full width, above the canvas, because a
   person who missed it would believe they were editing. */
.wf-looking {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: space-between;
  padding: 8px 12px; border-block-end: 1px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--surface));
}
.wf-canvas-readonly { cursor: grab; }
.wf-canvas-readonly .wf-node { cursor: default; opacity: 0.92; }
.wf-canvas-readonly .wf-port { display: none; }

/* ---- starting from a template ---- */
.wf-templates { display: grid; gap: 10px; margin-block-start: 12px; max-inline-size: 960px; }
.wf-template-list {
  display: grid; gap: 10px; margin: 0; padding: 0; list-style: none;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
}
.wf-template {
  display: grid; gap: 6px; align-content: start; padding: 12px;
  border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface);
}
.wf-template-name { font-size: var(--fs-sm); }
.wf-template-help { color: var(--muted); font-size: var(--fs-xs); line-height: 1.45; }
.wf-template .wf-btn { justify-self: start; margin-block-start: 4px; }

/* ================================================================================================
   ASK AI: A NEW CONVERSATION STARTS AT THE TOP (Syed, 16 September — "The chat Area i want like this")

   With nothing said yet, the question and the composer come first, on a panel tinted with the
   person's own accent, and the starters follow below. The same dock element as ever, reordered, so a
   draft typed here is still in the box when the first message goes and the dock settles at the bottom.
   ================================================================================================ */
.wsmain.is-new .wsbar { order: 0; }
.wsmain.is-new .dock {
  order: 1; position: relative; border-block-start: 0;
  padding: var(--sp-7) var(--sp-6) var(--sp-5);
  background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 10%, var(--surface)) 0%, var(--surface) 100%);
}
.wsmain.is-new .dock::before {
  content: ""; position: absolute; inset-block-start: 0; inset-inline-start: 50%; transform: translateX(-50%);
  inline-size: 120px; block-size: 3px; border-end-start-radius: 3px; border-end-end-radius: 3px; background: var(--accent);
}
.wsmain.is-new .thread { order: 2; padding-block-start: var(--sp-4); }
.dock-hero-title {
  margin: 0 0 var(--sp-3); color: var(--text);
  font-size: clamp(1.25rem, 2.4vw, 1.65rem); font-weight: 750; letter-spacing: -.01em; line-height: 1.2;
}
.wsmain.is-new .dock-box { border-radius: 16px; box-shadow: var(--sh-2, 0 4px 14px rgba(0, 0, 0, .08)); }
.wsmain.is-new .dock-box textarea { min-block-size: 96px; padding: var(--sp-4) var(--sp-4) var(--sp-2); }
.wsmain.is-new .dock-actions .btn-primary { padding-inline: 18px; min-block-size: 38px; border-radius: 10px; font-weight: 650; }

/* The model sits beside the button that sends to it, on every conversation, as in the design.
   A deliberate second row, not a wrap: the tools are more than one line holds, and left to wrap, the send
   button fell to the start of a new line on its own, away from the model it sends to (seen in the first
   screenshot). The ::after is a full-width item ordered between the two groups, which forces the break. */
.dock-foot::after { content: ""; flex-basis: 100%; order: 80; block-size: 0; }
.dock-foot > .mpick { order: 90; margin-inline-start: auto; }
.dock-foot > .dock-actions { order: 100; margin-inline-start: 0; }

/* ================================================================================================
   DESKTOP: THE FOLDER BAR (Syed, 16 September — "folder creation area should look like this")

   A full-width place to drop or choose a folder, above the apps; the folder then arrives among the tiles.
   ================================================================================================ */
/* Drawn as the approved mockup draws it (Syed, 18 September): a card like Ask AI's, an outlined square for the
   folder, the sentence at 14.5px over its note, and "Choose a folder" filled in the accent. */
.dk-folder-bar {
  display: flex; align-items: center; gap: 16px; padding: 16px 18px;
  border: 1px solid var(--dk-line); border-radius: 14px; background: var(--dk-surface);
  transition: border-color var(--dur-2) var(--ease), background var(--dur-2) var(--ease);
}
.dk-folder-bar.is-over {
  border-style: dashed; border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, var(--dk-surface));
}
.dk-folder-bar-ico {
  flex: none; inline-size: 42px; block-size: 42px; border-radius: 11px; display: grid; place-items: center;
  border: 1px solid var(--dk-line-strong); color: color-mix(in srgb, var(--dk-ink) 60%, var(--dk-ink-2));
}
.dk-folder-bar-ico svg { inline-size: 20px; block-size: 20px; }
.dk-folder-bar-text { flex: 1; min-inline-size: 0; }
.dk-folder-bar-title { margin: 0; font-size: 14.5px; font-weight: 600; color: var(--dk-ink); }
.dk-folder-bar-link {
  padding: 0; border: 0; background: none; color: inherit; font: inherit; font-weight: inherit; cursor: pointer;
}
.dk-folder-bar-link:hover { text-decoration: underline; }
.dk-folder-bar-note { margin: 3px 0 0; font-size: 13px; color: var(--dk-ink-2); }
.dk-folder-bar-ok { color: var(--ok-fg, #0a7d0a); font-weight: 600; }
.dk-folder-bar-warn { color: var(--warning-fg, #8a6100); font-weight: 600; }
.dk-folder-bar-btn {
  flex: none; display: inline-flex; align-items: center; gap: 9px; block-size: 40px; padding: 0 18px; border-radius: 10px;
  border: 0; cursor: pointer; font: inherit; font-size: 13.5px; font-weight: 600;
  color: var(--on-accent); background: var(--accent-fill);
}
.dk-folder-bar-btn:hover { background: var(--accent-fill-hover); }
.dk-folder-bar-btn svg { inline-size: 16px; block-size: 16px; }

@media (max-width: 560px) {
  .dk-folder-bar { flex-wrap: wrap; }
  .dk-folder-bar-btn { inline-size: 100%; justify-content: center; }
}

/* ---- icons on the desk (Syed, 16 September: real icons rather than letters) ------------------------------------------
   NETFIVE's own apps and a folder's chosen symbol: the symbol in its colour on a tint of it. The numbers are the ones
   icons/appIcon.ts holds every colour to 3:1 with (SYMBOL_TINT), so change both together. A library app's logo sits on
   a white chip in both themes, because a logo recoloured is no longer the logo. A folder's own picture fills its tile. */
.dk-glyph-sym {
  background: color-mix(in srgb, var(--dk-app) 14%, #FFFFFF);
  background-image: none;
  color: var(--dk-app);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--dk-app) 20%, transparent);
}
/* Dark, as the approved preview draws it (Syed, 17 September): the symbol bright, on a faint square
   of its own colour — 8% of it over the card, which is the mockup's `#4c9aff14` on `#0D0E11` — and
   no ring. The 30% tint it replaced was the old teal-grey surface and reads as a solid chip on
   near-black. SYMBOL_TINT in icons/appIcon.ts is these two numbers, and a test holds every app and
   folder colour to 3:1 with them, so the two must change together. */
[data-theme="dark"] .dk-glyph-sym {
  background: color-mix(in srgb, var(--dk-app) 8%, #0D0E11);
  color: color-mix(in srgb, var(--dk-app) 65%, #FFFFFF);
  box-shadow: none;
}
.dk-sym {
  display: block; inline-size: 58%; block-size: 58%; background: currentColor;
  -webkit-mask-repeat: no-repeat; -webkit-mask-position: center; -webkit-mask-size: contain;
  mask-repeat: no-repeat; mask-position: center; mask-size: contain;
}
.dk-glyph-logo { background: #FFFFFF; background-image: none; box-shadow: inset 0 0 0 1px rgba(15, 26, 24, .12); }
.dk-glyph-logo img { display: block; inline-size: 64%; block-size: 64%; object-fit: contain; }
.dk-glyph-picture { background: var(--dk-surface-3); background-image: none; overflow: hidden; }
.dk-glyph-picture img { display: block; inline-size: 100%; block-size: 100%; object-fit: cover; }
/* The smallest tiles, in lists and the window bar: a thinner margin, or the strokes vanish. */
.dk-glyph-sm .dk-sym { inline-size: 74%; block-size: 74%; }
.dk-glyph-sm.dk-glyph-logo img { inline-size: 78%; block-size: 78%; }
/* A full icon (Syed, 19 September 2026): the tile is the picture, corners and all, so the glyph adds nothing of its
   own — no ground, no ring, no sheen — in either theme, and the Your apps grid's neutral squircle and border stay off
   it. It keeps the glyph's size and shadow. */
.dk-glyph-tile, [data-theme="dark"] .dk-glyph-tile { background: none; background-image: none; border: 0; }
.dk-glyph-tile img { display: block; inline-size: 100%; block-size: 100%; }
.dk-tiles .dk-glyph-tile, .dk-tiles .dk-folder-glyph.dk-glyph-tile,
[data-theme="dark"] .dk-tiles .dk-folder-glyph.dk-glyph-tile, .dk-phone-grid .dk-glyph-tile { background: none; border: 0; box-shadow: none; }
.dk-tiles .dk-tile:hover .dk-glyph-tile { box-shadow: var(--dk-shadow-2); }

/* Changing a folder's icon from its tile in Customise: the opposite corner to its ×. */
.dk-tile-icon {
  position: absolute; inset-block-start: -6px; inset-inline-start: -6px; inline-size: 26px; block-size: 26px;
  border-radius: 50%; border: 1px solid var(--dk-line); background: var(--dk-surface); color: var(--dk-ink);
  display: grid; place-items: center; cursor: pointer; box-shadow: var(--dk-shadow-1);
}
.dk-tile-icon .dk-icon { inline-size: 14px; block-size: 14px; }
.dk-folder-row .dk-glyph { flex-shrink: 0; }

/* The folder icon picker. */
.dk-limit-card.dk-iconpick { inline-size: min(580px, calc(100% - 24px)); max-block-size: calc(100% - 24px); overflow-y: auto; }
.dk-iconpick-head { display: flex; align-items: center; gap: 14px; }
.dk-iconpick-words { display: grid; gap: 4px; min-inline-size: 0; }
.dk-iconpick-words h2 { overflow-wrap: anywhere; }
.dk-limit-card .dk-iconpick-fresh { color: var(--dk-good); font-weight: 600; }
.dk-iconpick-label {
  margin: 4px 0 0; font-size: var(--fs-micro); font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  color: var(--dk-ink-3);
}
.dk-iconpick-colours { display: flex; flex-wrap: wrap; gap: 8px; }
.dk-swatch {
  inline-size: 30px; block-size: 30px; border-radius: 50%; border: 3px solid var(--dk-surface); padding: 0;
  background: var(--dk-app); box-shadow: 0 0 0 1px var(--dk-line); cursor: pointer;
}
.dk-swatch[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--dk-ink); }
.dk-iconpick-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(42px, 1fr)); gap: 6px; color: var(--dk-app);
}
[data-theme="dark"] .dk-iconpick-grid { color: color-mix(in srgb, var(--dk-app) 45%, #FFFFFF); }
.dk-iconpick-cell {
  aspect-ratio: 1; display: grid; place-items: center; padding: 0; border-radius: 9px;
  border: 1px solid var(--dk-line-soft); background: var(--dk-surface); color: inherit; cursor: pointer;
}
.dk-iconpick-cell:hover { background: color-mix(in srgb, var(--dk-app) 10%, var(--dk-surface)); }
.dk-iconpick-cell[aria-pressed="true"] {
  border-color: currentColor; box-shadow: inset 0 0 0 1px currentColor;
  background: color-mix(in srgb, var(--dk-app) 16%, var(--dk-surface));
}
.dk-iconpick-cell .dk-sym { inline-size: 22px; block-size: 22px; }
.dk-limit-card .dk-iconpick-note { color: var(--dk-crit); }
.dk-iconpick-foot { flex-wrap: wrap; }

/* ---- Tether, to Syed's "Tether Redesign" canvas (19 September) -------------------------------------------------------
   Tether keeps its own identity, as the bigger apps do (Syed, 19 September): the canvas's cool blue-grey ground, its
   indigo, Figtree for the words and JetBrains Mono for codes and times, in its light and its dark. Every colour is the
   canvas's, as tokens on .tt-mount, so the dark theme is one block. Both faces are served from this site, with their
   OFL licences in wwwroot/fonts; the product loads nothing from another. */
@font-face {
  font-family: 'Figtree'; font-style: normal; font-weight: 400 800; font-display: swap;
  src: url('../fonts/figtree-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Figtree'; font-style: normal; font-weight: 400 800; font-display: swap;
  src: url('../fonts/figtree-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500 700; font-display: swap;
  src: url('../fonts/jetbrains-mono-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500 700; font-display: swap;
  src: url('../fonts/jetbrains-mono-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

.tt-mount {
  --tt-page: #F2F4FA; --tt-header: #FFFFFF; --tt-card: #FFFFFF; --tt-border: #DFE3EE; --tt-divider: #E9ECF4;
  --tt-text: #161B33; --tt-muted: #545B73; --tt-subtle: #F5F6FB; --tt-primary: #3651C9; --tt-primary-strong: #26358F;
  --tt-soft: #EAEFFD; --tt-soft-text: #2B3C9E; --tt-code-bg: #F3F5FD; --tt-code-border: #D9DFF4; --tt-code: #1B2352;
  --tt-input: #FFFFFF; --tt-input-border: #C8CEE0; --tt-danger-line: #B42318; --tt-danger-soft: #FDECEA;
  --tt-danger-text: #8C1D13; --tt-danger: #C23A30; --tt-amber-soft: #FDF1DC; --tt-amber-text: #7A4A00; --tt-amber: #C98A12;
  --tt-ok-dot: #1F9D6B; --tt-ok-soft: #E4F4EA; --tt-ok-text: #17663F; --tt-seg-sel: #FFFFFF; --tt-seg-sel-border: #A9B8EE;
  --tt-sec-bg: #FFFFFF; --tt-sec-border: #D9DFF4; --tt-sec-text: #2B3C9E; --tt-row-icon: #F3F5FD; --tt-alt: #EFE9FC;
  --tt-alt-text: #5B3FB8; --tt-focus: #6C84E6; --tt-shadow: 0 18px 44px rgba(22, 27, 51, .22);
  --tt-font: 'Figtree', "Segoe UI", system-ui, sans-serif;
  --tt-mono: 'JetBrains Mono', Consolas, ui-monospace, monospace;

  container-type: inline-size; min-block-size: 100dvh;
  background: var(--tt-page); color: var(--tt-text); font-family: var(--tt-font);
}
[data-theme="dark"] .tt-mount {
  --tt-page: #0E1018; --tt-header: #141724; --tt-card: #181C2B; --tt-border: #2A3044; --tt-divider: #232839;
  --tt-text: #E7E9F3; --tt-muted: #A0A6BE; --tt-subtle: #12151F; --tt-primary: #4058C8; --tt-primary-strong: #3348B0;
  --tt-soft: #1C2548; --tt-soft-text: #A9B8F8; --tt-code-bg: #161D3A; --tt-code-border: #2A3565; --tt-code: #DCE3FF;
  --tt-input: #10131C; --tt-input-border: #3A4260; --tt-danger-line: #F08A80; --tt-danger-soft: #3A1D1B;
  --tt-danger-text: #F6B8B1; --tt-amber-soft: #3A2C12; --tt-amber-text: #F2C879; --tt-ok-dot: #3CCB8F;
  --tt-ok-soft: #16301F; --tt-ok-text: #8FDDB0; --tt-seg-sel: #243060; --tt-seg-sel-border: #3C4E96; --tt-sec-bg: #1E2334;
  --tt-sec-border: #2F3752; --tt-sec-text: #C5CFFA; --tt-row-icon: #1E2334; --tt-alt: #2A2244; --tt-alt-text: #C9B8F5;
  --tt-shadow: 0 18px 44px rgba(0, 0, 0, .55);
}

/* The window's height, exactly: the session's picture fits it instead of pushing the toolbar off the top. */
.tt-app { position: relative; display: flex; flex-direction: column; block-size: 100dvh; min-block-size: 0; }
.tt-app :where(button, input) { font-family: inherit; }
.tt-app :where(button, a, input):focus-visible { outline: 3px solid var(--tt-focus); outline-offset: 2px; }
.tt-grow { flex: 1; min-inline-size: 0; }
.tt-svg { inline-size: 18px; block-size: 18px; flex-shrink: 0; }
.tt-spin { animation: tt-spin .8s linear infinite; }
@keyframes tt-spin { to { transform: rotate(360deg); } }
.tt-sr { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.tt-mono { font-family: var(--tt-mono); font-variant-numeric: tabular-nums; }
.tt-strong { font-weight: 700; }
.tt-fine { margin: 0; font-size: 13px; color: var(--tt-muted); }

/* ---- the header ---- */
.tt-top {
  block-size: 64px; flex-shrink: 0; box-sizing: border-box; display: flex; align-items: center; gap: 12px; padding: 0 28px;
  background: var(--tt-header); border-block-end: 1px solid var(--tt-border);
}
.tt-mark {
  display: grid; place-items: center; inline-size: 34px; block-size: 34px; border-radius: 9px;
  background: var(--tt-primary); color: #FFFFFF;
}
.tt-title { margin: 0; font-size: 18px; font-weight: 800; letter-spacing: -.01em; }
.tt-pill {
  display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 999px;
  font-size: 13px; font-weight: 600; background: var(--tt-subtle); color: var(--tt-muted); white-space: nowrap;
}
.tt-pill .tt-svg { inline-size: 12px; block-size: 12px; }
/* On a narrow window the status gives way, not the mark or the buttons. */
.tt-mark, .tt-title, .tt-head-btn { flex-shrink: 0; }
.tt-pill { min-inline-size: 0; }
.tt-pill-text { overflow: hidden; text-overflow: ellipsis; }
.tt-pill-dot { inline-size: 7px; block-size: 7px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
.tt-pill-good { background: var(--tt-ok-soft); color: var(--tt-ok-text); }
.tt-pill-good .tt-pill-dot { background: var(--tt-ok-dot); }
.tt-pill-busy { background: var(--tt-amber-soft); color: var(--tt-amber-text); }
.tt-pill-bad { background: var(--tt-danger-soft); color: var(--tt-danger-text); }
.tt-head-btn {
  block-size: 38px; display: inline-flex; align-items: center; gap: 8px; padding: 0 12px; border-radius: 9px;
  border: 1px solid var(--tt-border); background: var(--tt-card); color: var(--tt-text); font-size: 14px; font-weight: 500; cursor: pointer;
}
.tt-head-icon { inline-size: 38px; padding: 0; justify-content: center; }
.tt-kbd {
  padding: 1px 6px; border-radius: 5px; border: 1px solid var(--tt-border); background: var(--tt-subtle);
  color: var(--tt-muted); font-family: var(--tt-mono); font-size: 12px; white-space: nowrap;
}

/* ---- the banners: sharing ready, in a session, replaced ---- */
.tt-banner {
  flex-shrink: 0; display: flex; align-items: center; flex-wrap: wrap; gap: 10px; padding: 10px 28px;
  background: var(--tt-primary-strong); color: #FFFFFF; font-size: 14px;
}
.tt-banner b { font-weight: 700; }
.tt-banner-words { display: flex; flex-direction: column; gap: 1px; }
.tt-banner-words > span { font-size: 12px; color: #D7DDF7; }
.tt-banner.is-paused { background: #5A4210; }
.tt-banner-dot { inline-size: 8px; block-size: 8px; border-radius: 50%; background: #7CE0A8; flex-shrink: 0; }
.tt-live { background: #F04438; animation: tt-pulse 1.4s ease-in-out infinite; }
@keyframes tt-pulse { 50% { opacity: .35; } }
.tt-banner-quiet { background: var(--tt-subtle); color: var(--tt-text); border-block-end: 1px solid var(--tt-border); }
.tt-banner .tt-btn-quiet { color: #FFFFFF; }

/* ---- buttons ---- */
.tt-btn {
  block-size: 42px; display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 0 14px;
  border-radius: 9px; border: 1px solid var(--tt-border); background: var(--tt-card); color: var(--tt-text);
  font-size: 14px; font-weight: 600; cursor: pointer; text-decoration: none; white-space: nowrap; box-sizing: border-box;
}
.tt-btn:disabled { opacity: .5; cursor: not-allowed; }
.tt-btn .tt-svg { inline-size: 16px; block-size: 16px; }
.tt-btn-soft { background: var(--tt-sec-bg); border-color: var(--tt-sec-border); color: var(--tt-sec-text); }
.tt-btn-primary { background: var(--tt-primary); border-color: var(--tt-primary); color: #FFFFFF; }
.tt-btn-strong { background: var(--tt-primary-strong); border-color: var(--tt-primary-strong); color: #FFFFFF; }
.tt-btn-danger { background: var(--tt-danger); border-color: var(--tt-danger); color: #FFFFFF; }
.tt-btn-outline-danger { background: transparent; border-color: var(--tt-danger-line); color: var(--tt-danger-line); }
.tt-btn-quiet { background: transparent; border-color: transparent; }
.tt-btn-quiet:hover:not(:disabled) { background: color-mix(in srgb, currentColor 8%, transparent); }
.tt-btn-on-dark { block-size: 34px; background: transparent; border-color: rgba(255, 255, 255, .7); color: #FFFFFF; }
.tt-btn-big { block-size: 50px; border-radius: 10px; font-size: 16px; font-weight: 700; }
.tt-btn-wide { inline-size: 100%; }
.tt-btn-small { block-size: 34px; padding: 0 14px; border-radius: 8px; }
.tt-icon-btn {
  inline-size: 34px; block-size: 34px; display: inline-grid; place-items: center; flex-shrink: 0; padding: 0;
  border: 1px solid transparent; border-radius: 8px; background: transparent; color: var(--tt-muted); cursor: pointer;
}
.tt-icon-btn:disabled { opacity: .5; cursor: not-allowed; }
.tt-icon-btn-accent { background: var(--tt-primary); color: #FFFFFF; }
.tt-link { padding: 6px 8px; border: 0; background: transparent; color: var(--tt-soft-text); font-size: 14px; font-weight: 700; cursor: pointer; }
.tt-badge {
  min-inline-size: 18px; block-size: 18px; padding: 0 5px; border-radius: 999px; display: inline-grid; place-items: center;
  background: #E11D48; color: #FFFFFF; font-size: 11px; font-weight: 700; box-sizing: border-box;
}

/* ---- the two cards ---- */
.tt-main { flex: 1; min-block-size: 0; overflow: auto; padding: 24px 28px; display: flex; flex-direction: column; gap: 20px; }
.tt-tabs { display: none; }
.tt-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; align-items: stretch; }
.tt-card {
  background: var(--tt-card); border: 1px solid var(--tt-border); border-radius: 14px; padding: 26px;
  display: flex; flex-direction: column; gap: 18px; min-inline-size: 0;
}
.tt-card-intro { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
.tt-tag {
  padding: 3px 9px; border-radius: 6px; background: var(--tt-soft); color: var(--tt-soft-text);
  font-size: 12px; font-weight: 700; letter-spacing: .08em;
}
.tt-tag-alt { background: var(--tt-alt); color: var(--tt-alt-text); }
.tt-card-title { margin: 0; font-size: 22px; font-weight: 800; letter-spacing: -.01em; }
.tt-card-title-small { font-size: 16px; }
.tt-sub { margin: 0; font-size: 14px; color: var(--tt-muted); }

.tt-code-box {
  border-radius: 12px; background: var(--tt-code-bg); border: 1px solid var(--tt-code-border); padding: 20px;
  display: flex; flex-direction: column; align-items: center; gap: 16px;
}
.tt-code {
  font-family: var(--tt-mono); font-variant-numeric: tabular-nums; font-size: 42px; font-weight: 700;
  color: var(--tt-code); word-spacing: .35em; line-height: 1.1; text-align: center;
}
.tt-code-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; inline-size: 100%; }
.tt-code-box + .tt-fine { margin-block-start: -6px; }

.tt-fieldset { margin: 0; padding: 0; border: 0; display: flex; flex-direction: column; gap: 8px; min-inline-size: 0; }
.tt-label { padding: 0; margin: 0; font-size: 14px; font-weight: 700; }
.tt-fieldset .tt-label { margin-block-end: 8px; }
.tt-seg {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px; padding: 4px;
  background: var(--tt-subtle); border: 1px solid var(--tt-border); border-radius: 10px;
}
.tt-seg-btn {
  min-block-size: 38px; padding: 4px 6px; border-radius: 7px; border: 1px solid transparent; background: transparent;
  color: var(--tt-muted); font-size: 14px; font-weight: 500; cursor: pointer;
}
.tt-seg-btn[aria-pressed="true"] { background: var(--tt-seg-sel); border-color: var(--tt-seg-sel-border); color: var(--tt-soft-text); font-weight: 700; }

.tt-seen-as { display: flex; align-items: center; gap: 10px; padding-block-start: 14px; border-block-start: 1px solid var(--tt-divider); }
.tt-seen-label { font-size: 14px; color: var(--tt-muted); white-space: nowrap; }
.tt-name {
  flex: 1; min-inline-size: 0; block-size: 38px; box-sizing: border-box; padding: 0 12px; border-radius: 8px;
  border: 1px solid var(--tt-border); background: var(--tt-subtle); color: var(--tt-text); font-size: 14px; font-weight: 600;
}
.tt-name-shown { display: flex; align-items: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tt-input {
  block-size: 38px; box-sizing: border-box; padding: 0 12px; border-radius: 8px; border: 1px solid var(--tt-input-border);
  background: var(--tt-input); color: var(--tt-text); font-size: 14px;
}
input.tt-name { border-color: var(--tt-primary); background: var(--tt-input); }

.tt-partner-field { display: flex; flex-direction: column; gap: 8px; }
.tt-partner {
  block-size: 58px; box-sizing: border-box; padding: 0 18px; border-radius: 10px; border: 1px solid var(--tt-input-border);
  background: var(--tt-input); color: var(--tt-text); font-family: var(--tt-mono); font-size: 24px; font-weight: 500;
  word-spacing: .2em; min-inline-size: 0;
}
.tt-partner::placeholder { color: #8A90A6; }
.tt-partner:disabled { opacity: .7; }
.tt-checks { display: flex; flex-direction: column; gap: 10px; }
.tt-check { display: flex; align-items: center; gap: 10px; font-size: 14px; cursor: pointer; }
.tt-check input, .tt-ask-row input { inline-size: 18px; block-size: 18px; margin: 0; accent-color: var(--tt-primary); flex-shrink: 0; }

.tt-progress { display: flex; flex-direction: column; gap: 14px; }
.tt-waiting-title { margin: 0; font-size: 16px; font-weight: 800; }
.tt-steps {
  margin: 0; padding: 14px 16px; list-style: none; display: flex; flex-direction: column; gap: 11px;
  border-radius: 10px; background: var(--tt-subtle); border: 1px solid var(--tt-divider);
}
.tt-step { display: flex; align-items: center; gap: 12px; font-size: 14px; color: var(--tt-muted); }
.tt-step-dot { inline-size: 22px; block-size: 22px; flex-shrink: 0; display: grid; place-items: center; border-radius: 50%; }
.tt-step-dot .tt-svg { inline-size: 13px; block-size: 13px; }
.tt-step-todo .tt-step-dot::before { content: ""; inline-size: 9px; block-size: 9px; border-radius: 50%; border: 2px solid var(--tt-input-border); box-sizing: border-box; }
.tt-step-now { color: var(--tt-text); font-weight: 700; }
.tt-step-now .tt-step-dot { color: var(--tt-soft-text); }
.tt-step-now .tt-step-dot .tt-svg { inline-size: 20px; block-size: 20px; }
.tt-step-done { color: var(--tt-text); }
.tt-step-done .tt-step-dot { background: var(--tt-primary); color: #FFFFFF; }
.tt-step-failed { color: var(--tt-danger-line); font-weight: 700; }
.tt-step-failed .tt-step-dot { background: var(--tt-danger); color: #FFFFFF; }
.tt-facts { margin: 0; display: grid; gap: 8px; font-size: 14px; }
.tt-facts > div { display: flex; justify-content: space-between; gap: 16px; }
.tt-facts dt { color: var(--tt-muted); }
.tt-facts dd { margin: 0; text-align: end; font-weight: 700; overflow-wrap: anywhere; }
.tt-row { display: flex; gap: 10px; }

.tt-secure {
  margin-block-start: auto; display: flex; align-items: flex-start; gap: 14px; padding: 16px; border-radius: 12px;
  background: var(--tt-soft); color: var(--tt-soft-text);
}
.tt-secure .tt-svg { inline-size: 22px; block-size: 22px; }
.tt-secure > span { display: flex; flex-direction: column; gap: 3px; font-size: 13px; }
.tt-secure b { font-size: 14px; }

.tt-note { margin: 0; padding: 12px 14px; border-radius: 10px; font-size: 14px; }
.tt-note-warn { background: var(--tt-danger-soft); color: var(--tt-danger-text); }
.tt-note-ok { background: var(--tt-ok-soft); color: var(--tt-ok-text); }

/* ---- recent sessions ---- */
.tt-history { padding: 18px 26px 10px; gap: 0; }
.tt-history-head { display: flex; align-items: center; justify-content: space-between; padding-block-end: 8px; }
.tt-rows { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; }
.tt-row-item {
  display: grid; grid-template-columns: 40px minmax(0, 2.2fr) minmax(0, 1.6fr) minmax(0, .8fr) 120px;
  align-items: center; gap: 14px; padding: 11px 0; border-block-start: 1px solid var(--tt-divider);
}
.tt-row-icon {
  inline-size: 38px; block-size: 38px; border-radius: 10px; display: grid; place-items: center;
  background: var(--tt-row-icon); color: var(--tt-soft-text);
}
.tt-row-who { display: flex; flex-direction: column; gap: 2px; min-inline-size: 0; }
.tt-row-who b { font-size: 15px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tt-row-who span, .tt-row-action, .tt-row-dur { font-size: 13px; color: var(--tt-muted); }
.tt-row-action, .tt-row-dur { font-size: 14px; }
.tt-row-go { display: flex; justify-content: flex-end; }

/* ---- the request, and the plain dialogs ---- */
.tt-scrim {
  position: fixed; inset: 0; z-index: 50; display: grid; place-items: center; padding: 16px;
  background: rgba(10, 14, 30, .45);
}
.tt-dialog {
  inline-size: min(440px, 100%); max-block-size: calc(100dvh - 32px); overflow: auto; box-sizing: border-box;
  background: var(--tt-card); color: var(--tt-text); border: 1px solid var(--tt-border); border-radius: 16px;
  padding: 24px 22px 22px; display: flex; flex-direction: column; gap: 16px; box-shadow: var(--tt-shadow);
}
.tt-dialog-plain { inline-size: min(520px, 100%); }
.tt-dialog-head { display: flex; align-items: center; gap: 14px; }
.tt-dialog-ico {
  inline-size: 52px; block-size: 52px; border-radius: 12px; display: grid; place-items: center; flex-shrink: 0;
  background: var(--tt-soft); color: var(--tt-primary);
}
.tt-dialog-ico .tt-svg { inline-size: 24px; block-size: 24px; }
.tt-dialog-title { margin: 0; font-size: 20px; font-weight: 800; }
.tt-dialog-head .tt-sub { font-size: 13px; }
.tt-asks { margin: 0; padding: 0; border: 0; display: flex; flex-direction: column; gap: 8px; }
.tt-legend { padding: 0; margin-block-end: 8px; font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--tt-muted); }
.tt-ask-row {
  display: flex; align-items: center; gap: 10px; padding: 12px 14px; border-radius: 10px;
  border: 1px solid var(--tt-border); font-size: 14px;
}
.tt-ask-row.is-choice { border-color: var(--tt-seg-sel-border); background: color-mix(in srgb, var(--tt-soft) 55%, var(--tt-card)); cursor: pointer; }
.tt-ask-row.is-off { opacity: .65; cursor: not-allowed; }
.tt-ask-row.is-quiet { border-style: dashed; color: var(--tt-muted); }
.tt-ask-row > .tt-grow { display: flex; flex-direction: column; gap: 2px; }
.tt-ask-state { font-size: 13px; font-weight: 600; color: var(--tt-soft-text); }
.tt-ask-row.is-quiet .tt-ask-state { color: var(--tt-muted); font-weight: 400; }
.tt-ask-note { font-size: 12px; color: var(--tt-muted); font-weight: 400; }
.tt-dialog-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.tt-countdown { display: flex; flex-direction: column; gap: 6px; text-align: center; }
.tt-bar { block-size: 4px; border-radius: 2px; background: var(--tt-divider); overflow: hidden; }
.tt-bar > span { display: block; block-size: 100%; background: var(--tt-amber); transition: inline-size .25s linear; }
.tt-keys { margin: 0; display: flex; flex-direction: column; gap: 10px; }
.tt-keys > div { display: grid; grid-template-columns: 120px minmax(0, 1fr); align-items: center; gap: 12px; }
.tt-keys dd { margin: 0; font-size: 14px; }
.tt-settings { display: flex; flex-direction: column; gap: 10px; }
.tt-trusted { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; }
.tt-trusted li { display: flex; align-items: center; gap: 10px; padding: 10px 0; border-block-start: 1px solid var(--tt-divider); font-size: 14px; }

/* ---- the session: the helper's picture, and the chat and files panel ---- */
.tt-session { flex: 1; min-block-size: 0; display: flex; flex-direction: column; }
.tt-toolbar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding: 10px 16px;
  background: var(--tt-header); border-block-end: 1px solid var(--tt-border);
}
.tt-toolbar-who { display: flex; flex-direction: column; gap: 1px; margin-inline-end: 8px; }
.tt-toolbar-who b { font-size: 15px; font-weight: 800; }
.tt-toolbar-who > span { font-size: 12px; color: var(--tt-soft-text); }
.tt-figures { display: flex; align-items: center; gap: 12px; font-size: 12px; color: var(--tt-muted); }
.tt-figures b { color: var(--tt-text); font-family: var(--tt-mono); }
.tt-route { padding: 2px 8px; border-radius: 999px; background: var(--tt-ok-soft); color: var(--tt-ok-text); font-weight: 600; }
.tt-route-relay { background: var(--tt-amber-soft); color: var(--tt-amber-text); }
.tt-switch { display: inline-flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 600; cursor: pointer; }
.tt-switch input {
  appearance: none; inline-size: 40px; block-size: 24px; margin: 0; padding: 0; border-radius: 999px;
  background: var(--tt-input-border); position: relative; cursor: pointer; transition: background .15s;
}
.tt-switch input::after {
  content: ""; position: absolute; inset-block-start: 3px; inset-inline-start: 3px; inline-size: 18px; block-size: 18px;
  border-radius: 50%; background: #FFFFFF; transition: transform .15s;
}
.tt-switch input:checked { background: var(--tt-primary); }
.tt-switch input:checked::after { transform: translateX(16px); }
.tt-switch input:disabled { opacity: .5; cursor: not-allowed; }
.tt-steer-note {
  margin: 0; padding: 6px 16px; font-size: 13px; color: var(--tt-muted);
  background: var(--tt-card); border-block-end: 1px solid var(--tt-border);
}
.tt-session-body { flex: 1; min-block-size: 0; display: flex; }
.tt-stage { position: relative; flex: 1; min-inline-size: 0; background: #05070D; outline: none; }
.tt-stage-steering { cursor: default; }
.tt-stage-steering:focus-visible { outline: 3px solid #E0457B; outline-offset: -3px; }
.tt-video { inline-size: 100%; block-size: 100%; object-fit: contain; display: block; }
.tt-video.is-hidden { visibility: hidden; }
.tt-stage-note {
  position: absolute; inset: 0; margin: 0; display: flex; align-items: center; justify-content: center; gap: 10px;
  color: #DCE3FF; font-size: 15px; font-weight: 600; text-align: center; padding: 20px;
}

.tt-panel { display: none; inline-size: 320px; flex-shrink: 0; flex-direction: column; background: var(--tt-card); border-inline-start: 1px solid var(--tt-border); min-block-size: 0; }
.tt-panel.is-open { display: flex; }
/* The sharer's panel sits beside the cards, under the banner, so the banner's buttons are never covered. */
.tt-below { flex: 1; min-block-size: 0; display: flex; position: relative; }
.tt-below > .tt-main { min-inline-size: 0; }
.tt-panel-head { display: flex; align-items: center; gap: 8px; padding: 10px 12px 10px 16px; border-block-end: 1px solid var(--tt-divider); font-size: 15px; }
.tt-chat { flex: 1; min-block-size: 0; overflow: auto; padding: 12px 14px; display: flex; flex-direction: column; gap: 8px; }
.tt-chat-line {
  margin: 0; max-inline-size: 85%; align-self: flex-start; display: flex; flex-direction: column; gap: 2px;
  padding: 8px 10px; border-radius: 10px; background: var(--tt-subtle); font-size: 14px;
}
.tt-chat-line.is-mine { align-self: flex-end; background: var(--tt-soft); color: var(--tt-soft-text); }
.tt-chat-text { white-space: pre-wrap; overflow-wrap: anywhere; }
.tt-chat-line time { font-size: 11px; color: var(--tt-muted); }
.tt-files { margin: 0; padding: 8px 14px; list-style: none; display: flex; flex-direction: column; gap: 8px; border-block-start: 1px solid var(--tt-divider); max-block-size: 40%; overflow: auto; }
.tt-file { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.tt-file-name { flex: 1; min-inline-size: 0; display: flex; flex-direction: column; gap: 2px; }
.tt-file-name b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tt-file-name > span { color: var(--tt-muted); }
.tt-file-failed .tt-file-name > span, .tt-file-declined .tt-file-name > span { color: var(--tt-danger-line); }
.tt-file-bar { display: block; block-size: 3px; border-radius: 2px; background: var(--tt-divider); overflow: hidden; }
.tt-file-bar > span { display: block; block-size: 100%; background: var(--tt-primary); }
.tt-chat-form { display: flex; gap: 8px; padding: 10px 12px; border-block-start: 1px solid var(--tt-divider); }
.tt-chat-form .tt-input { flex: 1; min-inline-size: 0; }

/* ---- a narrow window: the canvas's compact board, with tabs ---- */
@container (max-width: 760px) {
  .tt-top { padding: 0 16px; gap: 8px; }
  .tt-main { padding: 14px 16px; gap: 14px; }
  .tt-banner { padding: 10px 16px; }
  .tt-tabs {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px; padding: 4px;
    background: var(--tt-subtle); border: 1px solid var(--tt-border); border-radius: 11px;
  }
  .tt-tab {
    block-size: 38px; border-radius: 8px; border: 1px solid transparent; background: transparent;
    color: var(--tt-muted); font-size: 15px; font-weight: 500; cursor: pointer;
  }
  .tt-tab[aria-selected="true"] { border-color: var(--tt-seg-sel-border); background: var(--tt-soft); color: var(--tt-soft-text); font-weight: 700; }
  .tt-cards { grid-template-columns: minmax(0, 1fr); }
  .tt-cards > .tt-card:not(.is-shown) { display: none; }
  .tt-card { padding: 18px; gap: 16px; }
  .tt-card-title { font-size: 18px; }
  .tt-code { font-size: 34px; }
  .tt-panel { position: absolute; inset-block: 0; inset-inline-end: 0; z-index: 5; box-shadow: var(--tt-shadow); max-inline-size: 100%; }
  .tt-session-body { position: relative; }
}
@container (max-width: 560px) {
  .tt-hide-narrow { display: none; }
  .tt-seg { grid-template-columns: minmax(0, 1fr); }
  .tt-row-item { grid-template-columns: 40px minmax(0, 1fr) auto; }
  .tt-row-action, .tt-row-dur { display: none; }
  .tt-figures { display: none; }
}

/* ---- on the owner's NETFIVE desktop itself, outside Tether's window ----
   The sharing bar (the canvas's SessionBar board), below the desktop's own bar, and the helper's pointer with their name.
   Neither can be pressed through: the cursor takes no pointer events, and the bar's buttons answer only the owner's own
   clicks. */
.tt-session-bar {
  position: fixed; inset-block-start: 64px; inset-inline-start: 50%; transform: translateX(-50%); z-index: 2147483645;
  display: flex; align-items: center; gap: 14px; max-inline-size: calc(100vw - 24px); box-sizing: border-box;
  padding: 10px 10px 10px 18px; border-radius: 14px; background: #1A2150; color: #FFFFFF;
  font-family: 'Figtree', "Segoe UI", system-ui, sans-serif; box-shadow: 0 10px 28px rgba(13, 20, 42, .35);
}
.tt-session-dot { inline-size: 10px; block-size: 10px; flex-shrink: 0; border-radius: 50%; background: #F04438; animation: tt-pulse 1.4s ease-in-out infinite; }
.tt-session-bar.is-paused .tt-session-dot { background: #C98A12; animation: none; }
.tt-session-words { display: flex; flex-direction: column; gap: 1px; min-inline-size: 0; }
.tt-session-words b { font-size: 14px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tt-session-words span { font-size: 12px; color: #B7C0E6; white-space: nowrap; }
.tt-session-rule { inline-size: 1px; block-size: 30px; background: #2E3874; flex-shrink: 0; }
.tt-session-btn {
  block-size: 38px; flex-shrink: 0; padding: 0 12px; border-radius: 8px; border: 1px solid #2E3874; background: #232B66;
  color: #FFFFFF; font: 600 14px 'Figtree', "Segoe UI", system-ui, sans-serif; cursor: pointer;
}
.tt-session-btn[hidden] { display: none; }
.tt-session-stop { border-color: #C23A30; background: #C23A30; font-weight: 700; }
.tt-session-btn:focus-visible { outline: 3px solid #6C84E6; outline-offset: 2px; }
.tt-session-keys { font: 500 12px 'JetBrains Mono', Consolas, monospace; color: #B7C0E6; padding-inline-end: 6px; white-space: nowrap; }
@media (max-width: 720px) { .tt-session-keys, .tt-session-rule { display: none; } }

.tt-remote-cursor {
  position: fixed; inset-block-start: 0; inset-inline-start: 0; z-index: 2147483646; pointer-events: none;
  display: flex; align-items: flex-start; gap: 2px; will-change: transform; transition: transform 40ms linear;
}
.tt-remote-cursor svg { inline-size: 18px; block-size: 22px; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .35)); }
.tt-remote-cursor path { fill: #E0457B; stroke: #fff; stroke-width: 1.4; }
.tt-remote-cursor span {
  margin-block-start: 14px; padding: 2px 8px; border-radius: 999px; background: #E0457B; color: #fff;
  font: 600 12px/1.4 'Figtree', system-ui, sans-serif; white-space: nowrap; box-shadow: 0 1px 3px rgba(0, 0, 0, .25);
}
@media (prefers-reduced-motion: reduce) {
  .tt-session-dot, .tt-live, .tt-spin { animation: none; }
  .tt-remote-cursor { transition: none; }
}

/* ---- the Browser (Syed, 20 September 2026: "create an Agentic and regular Browser") ----------------------------------
   One of the desk's basic apps, so it takes the shared stone palette and type, as Calendar and the desk do. Its own
   page rather than the desk's, because it is the one page allowed to frame a site nobody curated; the desk shows it by
   framing /browser. Nothing here is a real browser's chrome pretending to be the operating system's: the tabs and the
   bar are the app's, in the product's own type, so nobody mistakes what they are looking at. */
.bw-mount {
  --surface: #FFFFFF;
  --surface-2: #FAFAF9;
  --surface-3: #F0EFED;
  --surface-4: #E7E5E4;
  --border: #E7E5E4;
  --border-strong: #D6D3D1;
  --text: #1C1917;
  --muted: #78716C;
  --font: 'Instrument Sans', ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, "Cascadia Mono", Consolas, monospace;

  min-block-size: 100dvh; background: var(--surface-2); color: var(--text); font-family: var(--font);
}
[data-theme="dark"] .bw-mount {
  --surface: #1C1917;
  --surface-2: #231F1D;
  --surface-3: #2A2624;
  --surface-4: #332F2C;
  --border: #332F2C;
  --border-strong: #44403C;
  --text: #FAFAF9;
  --muted: #A8A29E;
}

.bw { display: flex; flex-direction: column; block-size: 100dvh; min-block-size: 0; }
.bw :where(button, input) { font-family: inherit; }
.bw :where(button, a, input):focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- the tab strip ---- */
.bw-tabs {
  flex-shrink: 0; display: flex; align-items: flex-end; gap: 4px; padding: 6px 10px 0;
  background: var(--surface-3); border-block-end: 1px solid var(--border); overflow-x: auto; scrollbar-width: thin;
}
.bw-tab {
  display: flex; align-items: center; gap: 6px; max-inline-size: 220px; min-inline-size: 110px; padding: 0 6px 0 12px;
  block-size: 34px; border-radius: 9px 9px 0 0; background: transparent; color: var(--muted); cursor: pointer;
  border: 1px solid transparent; border-block-end: none; font-size: 13px; font-weight: 600; user-select: none;
}
.bw-tab.is-on { background: var(--surface); color: var(--text); border-color: var(--border); }
.bw-tab-name { flex: 1; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bw-tab-x {
  flex-shrink: 0; inline-size: 20px; block-size: 20px; display: grid; place-items: center; border: none;
  border-radius: 6px; background: transparent; color: inherit; font-size: 15px; line-height: 1; cursor: pointer;
}
.bw-tab-x:hover { background: var(--surface-4); }
.bw-tab-add {
  flex-shrink: 0; inline-size: 30px; block-size: 30px; margin-block-end: 2px; display: grid; place-items: center;
  border: none; border-radius: 8px; background: transparent; color: var(--muted); font-size: 19px; cursor: pointer;
}
.bw-tab-add:hover { background: var(--surface-4); color: var(--text); }

/* ---- the address bar ---- */
.bw-bar {
  flex-shrink: 0; display: flex; align-items: center; gap: 6px; padding: 8px 12px;
  background: var(--surface); border-block-end: 1px solid var(--border);
}
.bw-nav {
  inline-size: 32px; block-size: 32px; flex-shrink: 0; display: grid; place-items: center; border-radius: 8px;
  border: 1px solid transparent; background: transparent; color: var(--text); font-size: 17px; cursor: pointer;
}
.bw-nav:hover:not(:disabled) { background: var(--surface-3); }
.bw-nav:disabled { color: var(--muted); opacity: .45; cursor: not-allowed; }
.bw-address {
  flex: 1; min-inline-size: 0; block-size: 36px; padding: 0 14px; border-radius: 18px;
  border: 1px solid var(--border-strong); background: var(--surface-2); color: var(--text); font-size: 14px;
}
.bw-go {
  flex-shrink: 0; block-size: 36px; padding: 0 16px; border-radius: 18px; border: 1px solid transparent;
  background: var(--accent-fill); color: #FFFFFF; font-size: 14px; font-weight: 600; cursor: pointer;
}
.bw-go:hover { background: var(--accent-fill-hover); }

/* ---- the page ---- */
.bw-page { flex: 1; min-block-size: 0; display: flex; flex-direction: column; background: var(--surface); }
.bw-frame { flex: 1; min-block-size: 0; inline-size: 100%; border: none; background: #FFFFFF; }
.bw-note {
  margin: 0; padding: 6px 14px; font-size: 12px; color: var(--muted);
  background: var(--surface-2); border-block-end: 1px solid var(--border);
}

.bw-waiting {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: 10px; color: var(--muted); font-size: 14px;
}
.bw-spinner {
  inline-size: 16px; block-size: 16px; border-radius: 50%; border: 2px solid var(--border-strong);
  border-block-start-color: var(--accent); animation: bw-spin .8s linear infinite;
}
@keyframes bw-spin { to { transform: rotate(360deg); } }

/* ---- the start page, and the two cards that stand in for a page ---- */
/* 40px rather than the 56px this had: the Browser opens at 720px wide on the desk, and a start page that spends a
   sixth of a small window on its own top margin looks like a page that failed to load. */
.bw-start { flex: 1; overflow-y: auto; padding: 40px 28px; max-inline-size: 760px; margin-inline: auto; inline-size: 100%; box-sizing: border-box; }
.bw-start-head { margin: 0 0 8px; font-size: 26px; font-weight: 700; letter-spacing: -.01em; }
.bw-start-hint { margin: 0 0 32px; font-size: 14px; color: var(--muted); max-inline-size: 56ch; }
.bw-start-sub { margin: 0 0 4px; font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.bw-start-fine { margin: 0 0 14px; font-size: 13px; color: var(--muted); }
.bw-picks { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; }
/* The name over the host, not beside it. Side by side, "Excalidraw" became "Excalidr…" the moment a card was
   narrow enough, which is every card in a window this size. */
.bw-pick {
  display: grid; grid-template-columns: 10px 1fr; grid-template-rows: auto auto; gap: 1px 10px;
  align-items: center; padding: 10px 14px; border-radius: 12px;
  border: 1px solid var(--border); background: var(--surface); color: var(--text); cursor: pointer; text-align: start;
}
.bw-pick:hover { border-color: var(--border-strong); background: var(--surface-2); }
.bw-pick-dot { grid-row: 1 / 3; inline-size: 10px; block-size: 10px; border-radius: 3px; background: var(--pick, var(--muted)); }
.bw-pick-name { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; font-weight: 600; }
.bw-pick-host { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: var(--muted); }

.bw-card {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  padding: 40px 32px; text-align: center;
}
.bw-card-head { margin: 0; font-size: 19px; font-weight: 700; }
.bw-card-why { margin: 0; font-size: 14px; color: var(--muted); max-inline-size: 52ch; }
.bw-card-url { margin: 0; font-family: var(--mono); font-size: 12px; color: var(--muted); word-break: break-all; max-inline-size: 60ch; }
.bw-card-do { display: flex; gap: 10px; margin-block-start: 8px; }
.bw-btn {
  block-size: 38px; display: inline-flex; align-items: center; padding: 0 16px; border-radius: 9px;
  border: 1px solid transparent; background: var(--accent-fill); color: #FFFFFF; font-size: 14px; font-weight: 600;
  cursor: pointer; text-decoration: none;
}
.bw-btn:hover { background: var(--accent-fill-hover); }
.bw-btn-quiet { background: transparent; border-color: var(--border-strong); color: var(--text); }
.bw-btn-quiet:hover { background: var(--surface-3); }

@media (prefers-reduced-motion: reduce) {
  .bw-spinner { animation: none; }
}

/* ---- Quire PDF (Syed, 20 September 2026: quire-pdf/DEVELOPER-NOTES.md and its canvas) --------------------------------
   One of the BIG apps, so it keeps its own identity rather than taking the desk's stone tokens - the same rule as
   Student, Meetings, Chat and the Quire editors. Every value here is from his tokens table, as tokens on .qp-mount, so
   the dark theme is one block. Plus Jakarta Sans is already served from this site for the desk; nothing is fetched. */
.qp-page { margin: 0; }

.qp-mount {
  --qp-red: #C8372D;
  --qp-tint: #FDECEA;
  --qp-tint-ink: #9F2A21;
  --qp-fold: #F3C7C2;
  --qp-ink: #1B1A21;
  --qp-muted: #5F5D68;
  --qp-line: #E4E2DD;
  --qp-paper: #FFFFFF;
  --qp-canvas: #ECEBE7;
  --qp-app: #FBFAF8;
  --qp-indigo: #4338CA;
  --qp-soft: #F1F0EC;
  --qp-font: 'Plus Jakarta Sans', 'Instrument Sans', ui-sans-serif, system-ui, "Segoe UI", sans-serif;

  display: block; block-size: 100dvh; background: var(--qp-app); color: var(--qp-ink); font-family: var(--qp-font);
}
/* His palette is a light one. Dark keeps his red and his indigo - a brand does not change with the lights - and
   moves only the surfaces, so the page itself stays white paper on a darker desk, as every PDF reader does. */
[data-theme="dark"] .qp-mount {
  --qp-ink: #F4F3F1;
  --qp-muted: #A8A29E;
  --qp-line: #332F2C;
  --qp-paper: #1F1D1C;
  --qp-canvas: #141312;
  --qp-app: #1A1917;
  --qp-soft: #2A2624;
  --qp-tint: #3A1C18;
  --qp-tint-ink: #F0A79E;
}

.qp { display: flex; flex-direction: column; block-size: 100dvh; min-block-size: 0; }
.qp :where(button, input) { font-family: inherit; }
.qp :where(button, a, input):focus-visible { outline: 2px solid var(--qp-indigo); outline-offset: 2px; }
.qp-grow { flex: 1; min-inline-size: 0; }
.qp-divider { inline-size: 1px; block-size: 24px; background: var(--qp-line); flex-shrink: 0; }
.qp-vsep { inline-size: 1px; block-size: 28px; background: var(--qp-line); margin: 0 8px; flex-shrink: 0; }

/* ---- top bar and menu bar ---- */
.qp-head { flex-shrink: 0; position: relative; z-index: 10; background: var(--qp-paper); border-block-end: 1px solid var(--qp-line); }
.qp-top { block-size: 52px; display: flex; align-items: center; gap: 16px; padding: 0 20px; }
.qp-brand { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.qp-word { font-size: 17px; font-weight: 800; letter-spacing: -.2px; white-space: nowrap; }
.qp-word span { font-weight: 500; color: var(--qp-muted); }
.qp-file { display: flex; align-items: center; gap: 10px; min-inline-size: 0; }
.qp-name { font-size: 14px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-inline-size: 280px; }
.qp-state { font-size: 12px; color: var(--qp-muted); white-space: nowrap; }

/* The name, editable in place. Syed, 25 September 2026: "NO RENAME OPTION".
   Drawn as text rather than as a box - no border, no background - so the bar looks exactly as his canvas
   has it until somebody actually goes to use it. The hover and focus states are what say it can be. */
.qp-namefield {
  inline-size: 280px; max-inline-size: 280px; padding: 2px 6px; margin-inline-start: -6px;
  border: 1px solid transparent; border-radius: 6px; background: transparent;
  color: inherit; font: inherit; font-size: 14px; font-weight: 600;
}
.qp-namefield:hover { border-color: var(--qp-line); }
.qp-namefield:focus {
  border-color: var(--qp-line); background: var(--qp-paper);
  outline: 2px solid var(--qp-indigo); outline-offset: 1px;
}
@media (max-width: 900px) { .qp-namefield { inline-size: 140px; max-inline-size: 140px; } }

.qp-find {
  display: flex; align-items: center; gap: 8px; block-size: 36px; inline-size: 220px; padding: 0 12px; flex-shrink: 0;
  border: 1px solid var(--qp-line); border-radius: 10px; background: var(--qp-soft); color: var(--qp-muted); font-size: 13px;
}
.qp-find input { border: none; background: transparent; outline: none; font: inherit; color: var(--qp-ink); inline-size: 100%; min-inline-size: 0; }
.qp-hits { font-size: 11px; font-weight: 700; color: var(--qp-tint-ink); }

.qp-quick { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }
.qp-sq {
  inline-size: 36px; block-size: 36px; display: grid; place-items: center; flex-shrink: 0;
  border: none; border-radius: 10px; background: transparent; color: var(--qp-ink); font-size: 17px; cursor: pointer;
}
.qp-sq:hover:not(:disabled) { background: var(--qp-soft); }
.qp-sq:disabled { color: var(--qp-muted); opacity: .45; cursor: not-allowed; }

.qp-btn {
  block-size: 36px; display: inline-flex; align-items: center; gap: 8px; padding: 0 14px; flex-shrink: 0;
  border: 1px solid var(--qp-line); border-radius: 10px; background: var(--qp-paper);
  font-size: 13px; font-weight: 600; color: var(--qp-ink); text-decoration: none; cursor: pointer;
}
.qp-btn:hover:not(:disabled) { background: var(--qp-soft); }
.qp-btn-red { border-color: transparent; background: var(--qp-red); color: #FFFFFF; font-weight: 700; }
.qp-btn-red:disabled { opacity: .5; cursor: not-allowed; }

.qp-menubar { block-size: 34px; display: flex; align-items: center; gap: 2px; padding: 0 12px; }
.qp-menuwrap { position: relative; }
.qp-menu {
  block-size: 26px; padding: 0 10px; border: none; border-radius: 7px; background: transparent;
  font-size: 13px; color: var(--qp-ink); cursor: pointer;
}
.qp-menu:hover, .qp-menu.is-on { background: var(--qp-soft); }
.qp-drop {
  position: absolute; inset-block-start: 32px; inset-inline-start: 0; inline-size: 264px; padding: 6px; z-index: 20;
  background: var(--qp-paper); border: 1px solid var(--qp-line); border-radius: 12px;
  box-shadow: 0 12px 32px rgba(27, 26, 33, .14); display: flex; flex-direction: column; gap: 1px;
}
.qp-sep { block-size: 1px; background: var(--qp-line); margin: 4px 6px; }
.qp-item {
  inline-size: 100%; block-size: 34px; padding: 0 10px; border: none; border-radius: 8px; background: transparent;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  font-size: 13px; color: var(--qp-ink); text-align: start; cursor: pointer;
}
.qp-item:hover:not(:disabled) { background: var(--qp-soft); }
.qp-item:disabled { color: var(--qp-muted); opacity: .5; cursor: not-allowed; }
.qp-key { font-size: 12px; color: var(--qp-muted); }

/* ---- toolbar ---- */
.qp-tools {
  block-size: 56px; flex-shrink: 0; display: flex; align-items: center; gap: 4px; padding: 0 16px;
  background: var(--qp-paper); border-block-end: 1px solid var(--qp-line); overflow-x: auto; scrollbar-width: thin;
}
.qp-toolwrap { display: flex; align-items: center; flex-shrink: 0; }
/* 44px tall, as his notes specify, and the minimum touch target the accessibility section asks for. */
.qp-tool {
  block-size: 44px; display: inline-flex; align-items: center; gap: 7px; padding: 0 11px; white-space: nowrap;
  border: none; border-radius: 9px; background: transparent; color: var(--qp-ink); font-size: 13px; font-weight: 600;
  cursor: pointer;
}
.qp-tool:hover:not(:disabled) { background: var(--qp-soft); }
.qp-tool.is-on { background: var(--qp-tint); color: var(--qp-tint-ink); }
.qp-tool:disabled { color: var(--qp-muted); opacity: .5; cursor: not-allowed; }
.qp-zoom { display: flex; align-items: center; gap: 4px; flex-shrink: 0; font-size: 13px; }
.qp-pct { min-inline-size: 52px; text-align: center; font-weight: 600; }
.qp-count { color: var(--qp-muted); white-space: nowrap; }

/* ---- body ---- */
.qp-body { flex: 1; display: flex; min-block-size: 0; }

.qp-rail {
  inline-size: 216px; flex-shrink: 0; display: flex; flex-direction: column; min-block-size: 0;
  background: var(--qp-paper); border-inline-end: 1px solid var(--qp-line);
}
.qp-railtabs { display: flex; gap: 4px; padding: 12px 12px 8px; flex-shrink: 0; }
.qp-railtab {
  flex: 1; block-size: 32px; border: none; border-radius: 8px; background: transparent;
  font-size: 12px; font-weight: 600; color: var(--qp-muted); cursor: pointer;
}
.qp-railtab.is-on { background: var(--qp-soft); color: var(--qp-ink); font-weight: 700; }
.qp-railbody { flex: 1; min-block-size: 0; overflow-y: auto; display: flex; flex-direction: column; align-items: center; gap: 14px; padding: 8px 0 16px; }
.qp-thumb { border: none; background: transparent; padding: 0; display: flex; flex-direction: column; align-items: center; gap: 6px; cursor: pointer; }
.qp-thumbimg { display: block; inline-size: 120px; block-size: auto; background: var(--qp-paper); border: 1px solid var(--qp-line); border-radius: 2px; }
/* The current page's thumbnail takes a 2px outline in the brand red, as the notes ask. */
.qp-thumb.is-on .qp-thumbimg { outline: 2px solid var(--qp-red); outline-offset: 1px; }
.qp-thumbnum { font-size: 11px; color: var(--qp-muted); }
.qp-thumb.is-on .qp-thumbnum { color: var(--qp-ink); font-weight: 700; }
.qp-outline { inline-size: 100%; padding: 6px 14px; font-size: 13px; color: var(--qp-ink); }
.qp-empty { margin: 0; padding: 24px 16px; font-size: 13px; color: var(--qp-muted); text-align: center; }

.qp-canvas {
  flex: 1; min-inline-size: 0; min-block-size: 0; overflow: auto;
  background: var(--qp-canvas); display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 16px 24px 24px;
}
.qp-sheet { position: relative; flex-shrink: 0; background: var(--qp-paper); box-shadow: 0 1px 4px rgba(27, 26, 33, .18); }
.qp-sheet canvas { display: block; }
.qp-sheet.is-hit { outline: 2px solid var(--qp-indigo); outline-offset: 2px; }

.qp-panel {
  inline-size: 340px; flex-shrink: 0; display: flex; flex-direction: column; min-block-size: 0;
  background: var(--qp-paper); border-inline-start: 1px solid var(--qp-line);
}
.qp-paneltabs { display: flex; gap: 4px; padding: 12px 16px; flex-shrink: 0; border-block-end: 1px solid var(--qp-line); }
.qp-paneltab {
  block-size: 32px; padding: 0 12px; border: none; border-radius: 8px; background: transparent;
  font-size: 12px; font-weight: 600; color: var(--qp-muted); cursor: pointer;
}
.qp-paneltab.is-on { background: var(--qp-soft); color: var(--qp-ink); font-weight: 700; }
.qp-panelbody { flex: 1; min-block-size: 0; overflow-y: auto; padding: 20px 16px; }
.qp-details { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 8px 16px; font-size: 13px; }
.qp-details dt { color: var(--qp-muted); }
.qp-details dd { margin: 0; font-weight: 600; }

/* ---- status bar ---- */
.qp-status {
  block-size: 28px; flex-shrink: 0; display: flex; align-items: center; gap: 20px; padding: 0 16px;
  background: var(--qp-paper); border-block-start: 1px solid var(--qp-line); font-size: 11px; color: var(--qp-muted);
}
.qp-status strong { color: var(--qp-ink); }

/* A narrow window drops the two side panels: 216 + 340 of chrome either side of a page leaves nothing to read. */
@media (max-width: 1100px) {
  .qp-panel { display: none; }
}
@media (max-width: 820px) {
  .qp-rail { display: none; }
  .qp-find { inline-size: 140px; }
  .qp-name { max-inline-size: 140px; }
}

/* ---- Quire Convert (Syed, 20 September 2026: quire-convert/DEVELOPER-NOTES.md and its canvas) ------------------------
   A big app, so it keeps its own identity like Quire PDF beside it: his teal, his output colours, Plus Jakarta Sans,
   as tokens on .qc-mount so the dark theme is one block. */
.qc-mount {
  --qc-teal: #0F766E;
  --qc-teal-dark: #115E59;
  --qc-tint: #EFFAF8;
  --qc-fold: #B8E0DA;
  --qc-ink: #1B1A21;
  --qc-muted: #5F5D68;
  --qc-line: #E4E2DD;
  --qc-rule: #EEEDE9;
  --qc-paper: #FFFFFF;
  --qc-app: #FBFAF8;
  --qc-drop: #F4F8F7;
  --qc-drop-line: #B9C9C6;
  --qc-good: #166534;
  --qc-warn-bg: #FEF3C7;
  --qc-warn: #78350F;
  --qc-bad: #9F2A21;
  --qc-font: 'Plus Jakarta Sans', 'Instrument Sans', ui-sans-serif, system-ui, "Segoe UI", sans-serif;

  display: block; block-size: 100dvh; background: var(--qc-app); color: var(--qc-ink); font-family: var(--qc-font);
}
[data-theme="dark"] .qc-mount {
  --qc-ink: #F4F3F1;
  --qc-muted: #A8A29E;
  --qc-line: #332F2C;
  --qc-rule: #2A2624;
  --qc-paper: #1F1D1C;
  --qc-app: #1A1917;
  --qc-drop: #1E2523;
  --qc-drop-line: #3A4B48;
  --qc-tint: #16302C;
  --qc-good: #7FCF9B;
  --qc-warn-bg: #3A2C12;
  --qc-warn: #F2C879;
  --qc-bad: #F0A79E;
}

.qc { display: flex; flex-direction: column; block-size: 100dvh; min-block-size: 0; }
.qc :where(button, input, select) { font-family: inherit; }
.qc :where(button, a, input, select):focus-visible { outline: 2px solid var(--qc-teal); outline-offset: 2px; }
.qc-grow { flex: 1; min-inline-size: 0; }
.qc-divider { inline-size: 1px; block-size: 24px; background: var(--qc-line); flex-shrink: 0; }
.qc-rule { block-size: 1px; background: var(--qc-rule); }
.qc-fine { font-size: 12px; color: var(--qc-muted); }

/* ---- header ---- */
.qc-top {
  block-size: 60px; flex-shrink: 0; display: flex; align-items: center; gap: 16px; padding: 0 20px 0 24px;
  background: var(--qc-paper); border-block-end: 1px solid var(--qc-line);
}
.qc-brand { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.qc-word { font-size: 17px; font-weight: 800; letter-spacing: -.2px; white-space: nowrap; }
.qc-word span { font-weight: 500; color: var(--qc-muted); }

.qc-tabs { display: flex; align-items: stretch; block-size: 60px; gap: 4px; flex-shrink: 0; }
/* The current tab is underlined in the teal rather than filled: it is a section of one app, not a button. */
.qc-tab {
  padding: 0 14px; border: none; background: transparent; color: var(--qc-muted);
  font-size: 13px; font-weight: 600; cursor: pointer; border-block-end: 2px solid transparent;
  /* Never broken across lines. "PDF to Office" wrapped into a three-line column is what Syed
     photographed on 25 September: the strip was the only thing in the row allowed to shrink, so it
     shrank by breaking its own words into the badge beside it. */
  white-space: nowrap;
}
.qc-tab:hover { color: var(--qc-ink); }
.qc-tab.is-on { color: var(--qc-teal); border-block-end-color: var(--qc-teal); font-weight: 700; }

.qc-badge {
  display: flex; align-items: center; gap: 6px; block-size: 32px; padding: 0 12px; flex-shrink: 0;
  border-radius: 16px; background: var(--qc-tint); color: var(--qc-teal-dark); font-size: 12px; font-weight: 600;
}
[data-theme="dark"] .qc-mount .qc-badge { color: var(--qc-fold); }

/*
 * A NARROW WINDOW DROPS THE REDUNDANT THING FIRST, and the redundant thing is our own name.
 *
 * The header is one row of five: the wordmark, the tab strip, the privacy badge. Something has to
 * give when the window is small, and it used to be the tabs - which gave by breaking their labels
 * into columns over the badge (Syed, 25 September: "Text are Overlapped"). The tabs are what the
 * app is FOR, so they no longer shrink or wrap; these two go instead, in the order of what is least
 * missed. The window title bar already says QUIRE Convert with its icon two centimetres above, so
 * the wordmark inside the app is saying it twice. The badge outlives it, because "nothing leaves
 * this server" is the one thing on this screen a person cannot learn anywhere else.
 *
 * The widths are measured, not chosen: tools/e2e/convert-header-check.mjs walks the header down
 * and reports the first width at which something wraps, overlaps or is cut off. Convert declares
 * minWidth 760, so 760 and up must hold - and these hold well below it, since a phone draws this
 * screen too.
 */
@media (max-width: 860px) { .qc-mount .qc-word { display: none; } }
@media (max-width: 720px) { .qc-mount .qc-badge { display: none; } }

.qc-quiet {
  block-size: 38px; padding: 0 12px; border: none; border-radius: 10px; background: transparent;
  font-size: 13px; font-weight: 600; color: var(--qc-ink); cursor: pointer;
}
.qc-quiet:hover:not(:disabled) { background: var(--qc-rule); }
.qc-quiet:disabled { color: var(--qc-muted); opacity: .5; cursor: not-allowed; }

/* ---- body ---- */
.qc-body { flex: 1; display: flex; gap: 24px; padding: 28px; min-block-size: 0; }
.qc-left { flex: 1; display: flex; flex-direction: column; gap: 20px; min-inline-size: 0; min-block-size: 0; }
.qc-head { margin: 0; font-size: 26px; font-weight: 800; letter-spacing: -.4px; }
.qc-sub { margin: 4px 0 0; font-size: 14px; color: var(--qc-muted); }

.qc-drop {
  block-size: 190px; flex-shrink: 0; box-sizing: border-box;
  border: 2px dashed var(--qc-drop-line); border-radius: 16px; background: var(--qc-drop);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; padding: 12px;
}
.qc-drop.is-over { border-color: var(--qc-teal); background: var(--qc-tint); }
.qc-dropword { font-size: 16px; font-weight: 700; }
.qc-dropdo { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; }

.qc-btn {
  block-size: 40px; display: inline-flex; align-items: center; padding: 0 16px; flex-shrink: 0;
  border: 1px solid var(--qc-line); border-radius: 10px; background: var(--qc-paper); color: var(--qc-ink);
  font-size: 13px; font-weight: 600; cursor: pointer; text-decoration: none; white-space: nowrap;
}
.qc-btn:hover:not(:disabled) { background: var(--qc-rule); }
.qc-btn:disabled { color: var(--qc-muted); opacity: .5; cursor: not-allowed; }
.qc-btn-teal { border-color: transparent; background: var(--qc-teal); color: #FFFFFF; font-weight: 700; }
.qc-btn-teal:hover:not(:disabled) { background: var(--qc-teal-dark); }

.qc-problem { margin: 0; font-size: 13px; font-weight: 600; color: var(--qc-bad); }
.qc-note { margin: 0; font-size: 12px; color: var(--qc-warn); background: var(--qc-warn-bg); padding: 8px 12px; border-radius: 9px; }
/* Something the conversion DID, not something it lost: teal rather than the amber of a warning, because
   recognised text is in the document and the person is being asked to check it, not told it is missing. */
.qc-said { color: var(--qc-teal-dark); background: var(--qc-tint); }
[data-theme="dark"] .qc-mount .qc-said { color: var(--qc-fold); }

/* ---- the file list ---- */
.qc-list {
  flex: 1; min-block-size: 0; display: flex; flex-direction: column; overflow-y: auto;
  background: var(--qc-paper); border: 1px solid var(--qc-line); border-radius: 16px;
}
.qc-listbar {
  block-size: 52px; flex-shrink: 0; display: flex; align-items: center; justify-content: space-between;
  padding: 0 20px; border-block-end: 1px solid var(--qc-rule);
}
.qc-listcount { font-size: 14px; font-weight: 700; }
.qc-empty { margin: 0; padding: 28px 20px; font-size: 13px; color: var(--qc-muted); text-align: center; }

.qc-row {
  min-block-size: 76px; flex-shrink: 0; display: flex; align-items: center; gap: 14px; padding: 12px 20px;
  border-block-end: 1px solid var(--qc-rule);
}
.qc-row:last-child { border-block-end: none; }
.qc-chip {
  inline-size: 36px; block-size: 44px; flex-shrink: 0; box-sizing: border-box; border-radius: 4px;
  background: var(--qc-tint); color: var(--qc-teal-dark); font-size: 10px; font-weight: 800;
  display: flex; align-items: flex-end; justify-content: center; padding-block-end: 6px;
}
[data-theme="dark"] .qc-mount .qc-chip { color: var(--qc-fold); }
.qc-rowmid { flex: 1; min-inline-size: 0; display: flex; flex-direction: column; gap: 4px; }
.qc-rowname { font-size: 14px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qc-rowfine { font-size: 12px; color: var(--qc-muted); }
/* Reading a scan is the slow step and it is the person's own machine doing it, so it is named rather than
   left as another "Converting…": teal, because it is work in progress and not a problem. */
.qc-rowread { color: var(--qc-teal-dark); font-weight: 600; }
[data-theme="dark"] .qc-mount .qc-rowread { color: var(--qc-teal); }
.qc-rowdone { font-size: 12px; color: var(--qc-good); font-weight: 600; }
.qc-rowbad { font-size: 12px; color: var(--qc-bad); font-weight: 600; }
.qc-bar { block-size: 6px; border-radius: 3px; background: var(--qc-line); overflow: hidden; }
.qc-barfill { block-size: 6px; inline-size: 40%; background: var(--qc-teal); animation: qc-slide 1.1s ease-in-out infinite; }
@keyframes qc-slide { 0% { margin-inline-start: -40%; } 100% { margin-inline-start: 100%; } }
.qc-x {
  inline-size: 36px; block-size: 36px; flex-shrink: 0; border: none; border-radius: 9px; background: transparent;
  color: var(--qc-muted); font-size: 18px; cursor: pointer;
}
.qc-x:hover { background: var(--qc-rule); color: var(--qc-ink); }

/* ---- settings ---- */
.qc-side {
  inline-size: 480px; flex-shrink: 0; box-sizing: border-box; display: flex; flex-direction: column; gap: 14px;
  padding: 20px 22px; overflow-y: auto;
  background: var(--qc-paper); border: 1px solid var(--qc-line); border-radius: 16px;
}
.qc-group { display: flex; flex-direction: column; gap: 10px; }
.qc-grouphead { font-size: 12px; font-weight: 700; letter-spacing: .6px; text-transform: uppercase; color: var(--qc-muted); }
.qc-cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.qc-card {
  display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 14px 8px;
  border: 1px solid var(--qc-line); border-radius: 12px; background: var(--qc-paper); cursor: pointer;
}
.qc-card.is-on { border-color: var(--qc-teal); background: var(--qc-tint); }
.qc-cardname { font-size: 14px; font-weight: 700; color: var(--qc-ink); }
.qc-cardext { font-size: 11px; color: var(--qc-muted); }
.qc-morerow { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.qc-pill {
  block-size: 30px; padding: 0 10px; border: 1px solid var(--qc-line); border-radius: 15px;
  background: var(--qc-paper); color: var(--qc-ink); font-size: 12px; font-weight: 600; cursor: pointer;
}
.qc-pill.is-on { border-color: var(--qc-teal); background: var(--qc-tint); }
.qc-pill span { color: var(--qc-muted); font-weight: 500; }

.qc-check { display: flex; align-items: center; gap: 10px; font-size: 13px; cursor: pointer; }
.qc-check input { inline-size: 18px; block-size: 18px; accent-color: var(--qc-teal); }
.qc-check.is-off { color: var(--qc-muted); opacity: .6; cursor: not-allowed; }
/* What ticking OCR costs, under the tick rather than in a tooltip nobody opens. */
.qc-ocrfine { margin: -2px 0 0 28px; }

.qc-two { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.qc-field { display: flex; flex-direction: column; gap: 6px; min-inline-size: 0; }
.qc-field label { font-size: 12px; font-weight: 600; color: var(--qc-muted); }
.qc-field :where(input, select) {
  block-size: 36px; inline-size: 100%; box-sizing: border-box; padding: 0 10px;
  border: 1px solid var(--qc-line); border-radius: 9px; background: var(--qc-paper); color: var(--qc-ink);
  font-size: 13px;
}
.qc-field :where(input, select):disabled { color: var(--qc-muted); opacity: .6; cursor: not-allowed; }

.qc-go {
  block-size: 52px; flex-shrink: 0; border: none; border-radius: 12px;
  background: var(--qc-teal); color: #FFFFFF; font-size: 15px; font-weight: 800; cursor: pointer;
}
.qc-go:hover:not(:disabled) { background: var(--qc-teal-dark); }
.qc-go:disabled { opacity: .45; cursor: not-allowed; }
.qc-kept { display: flex; align-items: flex-start; gap: 8px; line-height: 1.45; }
.qc-kept svg { flex-shrink: 0; margin-block-start: 2px; }

/* A narrow window stacks the settings under the files rather than squeezing a 480px column beside them. */
@media (max-width: 1080px) {
  .qc-body { flex-direction: column; overflow-y: auto; }
  .qc-side { inline-size: auto; }
  .qc-list { min-block-size: 200px; }
}
@media (prefers-reduced-motion: reduce) {
  .qc-barfill { animation: none; inline-size: 100%; }
}

/* ---- Quire PDF: the layers over a page (phase two, 20 September 2026) -----------------------------------------------
   The canvas is the page. Above it sit invisible words, so a sentence can be selected, and the marks drawn over
   them. Both are positioned in page pixels and rebuilt whenever the zoom changes. */
.qp-sheet { position: relative; }
.qp-sheet canvas { display: block; position: relative; z-index: 0; }

/* PDF.js positions every run absolutely inside this, and the runs are transparent: what a reader sees is the
   painted page, and what the browser selects is the text over it. Kept minimal rather than importing pdf_viewer.css,
   which brings a whole viewer's styling with it. */
.qp-text {
  position: absolute; inset: 0; z-index: 1; overflow: hidden; line-height: 1;
  opacity: 1; forced-color-adjust: none; text-size-adjust: none; transform-origin: 0 0;
}
.qp-text :where(span, br) {
  position: absolute; white-space: pre; cursor: text; transform-origin: 0 0;
  color: transparent;
}
.qp-text ::selection { background: color-mix(in srgb, var(--qp-indigo) 35%, transparent); }
/* Selecting is only on while a tool wants a selection; otherwise the text layer would swallow every drag. */
.qp-text { pointer-events: none; user-select: none; }
.qp-sheet.is-selecting .qp-text { pointer-events: auto; user-select: text; }

.qp-marks { position: absolute; inset: 0; z-index: 2; pointer-events: none; touch-action: pan-y; }
.qp-marks.is-drawing { pointer-events: auto; cursor: crosshair; touch-action: none; }
.qp-mark { pointer-events: auto; cursor: pointer; }
.qp-mark-highlight { mix-blend-mode: multiply; opacity: .45; }
.qp-mark-underline, .qp-mark-strike { opacity: .9; }
/* A redaction is opaque black, never translucent: a box somebody can read through is a box they will trust. */
.qp-mark-redaction { fill: #000000; }
.qp-mark-pin { cursor: pointer; }
.qp-mark-live { pointer-events: none; }
.qp-mark-rubber { fill: color-mix(in srgb, var(--qp-indigo) 18%, transparent); stroke: var(--qp-indigo); stroke-width: 1.5; }

/* The bar that appears above the page while a marking tool is active, as the canvas has it. */
.qp-context {
  position: sticky; inset-block-start: 0; z-index: 5; align-self: center; flex-shrink: 0;
  display: flex; align-items: center; gap: 6px; block-size: 44px; padding: 0 12px;
  background: var(--qp-paper); border: 1px solid var(--qp-line); border-radius: 12px;
  box-shadow: 0 2px 8px rgba(27, 26, 33, .10); font-size: 13px;
}
.qp-contextname { font-size: 12px; font-weight: 700; color: var(--qp-tint-ink); padding: 0 4px; }
.qp-contextfine { font-size: 12px; color: var(--qp-muted); }
.qp-swatch {
  inline-size: 26px; block-size: 26px; border-radius: 7px; border: 1px solid var(--qp-line); cursor: pointer;
}
.qp-swatch.is-on { outline: 2px solid var(--qp-ink); outline-offset: 1px; }

.qp-refused {
  position: sticky; inset-block-start: 0; z-index: 5; align-self: center; margin: 0; flex-shrink: 0;
  padding: 8px 14px; border-radius: 10px; font-size: 13px; font-weight: 600;
  background: var(--qp-tint); color: var(--qp-tint-ink);
}

/* ---- the Marks rail and the Comments panel ---- */
.qp-markrow {
  inline-size: 100%; display: flex; align-items: center; gap: 10px; padding: 8px 14px;
  border: none; background: transparent; color: var(--qp-ink); text-align: start; cursor: pointer;
}
.qp-markrow:hover { background: var(--qp-soft); }
.qp-markdot { inline-size: 12px; block-size: 12px; border-radius: 4px; flex-shrink: 0; border: 1px solid var(--qp-line); }
.qp-markwords { display: flex; flex-direction: column; min-inline-size: 0; }
.qp-markkind { font-size: 13px; font-weight: 600; }
.qp-markwhere { font-size: 11px; color: var(--qp-muted); }

.qp-comment { padding: 12px 0; border-block-end: 1px solid var(--qp-line); }
.qp-comment:last-child { border-block-end: none; }
.qp-commenthead { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.qp-commentwho { font-size: 13px; font-weight: 700; }
.qp-commentgo {
  border: none; background: transparent; padding: 0; cursor: pointer;
  font: inherit; font-size: 12px; color: var(--qp-indigo); text-decoration: underline;
}
.qp-commentbody { margin: 6px 0 0; font-size: 13px; line-height: 1.5; color: var(--qp-ink); }

/* Somewhere to actually write the comment. Syed, 25 September 2026: "There is no option to write
   comment is just add the icon again and again". Deliberately the same size and colour as
   .qp-commentbody above, because it becomes that text the moment it is left - a box that looked like
   a form field would make reading a thread of comments feel like filling one in. */
.qp-commentwrite {
  inline-size: 100%; margin-block-start: 6px; padding: 6px 8px; resize: vertical;
  border: 1px solid var(--qp-line); border-radius: 8px;
  background: var(--qp-app); color: var(--qp-ink);
  font: inherit; font-size: 13px; line-height: 1.5;
}
.qp-commentwrite::placeholder { color: var(--qp-muted); }
.qp-commentwrite:focus-visible { outline: 2px solid var(--qp-indigo); outline-offset: 1px; }

/* The keyboard shortcuts, which Help offered and never showed. Sized to its content rather than to the
   window: the list is short by design, because only the shortcuts that DO something are in it. */
.qp-keys {
  position: absolute; inset-block-start: 50%; inset-inline-start: 50%; transform: translate(-50%, -50%);
  z-index: 40; inline-size: min(380px, calc(100% - 48px)); max-block-size: min(70vh, 560px); overflow-y: auto;
  padding: 18px 20px 20px; border: 1px solid var(--qp-line); border-radius: 14px;
  background: var(--qp-app); color: var(--qp-ink); box-shadow: 0 24px 60px -24px rgb(0 0 0 / .45);
}
.qp-keyshead { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.qp-keyshead h2 { margin: 0; font-size: 15px; font-weight: 700; }
.qp-keyslist { margin: 14px 0 0; display: grid; gap: 2px; }
.qp-keysrow {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
  padding: 7px 8px; border-radius: 8px; font-size: 13px;
}
.qp-keysrow:nth-child(odd) { background: var(--qp-soft); }
.qp-keysrow dt { color: var(--qp-ink); }
.qp-keysrow dd { margin: 0; }
.qp-keysrow kbd {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px;
  padding: 2px 7px; border: 1px solid var(--qp-line); border-radius: 6px;
  background: var(--qp-paper); color: var(--qp-muted);
}
.qp-commentdrop {
  margin-block-start: 8px; border: none; background: transparent; padding: 0; cursor: pointer;
  font: inherit; font-size: 12px; color: var(--qp-muted); text-decoration: underline;
}
.qp-commentdrop:hover { color: var(--qp-red); }

/* Applying a redaction: the one control in Quire PDF that cannot be undone, so it looks like it. */
.qp-apply {
  block-size: 30px; padding: 0 12px; border: none; border-radius: 8px; cursor: pointer;
  background: var(--qp-ink); color: var(--qp-paper); font-size: 12px; font-weight: 700;
}
.qp-apply:disabled { opacity: .4; cursor: not-allowed; }
.qp-apply.is-danger { background: var(--qp-red); color: #FFFFFF; }
.qp-cancel {
  block-size: 30px; padding: 0 10px; border: none; border-radius: 8px; background: transparent;
  color: var(--qp-muted); font-size: 12px; font-weight: 600; cursor: pointer;
}
.qp-done {
  position: sticky; inset-block-start: 0; z-index: 5; align-self: center; margin: 0; flex-shrink: 0;
  padding: 8px 14px; border-radius: 10px; font-size: 13px;
  background: var(--qp-paper); border: 1px solid var(--qp-line);
}
.qp-done a { color: var(--qp-indigo); font-weight: 600; }

/* The signature pad: a small dialog over the page area, drawn or typed. */
.qp-sign {
  position: sticky; inset-block-start: 56px; z-index: 6; align-self: center; flex-shrink: 0;
  display: flex; flex-direction: column; gap: 12px; padding: 16px; inline-size: 460px; box-sizing: border-box;
  background: var(--qp-paper); border: 1px solid var(--qp-line); border-radius: 14px;
  box-shadow: 0 14px 36px rgba(27, 26, 33, .18);
}
.qp-signtabs { display: flex; gap: 4px; }
.qp-signtab {
  block-size: 30px; padding: 0 12px; border: none; border-radius: 8px; background: transparent;
  font-size: 12px; font-weight: 600; color: var(--qp-muted); cursor: pointer;
}
.qp-signtab.is-on { background: var(--qp-soft); color: var(--qp-ink); font-weight: 700; }
.qp-signtab:disabled { opacity: .45; cursor: not-allowed; }
.qp-signpad {
  inline-size: 100%; block-size: 150px; touch-action: none; cursor: crosshair;
  background: var(--qp-paper); border: 1px dashed var(--qp-line); border-radius: 10px;
}
.qp-signtype {
  block-size: 56px; padding: 0 14px; border: 1px solid var(--qp-line); border-radius: 10px;
  background: var(--qp-paper); color: var(--qp-ink); font-size: 22px;
}
.qp-signkeep { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--qp-ink); cursor: pointer; }
.qp-signkeep input { inline-size: 16px; block-size: 16px; accent-color: var(--qp-red); }
.qp-signdo { display: flex; align-items: center; gap: 8px; }

/* The status bar prompt on a scanned page, in the amber Syed's tokens give it. */
.qp-scan {
  block-size: 22px; padding: 0 8px; border: none; border-radius: 6px; cursor: pointer;
  background: #FEF3C7; color: #78350F; font-size: 11px; font-weight: 600;
}
.qp-scan:disabled { opacity: .7; cursor: progress; }
[data-theme="dark"] .qp-mount .qp-scan { background: #3A2C12; color: #F2C879; }
.qp-readnote { font-size: 11px; color: var(--qp-muted); }

/* ==========================================================================================
   THE COMPANY BRAIN (Syed, 21 September 2026)

   A search box, the answer with the passages behind it, and a list of what has been put in.
   Uses the desk's own tokens and adds none of its own: it opens inside a desk window as often
   as it opens as a page, and a Brain that did not match the desk around it would look pasted in.

   The reading column is capped. This screen is prose - an answer, a quotation, a filename - and
   a 1600px-wide line of text is unreadable however much room the window has.
   ========================================================================================== */
/* THE TOKENS ARE DECLARED HERE, light and dark, exactly as .ca-mount does for the Calendar.

   The desk's --dk-* tokens live on .dk, and this page has no .dk ancestor - so the first version of
   this screen inherited nothing, drew a search box with no border and an answer panel with no
   background, and looked broken. It read correctly in every text assertion; a screenshot is what
   found it. Declaring them on the mount means the screen is right as a page AND inside a desk
   window, without depending on who its parent turns out to be. */
.cb-mount {
  --dk-ground: #F5F5F4;
  --dk-surface: #FFFFFF;
  --dk-surface-2: #FAFAF9;
  --dk-surface-3: #F0EFED;
  --dk-line: #E7E5E4;
  --dk-line-soft: #F0EFED;
  --dk-ink: #1C1917;
  --dk-ink-2: #57534E;
  --dk-ink-3: #78716C;
  font-family: 'Instrument Sans', ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
}

[data-theme="dark"] .cb-mount {
  --dk-ground: #1C1917;
  --dk-surface: #231F1D;
  --dk-surface-2: #2A2624;
  --dk-surface-3: #332F2C;
  --dk-line: #332F2C;
  --dk-line-soft: #2A2624;
  --dk-ink: #FAFAF9;
  --dk-ink-2: #D6D3D1;
  --dk-ink-3: #A8A29E;
}

.cb {
  display: flex; flex-direction: column; gap: 18px;
  inline-size: min(100%, 860px); margin-inline: auto; padding: 22px 20px 40px;
  color: var(--dk-ink);
}

.cb-head { display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap; }
.cb-title { flex: 1; min-inline-size: 0; }
.cb-title h1 { margin: 0; font-size: 22px; font-weight: 700; letter-spacing: -.01em; }
.cb-strap { margin: 4px 0 0; font-size: 13px; color: var(--dk-ink-2); }

/* The two Brains. A segmented control rather than a dropdown: there are two, both are worth
   naming on the screen, and which one you are reading is the most important fact on it. */
.cb-scope {
  display: inline-flex; gap: 2px; padding: 3px; flex-shrink: 0;
  background: var(--dk-surface-2); border: 1px solid var(--dk-line); border-radius: 9px;
}
.cb-tab {
  padding: 5px 12px; border: none; border-radius: 7px; cursor: pointer;
  background: transparent; color: var(--dk-ink-2); font: inherit; font-size: 13px; font-weight: 600;
}
.cb-tab:hover { color: var(--dk-ink); }
.cb-tab[aria-selected="true"] { background: var(--dk-surface); color: var(--dk-ink); box-shadow: 0 1px 2px rgba(0, 0, 0, .06); }

/* Who can read this one. Quiet, but never hidden: it is what somebody is trusting. */
.cb-who { margin: -8px 0 0; font-size: 12px; color: var(--dk-ink-3); }

.cb-ask { display: flex; gap: 8px; }
.cb-ask-input {
  flex: 1; min-inline-size: 0; block-size: 42px; padding: 0 14px;
  border: 1px solid var(--dk-line); border-radius: 10px;
  background: var(--dk-surface); color: var(--dk-ink); font: inherit; font-size: 14px;
}
.cb-ask-input:focus-visible { outline: 2px solid var(--accent-fill); outline-offset: 1px; }
.cb-ask-go {
  block-size: 42px; padding: 0 18px; border: none; border-radius: 10px; cursor: pointer;
  background: var(--accent-fill); color: var(--on-accent); font: inherit; font-size: 14px; font-weight: 600;
}
.cb-ask-go:disabled { opacity: .55; cursor: default; }

.cb-error { margin: 0; font-size: 13px; color: var(--a-ruby, #B81628); }

.cb-answer {
  padding: 16px 18px; border: 1px solid var(--dk-line); border-radius: 12px;
  background: var(--dk-surface-2);
}
.cb-answer-text { margin: 0; font-size: 14px; line-height: 1.65; white-space: pre-wrap; }

/* The passages the answer came out of, shown rather than linked, so it can be checked against
   them without leaving the page. Indented and quieter than the answer: evidence, not the point. */
.cb-sources { margin-block-start: 14px; padding-block-start: 12px; border-block-start: 1px solid var(--dk-line-soft); }
.cb-sources-head {
  margin: 0 0 8px; font-size: 10px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--dk-ink-3);
}
.cb-sources ul { margin: 0; padding: 0; list-style: none; display: grid; gap: 10px; }
.cb-source { display: grid; gap: 3px; }
.cb-source-name { font-size: 12px; font-weight: 600; color: var(--dk-ink-2); }
.cb-source-quote {
  font-size: 12px; line-height: 1.55; color: var(--dk-ink-3);
  display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.cb-source-quote::before, .cb-source-quote::after { content: none; }

.cb-holds-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.cb-holds-head h2 { margin: 0; font-size: 14px; font-weight: 700; }
.cb-count { font-size: 12px; color: var(--dk-ink-3); }

.cb-list { margin: 12px 0 0; padding: 0; list-style: none; display: grid; gap: 1px; background: var(--dk-line-soft); border-radius: 10px; overflow: hidden; }
.cb-item { display: grid; gap: 2px; padding: 10px 14px; background: var(--dk-surface); }
.cb-item-name { font-size: 13px; font-weight: 600; overflow-wrap: anywhere; }
.cb-item-meta { font-size: 11px; color: var(--dk-ink-3); }

.cb-empty {
  margin-block-start: 12px; padding: 26px 20px; text-align: center;
  border: 1px dashed var(--dk-line); border-radius: 12px;
}
/* What the rooms decided. Kept visually quieter than the answer above it: this is a standing
   record somebody scans, not the thing they came to read. */
.cb-decided h2 { margin: 0 0 10px; font-size: 14px; font-weight: 700; }
.cb-decisions { margin: 0; padding: 0; list-style: none; display: grid; gap: 1px; background: var(--dk-line-soft); border-radius: 10px; overflow: hidden; }
.cb-decision { display: grid; gap: 3px; padding: 11px 14px; background: var(--dk-surface); }
.cb-decision-kind {
  justify-self: start; padding: 1px 7px; border-radius: 5px;
  background: var(--dk-surface-3); color: var(--dk-ink-3);
  font-size: 10px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
}
.cb-decision-text { font-size: 13px; line-height: 1.5; overflow-wrap: anywhere; }
.cb-decision-meta { font-size: 11px; color: var(--dk-ink-3); }

/* A replaced decision stays on the list - "we said the 30th, then we said the 7th" is the useful
   shape, and hiding the first makes the room look like it never changed its mind. It is dimmed and
   struck so it cannot be mistaken for what still stands. */
.cb-decision.is-replaced .cb-decision-text { text-decoration: line-through; opacity: .6; }
.cb-decision.is-replaced .cb-decision-kind { opacity: .6; }

.cb-empty h3 { margin: 0 0 6px; font-size: 14px; font-weight: 700; }
.cb-empty p { margin: 0; font-size: 13px; color: var(--dk-ink-2); }
.cb-quiet { margin-block-start: 8px !important; color: var(--dk-ink-3) !important; }

/* A desk window is narrower than a page. The switch drops under the title rather than squeezing
   the heading into two words. */
@media (max-width: 560px) {
  .cb { padding-inline: 16px; }
  .cb-head { flex-direction: column; gap: 10px; }
  .cb-ask { flex-direction: column; }
  .cb-ask-go { inline-size: 100%; }
}

/* A Roundup story: a real link, because pressing it leaves NETFIVE. Stacked like the task rows
   beside it so the two kinds of card read as one section (Syed, 24 September 2026). */
.dk-noticed-story {
  display: grid; gap: 1px; justify-self: stretch; min-inline-size: 0;
  padding-block-start: 6px; border-block-start: 1px solid var(--dk-line-soft);
  color: var(--dk-ink); text-decoration: none;
}
.dk-noticed-story:hover .dk-noticed-taskname { color: var(--accent); text-decoration: underline; }

/* "Uploading…" while an attachment is on its way (Syed, 24 September 2026). Quiet rather than red:
   nothing is wrong, something is simply taking a moment, and the red beneath it means the opposite. */
.rooms-uploading { margin: 0; color: var(--muted); font-size: var(--fs-sm); }

/* The assistant's answer, rendered as the markdown it is. The first and last blocks lose their
   outside margins so a one-line answer sits exactly where a person's one-line message does. */
.rooms-md > :first-child { margin-block-start: 0; }
.rooms-md > :last-child { margin-block-end: 0; }
