/* Bundled fonts (Apache-2.0 / SIL OFL-1.1) — the metric-compatible substitutes
   the canvas renders through so a cell's font shows deterministically on every
   machine (e.g. Calibri→Carlito, Arial→Liberation Sans), regardless of what the
   OS has installed. font_css_stack() (wasm) picks the family per cell. */
@font-face { font-family: "Roboto"; font-weight: 400; font-style: normal; src: url("./fonts/Roboto-Regular.ttf"); font-display: swap; }
@font-face { font-family: "Roboto"; font-weight: 700; font-style: normal; src: url("./fonts/Roboto-Bold.ttf"); font-display: swap; }
@font-face { font-family: "Roboto"; font-weight: 400; font-style: italic; src: url("./fonts/Roboto-Italic.ttf"); font-display: swap; }
@font-face { font-family: "Roboto"; font-weight: 700; font-style: italic; src: url("./fonts/Roboto-BoldItalic.ttf"); font-display: swap; }
@font-face { font-family: "Carlito"; font-weight: 400; font-style: normal; src: url("./fonts/Carlito-Regular.ttf"); font-display: swap; }
@font-face { font-family: "Carlito"; font-weight: 700; font-style: normal; src: url("./fonts/Carlito-Bold.ttf"); font-display: swap; }
@font-face { font-family: "Carlito"; font-weight: 400; font-style: italic; src: url("./fonts/Carlito-Italic.ttf"); font-display: swap; }
@font-face { font-family: "Carlito"; font-weight: 700; font-style: italic; src: url("./fonts/Carlito-BoldItalic.ttf"); font-display: swap; }
@font-face { font-family: "Caladea"; font-weight: 400; font-style: normal; src: url("./fonts/Caladea-Regular.ttf"); font-display: swap; }
@font-face { font-family: "Caladea"; font-weight: 700; font-style: normal; src: url("./fonts/Caladea-Bold.ttf"); font-display: swap; }
@font-face { font-family: "Caladea"; font-weight: 400; font-style: italic; src: url("./fonts/Caladea-Italic.ttf"); font-display: swap; }
@font-face { font-family: "Caladea"; font-weight: 700; font-style: italic; src: url("./fonts/Caladea-BoldItalic.ttf"); font-display: swap; }
@font-face { font-family: "Liberation Sans"; font-weight: 400; font-style: normal; src: url("./fonts/LiberationSans-Regular.ttf"); font-display: swap; }
@font-face { font-family: "Liberation Sans"; font-weight: 700; font-style: normal; src: url("./fonts/LiberationSans-Bold.ttf"); font-display: swap; }
@font-face { font-family: "Liberation Sans"; font-weight: 400; font-style: italic; src: url("./fonts/LiberationSans-Italic.ttf"); font-display: swap; }
@font-face { font-family: "Liberation Sans"; font-weight: 700; font-style: italic; src: url("./fonts/LiberationSans-BoldItalic.ttf"); font-display: swap; }
@font-face { font-family: "Liberation Serif"; font-weight: 400; font-style: normal; src: url("./fonts/LiberationSerif-Regular.ttf"); font-display: swap; }
@font-face { font-family: "Liberation Serif"; font-weight: 700; font-style: normal; src: url("./fonts/LiberationSerif-Bold.ttf"); font-display: swap; }
@font-face { font-family: "Liberation Serif"; font-weight: 400; font-style: italic; src: url("./fonts/LiberationSerif-Italic.ttf"); font-display: swap; }
@font-face { font-family: "Liberation Serif"; font-weight: 700; font-style: italic; src: url("./fonts/LiberationSerif-BoldItalic.ttf"); font-display: swap; }
@font-face { font-family: "Liberation Mono"; font-weight: 400; font-style: normal; src: url("./fonts/LiberationMono-Regular.ttf"); font-display: swap; }
@font-face { font-family: "Liberation Mono"; font-weight: 700; font-style: normal; src: url("./fonts/LiberationMono-Bold.ttf"); font-display: swap; }
@font-face { font-family: "Liberation Mono"; font-weight: 400; font-style: italic; src: url("./fonts/LiberationMono-Italic.ttf"); font-display: swap; }
@font-face { font-family: "Liberation Mono"; font-weight: 700; font-style: italic; src: url("./fonts/LiberationMono-BoldItalic.ttf"); font-display: swap; }

/* Embedded isolation.

   A shadow boundary stops a host page's *selectors* from reaching in, but not
   its **inherited** properties: `font`, `color`, `line-height`, `letter-spacing`,
   `direction` and friends cross it, so an editor dropped into a page with
   `body { font: 20px Georgia; letter-spacing: .1em }` inherits all of it and
   comes out wrong. `all: initial` on the host severs inheritance for the whole
   subtree, and it is the one declaration that does.

   It deliberately does **not** reset custom properties — `all` never touches
   them — which is exactly what makes `--oc-*` the theming API: the host sets
   them on this element and they still get through. */
:host {
  all: initial;
  display: block;
  /* Deliberately **no** `contain`. `contain: layout` or `paint` makes this
     element a containing block for `position: fixed` descendants, and every
     menu, tooltip and popover in the editor is fixed and positioned from
     `getBoundingClientRect()` — i.e. from the viewport. With containment on,
     each of them resolved against this element instead and opened as far below
     where it belonged as the element sits down the page. `paint` would also
     clip a dropdown that extends past the editor's own box, which is most of
     them near the bottom edge. Isolation here comes from the shadow boundary
     and `all: initial`; containment added nothing that those two do not. */
  /* Re-established after `all: initial`, because the subtree inherits from
     here and `initial` for `font` is not a font anyone wants to read. */
  font: 400 13px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  color: var(--oc-text-color);
  text-align: left;
  direction: ltr;
}

/* Design tokens — self-contained editor chrome, implementing the "OpenCalc
   Editor" design system. The editor never depends on the marketing style.css.
   `--oc-surface-color` is the grouped-pill background; `--oc-popover-background-color` is popovers/panels.

   These are the **public theming surface** for an embedded editor: set any of
   them on the host element and it takes effect, because custom properties are
   the one thing that crosses a shadow boundary. Anything not listed keeps the
   default below, so a host overrides two colours rather than restating thirty. */
:root, :host {
  --oc-freeze-line-color: #5f6368;
  --oc-background-color: #ffffff;
  --oc-text-color: #0b0d12;
  --oc-icon-color: #454b57;        /* default toolbar icon */
  --oc-muted-text-color: #7b8391;       /* secondary text */
  --oc-faint-text-color: #9aa1ad;       /* tertiary / shortcuts */
  --oc-disabled-color: #b6bcc7;
  --oc-border-color: #e8eaee;      /* dividers, gridlines */
  --oc-control-border-color: #dfe2e8;    /* control outlines */
  --oc-border-hover-color: #c9cfd9;
  --oc-surface-color: #f6f7f9;     /* toolbar pill groups, name box */
  --oc-popover-background-color: #ffffff;        /* popovers, panels */
  --oc-gridline-color: #d3d7de;        /* interior gridlines — see the contrast note */
  --oc-accent-color: #2f6df6;
  --oc-accent-hover-color: #1f56cf;
  --oc-accent-contrast-color: #ffffff;
  --oc-selection-color: rgba(47, 109, 246, .10);
  --oc-find-highlight-color: rgba(245, 158, 11, .28);
  --oc-success-color: #16a34a;
  /* Advertised in the SDK's theme list and referenced by nothing, so a host
     that set `dangerColor` changed exactly nothing. Two different reds were
     spelled out instead — `#e53935` for an invalid-input ring and `#e5484d`
     for a destructive menu item — which is the same value meaning the same
     thing written twice, differently (`UX-TOKEN-01`). */
  --oc-danger-color: #e5484d;
  /* These were already honoured through a `var(…, fallback)`, so setting them
     worked; declaring them here makes the default discoverable in the same
     place as every other token rather than hidden in one rule. */
  --oc-tooltip-background-color: #1f2430;
  --oc-tooltip-text-color: #ffffff;
  --oc-control-shadow: 0 1px 2px rgba(11, 13, 18, .08);
  --oc-popover-shadow: 0 1px 2px rgba(11, 13, 18, .06), 0 14px 34px rgba(11, 13, 18, .14);
  /* --- Elevation and state, derived ------------------------------------
     Two elevations, because a third is a distinction nobody applies
     consistently: `raised` is attached to the page (a control), `overlay`
     floats above it (a menu, a popover, a dialog, a toast). They alias the
     component-named shadows rather than replacing them, because `controlShadow`
     and `popoverShadow` are in the published theme contract and renaming them
     would break every host that sets one.
  
     The accent derivations are the reason this is not only tidiness: the focus
     ring and the text selection used to be written as `rgba(47, 109, 246, ...)`
     — the *default* accent, spelled out. A host that set a brand accent got
     brand-coloured buttons and blue focus rings, which is exactly the "assembled
     rather than designed" look, and it was invisible to us because our own
     accent is that blue (`UX-TOKEN-01`). */
  --oc-elevation-raised: var(--oc-control-shadow);
  --oc-elevation-overlay: var(--oc-popover-shadow);
  --oc-accent-ring: color-mix(in srgb, var(--oc-accent-color) 32%, transparent);
  --oc-accent-soft: color-mix(in srgb, var(--oc-accent-color) 18%, transparent);
  --oc-danger-ring: color-mix(in srgb, var(--oc-danger-color) 30%, transparent);
  --oc-mono-font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
/* Dark tokens — applied both by the manual theme toggle (data-theme="dark",
   which must win over the OS setting) and by the OS preference when the user
   hasn't forced light. Duplicated because CSS can't share a declaration block
   across a media query and an attribute selector. */
:root[data-theme="dark"], :host([data-theme="dark"]) {
  --oc-freeze-line-color: #8b95a5;
  --oc-background-color: #0f1216;
  --oc-text-color: #e7ebf0;
  --oc-icon-color: #b3bccb;
  --oc-muted-text-color: #9aa6b4;
  --oc-faint-text-color: #6b7686;
  --oc-disabled-color: #454e5c;
  --oc-border-color: #262c34;
  --oc-control-border-color: #2c333d;
  --oc-border-hover-color: #3a434f;
  --oc-surface-color: #1a1f27;
  --oc-popover-background-color: #171b21;
  --oc-gridline-color: #333b46;
  --oc-accent-color: #5b8bff;
  --oc-accent-hover-color: #7aa0ff;
  --oc-accent-contrast-color: #0b0d12;
  --oc-selection-color: rgba(91, 139, 255, .16);
  --oc-find-highlight-color: rgba(245, 158, 11, .34);
  --oc-control-shadow: 0 1px 2px rgba(0, 0, 0, .4);
  --oc-danger-color: #ff6b6b;
  --oc-tooltip-background-color: #eef1f6;
  --oc-tooltip-text-color: #10131a;
  --oc-popover-shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 16px 40px rgba(0, 0, 0, .55);
  /* --- Elevation and state, derived ------------------------------------
     Two elevations, because a third is a distinction nobody applies
     consistently: `raised` is attached to the page (a control), `overlay`
     floats above it (a menu, a popover, a dialog, a toast). They alias the
     component-named shadows rather than replacing them, because `controlShadow`
     and `popoverShadow` are in the published theme contract and renaming them
     would break every host that sets one.
  
     The accent derivations are the reason this is not only tidiness: the focus
     ring and the text selection used to be written as `rgba(47, 109, 246, ...)`
     — the *default* accent, spelled out. A host that set a brand accent got
     brand-coloured buttons and blue focus rings, which is exactly the "assembled
     rather than designed" look, and it was invisible to us because our own
     accent is that blue (`UX-TOKEN-01`). */
  --oc-elevation-raised: var(--oc-control-shadow);
  --oc-elevation-overlay: var(--oc-popover-shadow);
  --oc-accent-ring: color-mix(in srgb, var(--oc-accent-color) 32%, transparent);
  --oc-accent-soft: color-mix(in srgb, var(--oc-accent-color) 18%, transparent);
  --oc-danger-ring: color-mix(in srgb, var(--oc-danger-color) 30%, transparent);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]), :host(:not([data-theme="light"])) {
    --oc-freeze-line-color: #8b95a5;
    --oc-background-color: #0f1216;
    --oc-text-color: #e7ebf0;
    --oc-icon-color: #b3bccb;
    --oc-muted-text-color: #9aa6b4;
    --oc-faint-text-color: #6b7686;
    --oc-disabled-color: #454e5c;
    --oc-border-color: #262c34;
    --oc-control-border-color: #2c333d;
    --oc-border-hover-color: #3a434f;
    --oc-surface-color: #1a1f27;
    --oc-popover-background-color: #171b21;
    --oc-gridline-color: #333b46;
    --oc-accent-color: #5b8bff;
    --oc-accent-hover-color: #7aa0ff;
    --oc-accent-contrast-color: #0b0d12;
    --oc-selection-color: rgba(91, 139, 255, .16);
  --oc-find-highlight-color: rgba(245, 158, 11, .34);
    --oc-control-shadow: 0 1px 2px rgba(0, 0, 0, .4);
  --oc-danger-color: #ff6b6b;
  --oc-tooltip-background-color: #eef1f6;
  --oc-tooltip-text-color: #10131a;
    --oc-popover-shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 16px 40px rgba(0, 0, 0, .55);
    /* --- Elevation and state, derived ------------------------------------
       Two elevations, because a third is a distinction nobody applies
       consistently: `raised` is attached to the page (a control), `overlay`
       floats above it (a menu, a popover, a dialog, a toast). They alias the
       component-named shadows rather than replacing them, because `controlShadow`
       and `popoverShadow` are in the published theme contract and renaming them
       would break every host that sets one.
    
       The accent derivations are the reason this is not only tidiness: the focus
       ring and the text selection used to be written as `rgba(47, 109, 246, ...)`
       — the *default* accent, spelled out. A host that set a brand accent got
       brand-coloured buttons and blue focus rings, which is exactly the "assembled
       rather than designed" look, and it was invisible to us because our own
       accent is that blue (`UX-TOKEN-01`). */
    --oc-elevation-raised: var(--oc-control-shadow);
    --oc-elevation-overlay: var(--oc-popover-shadow);
    --oc-accent-ring: color-mix(in srgb, var(--oc-accent-color) 32%, transparent);
    --oc-accent-soft: color-mix(in srgb, var(--oc-accent-color) 18%, transparent);
  --oc-danger-ring: color-mix(in srgb, var(--oc-danger-color) 30%, transparent);
  }
}
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, sans-serif;
  color: var(--oc-text-color);
  background: var(--oc-background-color);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
::selection { background: var(--oc-accent-soft); }
.editor-body {
  margin: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
/* Embedded, the shell fills the element it was given rather than the viewport.
   `100vh` inside a shadow root still measures the *window*, so an editor in a
   500px-tall slot laid itself out at full window height and spilled its bottom
   bar out of the frame. */
:host .editor-body { height: 100%; }

/* Chrome the host can turn off.
 *
 * An embedded editor is *their* product, not ours: the brand mark, the alpha
 * badge, the file button and the settings gear belong to this project's demo
 * page and have no business in someone else's application. So the header is
 * off by default when embedded and every other region is nameable, rather than
 * a host having to reach into the shadow root and hide things by selector —
 * which would break the moment the markup moved.
 *
 * `display: none` rather than removal: the editor's own code still binds these
 * elements at mount, and a missing node would be a null dereference in a code
 * path that has nothing to do with chrome. */
.oc-hide-header .app-header,
.oc-hide-toolbar .toolbar,
.oc-hide-formulabar .formula-bar,
.oc-hide-statusbar .bottom-bar { display: none; }
/* The tabs live inside the bottom bar, so hiding them alone keeps the bar. */
.oc-hide-tabs .sheet-tabs { display: none; }
/* **The menu bar hides its items, not itself** — because the roster lives
   inside it (COL-33 put it there so it would not fold away with the page
   header) and `display: none` on the bar takes the whole subtree with it. A
   host that hid the menus lost the ability to see who else was in the document,
   which is not a menu (`UX-CHROME-01`).
   The bar's own box is neutralised as well, so with nobody else present — the
   roster carries `hidden` until there is a session — it collapses to nothing
   and a hidden menu bar is genuinely gone. With somebody present it is a thin
   strip carrying only the faces. */
.oc-hide-menubar .menubar > *:not(.presence) { display: none; }
.oc-hide-menubar .menubar {
  height: auto; min-height: 0; padding: 0; background: none; border-bottom: 0;
}
.oc-hide-localepicker .locale-picker { display: none; }
/* `?mode=embedded` — the third chrome.
 *
 * `<opencalc-sheet>` already hides the header when it embeds this editor, on
 * the grounds quoted above: an embedded editor is the host's product. A host
 * that iframes `editor.html` with `?mode=embedded` is in exactly that position
 * and was getting the brand mark, the alpha badge, the Open button and the
 * settings gear anyway. This is that same decision, reached from the mode
 * rather than from an element wrapper — the region rule it reuses is the one
 * directly above, not a second way of saying the same thing.
 *
 * It is a *separate selector* from `.oc-hide-header` rather than the mode
 * adding that class, so `?hide=` stays the host's explicit list and the mode
 * stays the mode: a host can read either off the root and know which asked. */
.oc-chrome-embedded .app-header { display: none; }
.locale-picker select {
  font: inherit; font-size: 12px; padding: 2px 6px; border-radius: 6px;
  border: 1px solid var(--oc-control-border-color); background: var(--oc-bg, transparent);
  color: var(--oc-muted-text-color);
}

/* A command the host hid, or read-only removed. `hidden` alone loses to the
   `display:flex` these controls carry. */
[data-oc-command].oc-cmd-hidden,
.tb-group.oc-cmd-hidden,
.toolbar.oc-cmd-hidden { display: none !important; }
[data-oc-command].oc-cmd-disabled { opacity: .45; pointer-events: none; }

.app-header {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 52px;
  padding: 0 14px;
  border-bottom: 1px solid var(--oc-border-color);
  background: var(--oc-background-color);
  flex: 0 0 auto;
}
/* Folded away by the menu bar's caret, handing its 52px to the grid. */
.app-header.collapsed { display: none; }
.brand-logo { display: inline-flex; width: 22px; height: 22px; color: var(--oc-accent-color); flex: 0 0 auto; }
.brand-logo .icon { width: 100%; height: 100%; }
.tb-brand { font-weight: 600; font-size: 14px; letter-spacing: -0.01em; color: var(--oc-text-color); text-decoration: none; }
/* Alpha badge — pill with a status dot. */
.badge {
  display: inline-flex; align-items: center; gap: 6px; padding: 3px 9px 3px 7px;
  border-radius: 999px; border: 1px solid var(--oc-control-border-color); background: var(--oc-surface-color);
  font-size: 11.5px; font-weight: 600; color: #454b57; letter-spacing: .01em; white-space: nowrap;
}
.badge .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--oc-success-color); }
.header-right { margin-left: auto; display: flex; align-items: center; gap: 10px; }

.toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 48px;
  padding: 0 12px;
  border-bottom: 1px solid var(--oc-border-color);
  background: var(--oc-background-color);
  flex: 0 0 auto;
  /* One fixed row, never a scrollbar or a second row. When the toolbar is too
     narrow, whole groups collapse in-place into a single "Label ▾" button that
     opens the group's tools in a flyout — Excel-ribbon-style progressive
     collapse, lowest-priority group first. reflowToolbar() in editor.js drives
     it via each group's data-collapse priority. */
  overflow: visible;
}
/* A group's collapsed form: a labeled button shown in place of the group. */
.tb-collapsed {
  width: auto !important; padding: 0 8px !important; gap: 4px;
  font-size: 12px; font-weight: 600; color: var(--oc-icon-color); white-space: nowrap;
}
/* Must out-specify `.toolbar .tb-btn` (same 0,2,0 but declared later), or a
   hidden collapsed button would still render as a phantom next to its group. */
.toolbar .tb-collapsed[hidden] { display: none; }
.tb-collapsed .tbc-caret { width: 9px; height: 6px; opacity: .75; }
.tb-group[hidden] { display: none; }
/* Flyout holding a collapsed group's tools.
   Laid out as a labelled list, not as a mini-toolbar. In the toolbar an icon
   sits next to five neighbours in a known group, and hovering for a tooltip
   costs nothing because the thing is already on screen. Once the group has
   collapsed, three unlabelled glyphs in a floating box are a guessing game —
   you have to hover each one to find out what you opened. Every other menu in
   this application is a labelled list; a collapsed group is a menu. */
.tb-flyout { display: none; }
.tb-flyout:not([hidden]) {
  display: flex; flex-direction: column; align-items: stretch; gap: 1px;
  padding: 5px; min-width: 190px; max-width: min(92vw, 320px);
}
/* Full-width rows: icon at a fixed offset so the labels line up, which is what
   makes a list scannable rather than merely vertical. */
.tb-flyout .tb-btn {
  width: 100% !important; height: 28px; padding: 0 9px !important;
  justify-content: flex-start; gap: 10px; border-radius: 6px;
}
/* The label comes from the tooltip the button already carries, so there is no
   second copy of the text to translate and none to fall out of step: the i18n
   pass rewrites `data-tip`, and this follows it. */
.tb-flyout .tb-btn[data-tip]::after {
  content: attr(data-tip);
  font: 400 12.5px/1 var(--oc-font-family, inherit);
  color: var(--oc-text-color); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.tb-flyout .tb-btn .icon { flex: 0 0 auto; }
/* Controls that are not icon buttons — the font box, a colour swatch, a size
   field — keep their own shape and simply take a row each. */
.tb-flyout .menu-wrap { display: flex; }
.tb-flyout select, .tb-flyout input { width: 100%; }
.tb-flyout .tb-sep { display: none; }

/* ---- Application menu bar (File / Edit / View / …) ---- */
.menubar {
  display: flex; align-items: stretch; gap: 1px;
  height: 30px; padding: 0 8px; flex: 0 0 auto;
  background: var(--oc-background-color); border-bottom: 1px solid var(--oc-border-color);
  -webkit-user-select: none; user-select: none;
}
/* Page-header collapse toggle, pinned to the right end of the menu bar. The
   caret points up while the header is showing (click to fold it away) and
   flips down once it is hidden. */
.menu-collapse {
  margin-left: auto; align-self: center;
  width: 26px; height: 22px; padding: 0; border: 0; border-radius: 6px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; color: var(--oc-muted-text-color); cursor: pointer;
}
.menu-collapse:hover { background: var(--oc-surface-color); color: var(--oc-text-color); }
.menu-collapse .mc-caret { width: 12px; height: 8px; transition: transform 120ms ease; }
.menu-collapse.collapsed .mc-caret { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  .menu-collapse .mc-caret { transition: none; }
}

/* ---- Who else is in this document (COL-33) ----
   A face stack in the menu bar that opens the roster: who is here, which cell
   they are in, whether they are typing, and one click to go to them. The menu
   bar rather than the app header, because the header folds away and the people
   you are working with should not fold away with it. */
.presence { position: relative; display: inline-flex; align-items: center; margin-left: auto; }
/* `hidden` alone loses to the rule above: an author `display` beats the UA's
   `[hidden]` regardless of specificity — the same trap `.app-header` fell into
   and the reason its collapse is a class. */
.presence[hidden] { display: none; }
/* Exactly one element in the bar may claim the slack. With `margin-left:auto`
   on both this and the caret the free space is split between them and the
   roster strands itself mid-bar; when the roster is absent the caret takes the
   space back, which is why this is conditioned on `:not([hidden])`. */
.presence:not([hidden]) ~ .menu-collapse { margin-left: 4px; }
.presence-btn {
  display: inline-flex; align-items: center; gap: 7px; align-self: center;
  height: 22px; padding: 0 8px; border: 0; border-radius: 999px;
  background: transparent; color: var(--oc-muted-text-color);
  font: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
}
.presence-btn:hover { background: var(--oc-surface-color); color: var(--oc-text-color); }
.presence-btn:focus-visible { outline: 2px solid var(--oc-accent-color); outline-offset: 1px; }
/* A name is somebody else's text of any length; the chip is clamped so a long
   one cannot push the collapse caret off the bar. */
.presence-label { max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.presence-faces { display: inline-flex; align-items: center; }
.presence-faces:empty { display: none; }
.presence-face {
  box-sizing: border-box; flex: 0 0 auto;
  width: 18px; height: 18px; border-radius: 50%; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 9px; font-weight: 700; line-height: 1;
  /* Overlapped, so a crowd stays the width of a control rather than the width
     of the bar; the ring is the bar behind it, which is what separates two
     adjacent faces. */
  border: 1.5px solid var(--oc-background-color); margin-left: -5px;
}
.presence-faces .presence-face:first-child { margin-left: 0; }
.presence-face.presence-more { background: var(--oc-surface-color); color: var(--oc-muted-text-color); }
/* Somebody mid-word. Colour is never the only signal — the row in the list
   says "typing" in words. */
.presence-face.typing { box-shadow: 0 0 0 2px var(--oc-accent-color); }

/* The roster itself. Absolutely positioned, unlike every other `.popmenu`,
   which is `position: fixed` and placed by JS: those open from a toolbar that
   scrolls sideways, so their anchor moves. This one hangs off a button in a bar
   that never moves, and an anchored panel cannot drift away from its trigger.

   Written as `.popmenu.presence-menu` — two classes — because `.popmenu`'s own
   `position: fixed` is declared further down this file and a single class would
   lose to it on order alone. It did: the list rendered `position: fixed` with
   `top: calc(100% + 4px)` resolved against the *viewport*, which put it 424px
   down the page, nowhere near the button that opened it. Nothing about the rule
   said so; only opening it in a browser did. */
.popmenu.presence-menu {
  position: absolute; right: 0; left: auto; top: calc(100% + 4px);
  min-width: 236px; max-width: 320px;
  /* A busy document must not produce a list taller than the window. */
  max-height: min(52vh, 320px);
}
.presence-item {
  box-sizing: border-box; display: flex; align-items: center; gap: 9px;
  padding: 7px 9px;
}
.presence-item:focus-visible { outline: 2px solid var(--oc-accent-color); outline-offset: -1px; }
/* In the list a face sits on the popover, not on the bar, so it needs no ring
   and has room to be legible. */
.presence-item .presence-face {
  width: 22px; height: 22px; font-size: 10px; margin-left: 0; border: 0;
}
.presence-who { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto; }
.presence-name {
  font-size: 13px; font-weight: 600; color: var(--oc-text-color);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.presence-where {
  font-family: var(--oc-mono-font-family); font-size: 11px; color: var(--oc-muted-text-color);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Somebody on another sheet. The address already names their sheet; the arrow
   says at a glance that reaching them means leaving this one. */
.presence-item.elsewhere .presence-where::after { content: " \2197"; color: var(--oc-faint-text-color); }
.presence-typing {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; font-weight: 600; color: var(--oc-accent-color); white-space: nowrap;
}
.presence-typing::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor;
  animation: oc-presence-pulse 1.4s ease-in-out infinite;
}
@keyframes oc-presence-pulse { 0%, 100% { opacity: .3 } 50% { opacity: 1 } }
@media (prefers-reduced-motion: reduce) { .presence-typing::before { animation: none; } }
.presence-empty { padding: 8px 10px; font-size: 12.5px; color: var(--oc-muted-text-color); }

.menu-top {
  border: 0; background: transparent; color: var(--oc-text-color);
  font: inherit; font-size: 13px; padding: 0 10px; border-radius: 6px;
  cursor: pointer; display: inline-flex; align-items: center;
}
/* `display: inline-flex` above outranks the `hidden` attribute, so a hidden
   menu button kept its width and the bar measured 489px on a 390px screen with
   every menu supposedly folded away. Fourth time this trap has been sprung
   here — see .app-header (line ~399), .presence, and the overflow flyout — and
   each time the symptom was the same: JS sets `.hidden = true`, the element
   goes invisible-ish, and it still occupies the layout being measured. */
.menu-top[hidden] { display: none; }
.toolbar .tb-btn[hidden] { display: none; }
.menu-top:hover, .menu-top[aria-expanded="true"] { background: var(--oc-surface-color); }
/* A menu dropdown / submenu reuses .popmenu chrome. Items are full-width rows. */
.menu-drop { min-width: 208px; }
.menu-drop button, .menu-sub button {
  display: flex; align-items: center; gap: 10px; width: 100%;
  text-align: left; white-space: nowrap;
}
/* Left rail holds the ✓ for toggle items so labels stay aligned. */
.mi-check { width: 14px; flex: 0 0 14px; color: var(--oc-accent-color); font-size: 12px; }
.mi-label { flex: 1 1 auto; }
.mi-key { color: var(--oc-faint-text-color); font-size: 11.5px; margin-left: 18px; }
.mi-caret { color: var(--oc-muted-text-color); margin-left: 8px; }
.menu-drop .menu-sep, .menu-sub .menu-sep {
  height: 1px; margin: 4px 6px; background: var(--oc-border-color); border: 0;
}
.menu-sub { min-width: 176px; }

/* ---- Lightweight modal (Help ▸ shortcuts / about) ---- */
.oc-modal {
  position: fixed; inset: 0; z-index: 300; display: flex;
  align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .38);
}
.oc-modal[hidden] { display: none; }
.oc-modal-box {
  position: relative; background: var(--oc-popover-background-color); color: var(--oc-text-color);
  border: 1px solid var(--oc-control-border-color); border-radius: 14px;
  padding: 22px 24px; max-width: min(92vw, 460px); max-height: 80vh;
  overflow: auto; box-shadow: var(--oc-popover-shadow);
}
.oc-modal-x {
  position: absolute; top: 10px; right: 12px; border: 0; background: transparent;
  color: var(--oc-muted-text-color); font-size: 22px; line-height: 1; cursor: pointer;
}
.oc-modal-title { margin: 0 0 12px; font-size: 16px; }
.oc-modal-body { font-size: 13px; line-height: 1.55; }
/* Confirm prompt (e.g. the merge-discards-values warning). */
.oc-confirm-text { margin: 0 0 16px; }
.oc-confirm-actions { display: flex; justify-content: flex-end; gap: 8px; }
.oc-btn {
  padding: 7px 14px; border-radius: 8px; border: 1px solid var(--oc-control-border-color);
  background: var(--oc-background-color); color: var(--oc-text-color); font: inherit; font-size: 13px; cursor: pointer;
}
.oc-btn:hover { border-color: var(--oc-border-hover-color); }

/* ── Crash recovery bar (`SAVE-03`, `docs/83` §4.3) ──────────────────────────
   A band above the toolbar, present only when this browser holds a draft from
   a session that ended without one. It is deliberately not a modal: a recovered
   document is offered, never applied, and a dialog that blocks the editor to
   ask about yesterday's document is the feature deciding for the user in the
   other direction.

   `flex: 0 0 auto` in the body's column, so it takes its own height rather than
   stealing the grid's — the grid is sized from the space that is left, and a
   bar that overlaid it would hide the document the user is being asked to
   compare against. */
.oc-recovery {
  flex: 0 0 auto; padding: 8px 12px;
  border-bottom: 1px solid var(--oc-border-color);
  background: color-mix(in srgb, var(--oc-accent-color) 8%, var(--oc-background-color));
  font-size: 12.5px; line-height: 1.5; color: var(--oc-text-color);
}
.oc-recovery[hidden] { display: none; }
.oc-recovery-row {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
}
.oc-recovery-row + .oc-recovery-row { margin-top: 6px; }
.oc-recovery-note { color: var(--oc-muted-text-color); }
/* `margin-left: auto` rather than a spacer element: the buttons ride the right
   edge on a wide window and wrap under the sentence on a narrow one, which is
   what `editor.narrow-screens` asks of every band in this column. */
.oc-recovery-actions { margin-left: auto; display: inline-flex; gap: 6px; flex: 0 0 auto; }
.oc-recovery-actions .oc-btn { padding: 4px 10px; font-size: 12px; }
.oc-btn.primary { background: var(--oc-accent-color); border-color: var(--oc-accent-color); color: #fff; }
.oc-btn.primary:hover { filter: brightness(1.06); }

/* ── Share ───────────────────────────────────────────────────────────────────
   The warning is the first thing in the dialog and is styled as a warning, not
   as fine print: `COL-46` is an open P0 that can leave two replicas holding
   different formulas, and a notice a user's eye slides over is not a notice. */
.share-body { display: grid; gap: 14px; }
.share-warning {
  display: grid; gap: 6px; padding: 12px 14px; border-radius: 9px;
  border: 1px solid var(--oc-danger-color);
  background: color-mix(in srgb, var(--oc-danger-color) 9%, transparent);
  font-size: 12.5px; line-height: 1.45;
}
.share-warning p { margin: 0; color: var(--oc-text-color); }
.share-field { display: grid; gap: 5px; }
.share-field-label { font-size: 12px; color: var(--oc-muted-text-color); }
.share-field .oc-input {
  width: 100%; box-sizing: border-box; height: 30px; padding: 0 9px;
  border: 1px solid var(--oc-control-border-color); border-radius: 7px;
  background: var(--oc-background-color); color: var(--oc-text-color); font-size: 13px;
}
.share-note { margin: 0; font-size: 13px; color: var(--oc-text-color); }
.share-hint { margin: 0; font-size: 11.5px; color: var(--oc-muted-text-color); }
.share-ack { display: flex; align-items: flex-start; gap: 8px; font-size: 12.5px; cursor: pointer; }
.share-error { margin: 0; font-size: 12.5px; color: var(--oc-danger-color); }
/* Scoped to this dialog rather than to `.oc-btn` everywhere: the toolbar's
   undo/redo are authored disabled and already have their own dimming, and a
   global rule here would quietly restyle chrome this change has no business
   touching. */
.share-body .oc-btn:disabled { opacity: .45; cursor: default; filter: none; }
/* Custom number-format dialog: code field, live preview, quick presets. */
.cf-code {
  width: 100%; box-sizing: border-box; height: 34px; padding: 0 10px; margin-bottom: 10px;
  border: 1px solid var(--oc-control-border-color); border-radius: 8px;
  background: var(--oc-background-color); color: var(--oc-text-color); font-family: var(--oc-mono-font-family); font-size: 13px;
}
.cf-code:focus { outline: none; border-color: var(--oc-accent-color); }
.cf-preview {
  min-height: 34px; display: flex; align-items: center; justify-content: flex-end;
  padding: 0 10px; margin-bottom: 12px; border-radius: 8px;
  background: var(--oc-surface-color); font-family: var(--oc-mono-font-family); font-size: 14px;
}
.cf-preview.bad { color: var(--oc-danger-color); }
.cf-presets { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.cf-preset {
  padding: 4px 9px; border-radius: 999px; border: 1px solid var(--oc-control-border-color);
  background: var(--oc-background-color); color: var(--oc-text-color); font: inherit; font-size: 12px; cursor: pointer;
}
.cf-preset:hover { border-color: var(--oc-accent-color); color: var(--oc-accent-color); }
.cf-hint { margin: 0 0 16px; color: var(--oc-muted-text-color); font-size: 12px; line-height: 1.5; }
/* Sort dialog: a header checkbox and up to three "sort by / then by" rows. */
.sort-head { display: flex; align-items: center; gap: 7px; margin: 0 0 12px; cursor: pointer; }
.sort-keys { display: flex; flex-direction: column; gap: 8px; margin-bottom: 18px; }
.sort-key { display: grid; grid-template-columns: 64px 1fr 110px; align-items: center; gap: 8px; }
.sort-lbl { color: var(--oc-muted-text-color); font-size: 12.5px; }
.sort-key select {
  height: 30px; padding: 0 8px; border-radius: 8px; border: 1px solid var(--oc-control-border-color);
  background: var(--oc-background-color); color: var(--oc-text-color); font: inherit; font-size: 13px; min-width: 0;
}
.oc-modal-body kbd {
  font: 11.5px var(--oc-mono-font-family); background: var(--oc-surface-color); border: 1px solid var(--oc-border-color);
  border-radius: 5px; padding: 1px 6px;
}
.oc-modal-body .kb-row { display: flex; justify-content: space-between; gap: 20px; padding: 3px 0; }
/* A grouped-pill cluster of icon buttons. */
.tb-group {
  display: flex; align-items: center; gap: 2px; padding: 2px;
  border-radius: 10px; background: var(--oc-surface-color);
}
/* Bordered controls (font / size) sit directly on the bar, not in a pill. */
.tb-group.controls { background: transparent; padding: 0; gap: 6px; }
.tb-select {
  height: 30px; padding: 0 9px; border: 1px solid var(--oc-control-border-color); border-radius: 8px;
  background: var(--oc-background-color); color: var(--oc-text-color); font-size: 12.5px; cursor: pointer; max-width: 132px;
}
.tb-select:hover { border-color: var(--oc-border-hover-color); }
.tb-size { width: 62px; }
/* The font-name and font-size controls are editable comboboxes: a text input
   (any value accepted) plus a caret that opens a custom .combo-menu list — a
   native <datalist> is filtered by the input's own text, which hid every option
   but the current one. `.tb-font` is wider so long names (Times New Roman) fit. */
input.tb-size, input.tb-font { box-sizing: border-box; cursor: text; }
input.tb-size:focus, input.tb-font:focus { outline: none; border-color: var(--oc-accent-color); }
input.tb-font { width: 118px; }
input.tb-size::placeholder, input.tb-font::placeholder { color: var(--oc-faint-text-color); }
.tb-combo { position: relative; display: inline-flex; align-items: center; }
.tb-combo input { padding-right: 22px; }
.tb-combo-caret {
  position: absolute; right: 1px; top: 1px; bottom: 1px; width: 20px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 0 7px 7px 0; background: transparent; color: var(--oc-muted-text-color); cursor: pointer;
}
.tb-combo-caret:hover { color: var(--oc-text-color); background: var(--oc-surface-color); }
.tb-combo-caret svg { width: 9px; height: 6px; }
/* The combobox dropdown: a scrolling option list in .popmenu chrome. */
.combo-menu { max-height: 320px; overflow-y: auto; padding: 4px; min-width: 0; }
/* Scoped under .combo-menu so these win over the generic `.popmenu button`. */
.combo-menu .combo-item {
  display: block; width: 100%; text-align: left; padding: 6px 10px 6px 26px;
  border: 1px solid transparent; border-radius: 6px; background: transparent; color: var(--oc-text-color);
  font-size: 13px; font-weight: 400; line-height: 1.35; white-space: nowrap; cursor: pointer; position: relative;
}
.combo-menu .combo-item:hover, .combo-menu .combo-item.active { background: var(--oc-surface-color); }
/* The applied value keeps a check so the list is readable at a glance even
   when the row rendered in its own face looks nothing like the others. */
.combo-menu .combo-item.checked::before {
  content: "✓"; position: absolute; left: 9px; top: 50%; transform: translateY(-50%);
  font-size: 11px; color: var(--oc-accent-color); font-family: system-ui, sans-serif;
}
/* Grow/shrink font glyphs ("A▲" / "A▼") — small triangle so the A stays legible. */
#tb-size-up, #tb-size-down { font-size: 12px; letter-spacing: -1px; }

.icon { width: 16px; height: 16px; display: block; }
.icon-sm { width: 13px; height: 13px; display: block; }

/* Pill icon buttons: transparent, lift to white on hover, white+accent when on. */
.toolbar .tb-btn, .tb-file {
  width: 30px; height: 30px; padding: 0; border: 0; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; color: var(--oc-icon-color); cursor: pointer; transition: background .1s, color .1s, box-shadow .1s;
}
.toolbar .tb-btn:hover, .tb-file:hover { background: var(--oc-background-color); color: var(--oc-text-color); box-shadow: var(--oc-control-shadow); }
.toolbar .tb-btn:disabled { color: var(--oc-disabled-color); background: transparent; box-shadow: none; cursor: default; }
.toolbar .tb-btn.on, .toolbar .tb-btn[aria-pressed="true"], .tb-toggle[aria-pressed="true"] {
  background: var(--oc-background-color); color: var(--oc-accent-color); box-shadow: var(--oc-control-shadow);
}
/* Text-glyph buttons ($ % .00) in a pill. */
.toolbar .tb-btn.glyph { font-size: 13px; font-weight: 600; color: var(--oc-icon-color); }
.toolbar .tb-btn.glyph.mono { font-family: var(--oc-mono-font-family); font-size: 12px; }
/* Increase/decrease-decimal glyphs (".0→" / "←.0") need a smaller size to fit. */
.toolbar .tb-btn.glyph.nf-dec { font-size: 10.5px; letter-spacing: -0.5px; }

.settings { position: relative; }
.tb-icon {
  width: 32px; height: 32px; padding: 0; border-radius: 8px; border: 1px solid var(--oc-control-border-color);
  background: var(--oc-background-color); color: var(--oc-icon-color); font-size: 16px; cursor: pointer; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
.tb-icon:hover { border-color: var(--oc-border-hover-color); background: var(--oc-surface-color); color: var(--oc-text-color); }
.settings-panel {
  position: absolute; top: 40px; right: 0; z-index: 20; width: 230px;
  background: var(--oc-popover-background-color); border: 1px solid var(--oc-control-border-color); border-radius: 12px;
  padding: 14px; box-shadow: var(--oc-popover-shadow);
  display: flex; flex-direction: column; gap: 14px;
}
.settings-panel[hidden] { display: none; }
.set-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; font-size: 13px; color: var(--oc-text-color); }
.set-row select {
  padding: 6px 8px; border-radius: 8px; border: 1px solid var(--oc-border-color);
  background: var(--oc-background-color); color: var(--oc-text-color);
}
.range-wrap { display: flex; align-items: center; gap: 8px; }
.range-wrap input[type="range"] { width: 110px; accent-color: var(--oc-accent-color); }
.range-val { font-family: var(--oc-mono-font-family); font-size: 12px; color: var(--oc-muted-text-color); min-width: 30px; }
.swatches { display: flex; gap: 8px; }
.swatches button {
  width: 22px; height: 22px; padding: 0; border-radius: 50%; border: 2px solid transparent;
  cursor: pointer;
}
.swatches button[aria-current="true"] { border-color: var(--oc-text-color); }

.menu-wrap { position: relative; display: inline-flex; }
/* Fixed-positioned so it escapes the toolbar's `overflow-x` scroll clip; the
   host computes `left`/`top` from the trigger button on open. */
.popmenu {
  position: fixed; z-index: 60; min-width: 178px;
  background: var(--oc-popover-background-color); border: 1px solid var(--oc-control-border-color); border-radius: 12px;
  padding: 5px; display: flex; flex-direction: column; gap: 2px;
  box-shadow: var(--oc-popover-shadow);
}
.popmenu[hidden] { display: none; }
/* A clamped menu scrolls rather than overflowing the viewport — `anchorMenu`
   sets `max-height` when neither side has room for the whole list. */
/* `border-box` so `anchorMenu`'s clamp is exact: with the default `content-box`
   the padding and border sit *outside* `max-height`, and a menu told to be
   exactly as tall as the space below the button overflowed the viewport by
   precisely that much. */
.popmenu { overflow-y: auto; overscroll-behavior: contain; box-sizing: border-box; }
.popmenu button {
  width: 100%; text-align: left; padding: 8px 10px; border: 1px solid transparent;
  border-radius: 6px; background: transparent; color: var(--oc-text-color);
  font-size: 13px; font-weight: 500; cursor: pointer;
}
.popmenu button:hover { background: var(--oc-surface-color); }
.tb-file input { display: none; }
.tb-sep { width: 1px; height: 22px; background: var(--oc-border-color); margin: 0 2px; flex: 0 0 auto; }

/* Swatch popover (font color, fill color). */
.swatch-menu { flex-direction: row; flex-wrap: wrap; width: 152px; gap: 6px; padding: 8px; }
.swatch-menu button {
  width: 24px; height: 24px; padding: 0; border-radius: 6px; border: 1px solid var(--oc-border-color);
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
}
.swatch-menu button:hover { outline: 2px solid var(--oc-accent-color); outline-offset: 1px; }
.swatch-menu .no-fill { background: var(--oc-background-color); color: var(--oc-muted-text-color); }
.popmenu button.danger { color: var(--oc-danger-color); }
.menu-sep { height: 1px; background: var(--oc-border-color); margin: 4px 2px; }
.ctx-menu { position: fixed; min-width: 168px; }
/* Submenu row: a › affordance; its child popmenu is fixed-positioned by JS. */
.popmenu button.has-sub { position: relative; padding-right: 24px; }
.popmenu button.has-sub::after {
  content: "›"; position: absolute; right: 11px; top: 50%; transform: translateY(-50%);
  color: var(--oc-muted-text-color); font-size: 16px; line-height: 1;
}
.ctx-submenu { min-width: 168px; }

/* Enhanced color popover (shared by font & fill): palette + custom hex + recent. */
.color-menu { width: 214px; gap: 8px; }
.color-menu .cm-none {
  width: 100%; box-sizing: border-box; display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; border-radius: 7px; border: 1px solid var(--oc-control-border-color);
  background: var(--oc-background-color); color: var(--oc-text-color); font-size: 13px; font-weight: 500; cursor: pointer;
}
.color-menu .cm-none:hover { background: var(--oc-surface-color); }
.color-menu .cm-none .icon-sm { width: 15px; height: 15px; color: var(--oc-muted-text-color); }
.color-menu .cm-grid { display: grid; grid-template-columns: repeat(10, 1fr); gap: 3px; }
.color-menu .cm-sw {
  width: 100%; aspect-ratio: 1; padding: 0; border-radius: 3px;
  border: 1px solid rgba(0, 0, 0, .12); cursor: pointer;
}
.color-menu .cm-sw:hover { outline: 2px solid var(--oc-accent-color); outline-offset: 1px; }
/* The theme tint ladder sits directly under its base row, with no gap, so the
   five rows read as one block of variations rather than five palettes. */
.color-menu .cm-label + .cm-grid { margin-bottom: 0; }
.color-menu .cm-grid + .cm-grid { margin-top: 2px; }
/* Native colour input, shown as a swatch-sized button rather than a form field. */
.color-menu .cm-native {
  width: 32px; height: 26px; padding: 0; border: 1px solid var(--oc-border-color);
  border-radius: 6px; background: var(--oc-background-color); cursor: pointer;
}
.color-menu .cm-label { font-size: 10.5px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--oc-muted-text-color); }
.color-menu .cm-custom { display: flex; gap: 6px; }
.color-menu .cm-hex {
  flex: 1 1 auto; min-width: 0; box-sizing: border-box; padding: 6px 8px;
  border: 1px solid var(--oc-control-border-color); border-radius: 7px; background: var(--oc-background-color);
  color: var(--oc-text-color); font-size: 12px; font-family: var(--oc-mono-font-family);
}
.color-menu .cm-hex:focus { outline: none; border-color: var(--oc-accent-color); }
.color-menu .cm-apply {
  flex: 0 0 auto; width: auto; padding: 6px 12px; border-radius: 7px; border: 1px solid var(--oc-accent-color);
  background: var(--oc-accent-color); color: #fff; font-size: 12px; font-weight: 600; cursor: pointer;
}

/* Border palette: placement grid + line-style select + color swatches. */
.border-menu { width: 200px; gap: 8px; }
.border-menu .bd-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 2px; }
.border-menu .bd-cell {
  width: 34px; height: 30px; padding: 0; border: 0; border-radius: 7px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; color: var(--oc-icon-color);
}
.border-menu .bd-cell:hover { background: var(--oc-surface-color); color: var(--oc-text-color); }
.border-menu .bd-cell .icon { width: 20px; height: 20px; }
.border-menu .bd-style { width: 100%; box-sizing: border-box; text-align: left; }
.border-menu .bd-swatches { display: flex; flex-wrap: wrap; gap: 6px; }
.border-menu .bd-color {
  width: 20px; height: 20px; padding: 0; border-radius: 5px; border: 1px solid var(--oc-control-border-color);
  cursor: pointer; font-size: 11px; font-weight: 600; color: var(--oc-muted-text-color);
  display: inline-flex; align-items: center; justify-content: center;
}
.border-menu .bd-color.on { outline: 2px solid var(--oc-accent-color); outline-offset: 1px; }

/* Custom tooltip — faster and styled vs. the native title bubble. */
.tooltip {
  position: fixed; z-index: 200; pointer-events: none;
  background: var(--oc-tooltip-background-color, #1f2430); color: var(--oc-tooltip-text-color, #fff);
  padding: 4px 8px; border-radius: 6px; font-size: 11.5px; font-weight: 500;
  line-height: 1.3; white-space: nowrap; box-shadow: var(--oc-popover-shadow);
  opacity: 0; transition: opacity .08s ease;
}
.tooltip.show { opacity: 1; }
.tooltip[hidden] { display: none; }

.sheet-rename {
  width: 90px; padding: 3px 6px; border: 1px solid var(--oc-accent-color); border-radius: 5px;
  background: var(--oc-background-color); color: var(--oc-text-color); font-size: 13px; font-weight: 600; outline: none;
}
.tb-status { color: var(--oc-faint-text-color); font-size: 12px; white-space: nowrap; max-width: 320px; overflow: hidden; text-overflow: ellipsis; }
/* The offer to re-run a job that was stopped for taking too long (`SEC-017`).
   A sibling of the status text, not a child: `.tb-status` is one ellipsised
   line with a `max-width`, so a control inside it is clipped out of reach. */
.keep-waiting { padding: 3px 9px; font-size: 12px; border-radius: 6px; flex: 0 0 auto; }

.formula-bar {
  /* The tint mirror is absolutely positioned against this box, so it has to be
     the offset parent — otherwise the mirror anchors to the page. */
  position: relative;
  display: flex; align-items: center; gap: 10px; height: 40px; padding: 0 12px;
  border-bottom: 1px solid var(--oc-border-color); background: var(--oc-background-color); flex: 0 0 auto;
}
/* Name box (higher specificity than `.formula-bar input` so it stays narrow). */
.formula-bar .cell-ref {
  flex: 0 0 auto; width: 84px; height: 28px; text-align: center; font-family: var(--oc-mono-font-family); font-weight: 600;
  font-size: 12.5px; color: var(--oc-text-color); padding: 0 6px; border: 1px solid var(--oc-control-border-color);
  border-radius: 8px; background: var(--oc-background-color);
}
.formula-bar .cell-ref:focus { outline: none; border-color: var(--oc-accent-color); }
.formula-bar .fx { font-family: Georgia, serif; font-style: italic; font-size: 14px; color: var(--oc-muted-text-color); padding: 0 2px; flex: 0 0 auto; }
.formula-bar input#formula-input {
  flex: 1; height: 28px; padding: 0 10px; border-radius: 8px; border: 1px solid var(--oc-control-border-color);
  background: var(--oc-background-color); color: var(--oc-text-color); font-family: var(--oc-mono-font-family); font-size: 13px;
}
.formula-bar input#formula-input:focus {
  outline: none; border-color: var(--oc-accent-color); box-shadow: 0 0 0 3px var(--oc-accent-ring);
}

.bottom-bar {
  display: flex; align-items: center; gap: 10px; height: 40px; padding: 0 12px; flex: 0 0 auto;
  border-top: 1px solid var(--oc-border-color); background: var(--oc-background-color);
}
/* Sheet tabs live in a grouped pill (like the toolbar clusters). */
.sheet-tabs {
  display: flex; align-items: center; gap: 2px; padding: 2px;
  border-radius: 9px; background: var(--oc-surface-color);
  flex: 0 1 auto; overflow-x: auto; min-width: 0;
}
.sheet-tab {
  height: 26px; padding: 0 12px; border: 0; border-radius: 7px;
  background: transparent; color: var(--oc-muted-text-color); font-size: 12.5px; font-weight: 600;
  cursor: pointer; white-space: nowrap; display: inline-flex; align-items: center; gap: 7px;
}
.sheet-tab::before { content: ""; width: 6px; height: 6px; border-radius: 2px; background: var(--oc-x-tab-color, transparent); flex: 0 0 auto; display: none; }
.sheet-tab.colored::before { display: inline-block; }
.sheet-tab:hover { color: var(--oc-text-color); }
.sheet-tab.active { background: var(--oc-background-color); color: var(--oc-text-color); box-shadow: var(--oc-control-shadow); }
.sheet-tab.active::before { display: inline-block; background: var(--oc-x-tab-color, var(--oc-accent-color)); }
.sheet-tab.dragging { opacity: 0.45; }
/* Floating glass selection summary (Sum / Avg / Count), bottom-right of grid. */
.sel-stats {
  position: absolute; bottom: 16px; right: 24px; z-index: 8; white-space: nowrap;
  display: none; align-items: center; gap: 14px; padding: 8px 15px;
  border-radius: 11px; border: 1px solid var(--oc-control-border-color);
  background: color-mix(in srgb, var(--oc-background-color) 86%, transparent);
  backdrop-filter: saturate(1.5) blur(14px); box-shadow: var(--oc-popover-shadow);
  color: var(--oc-muted-text-color); font-size: 12px;
}
.sel-stats:not(:empty) { display: inline-flex; }
.sel-stats b { color: var(--oc-text-color); font-weight: 600; font-variant-numeric: tabular-nums; }
.bottom-status {
  margin-left: auto; display: inline-flex; align-items: center; gap: 6px;
  font-size: 11.5px; color: var(--oc-faint-text-color); white-space: nowrap;
}
.bottom-status .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--oc-success-color); }
/* Autosave, when it is not running (`SAVE-03`, `docs/83` §5.5). Coloured as a
   warning and not as fine print, for the reason `.share-warning` gives: a
   notice a user's eye slides over is not a notice, and this one says their work
   is no longer being kept. Hidden entirely while autosave works — an indicator
   that is always on screen is one nobody reads. */
.autosave-state {
  display: inline-flex; align-items: center; flex: 0 0 auto;
  padding: 2px 8px; border-radius: 7px;
  border: 1px solid var(--oc-danger-color);
  background: color-mix(in srgb, var(--oc-danger-color) 9%, transparent);
  font-size: 11.5px; color: var(--oc-text-color); white-space: nowrap;
}
.autosave-state[hidden] { display: none; }
/* Zoom, at the far right of the status bar — Excel's and Sheets' position.
   After `.bottom-status`, which carries `margin-left: auto`, so this rides the
   right edge without a second auto margin fighting the first. */
.zoom-widget {
  display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto;
  padding-left: 10px; margin-left: 4px; border-left: 1px solid var(--oc-border-color);
}
.zoom-step {
  width: 20px; height: 20px; padding: 0; border: 0; border-radius: 5px;
  background: transparent; color: var(--oc-muted-text-color);
  font-size: 14px; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.zoom-step:hover:not(:disabled) { background: var(--oc-surface-color); color: var(--oc-text-color); }
.zoom-step:disabled { opacity: .4; cursor: default; }
.zoom-slider { width: 84px; height: 4px; margin: 0; accent-color: var(--oc-accent-color); cursor: pointer; }
/* Tabular numerals, and a width that fits "100%": the readout sits at the edge
   of the window and a percentage that shifts the layout as it changes is the
   one thing a status bar must not do. */
.zoom-level {
  min-width: 42px; height: 20px; padding: 0 4px; border: 0; border-radius: 5px;
  background: transparent; color: var(--oc-muted-text-color);
  font-size: 11.5px; font-variant-numeric: tabular-nums; text-align: right; cursor: pointer;
}
.zoom-level:hover { background: var(--oc-surface-color); color: var(--oc-text-color); }
/* On a narrow window the sheet tabs are what the user needs; the slider is the
   part of this that can go, because − / + / the readout still do all of it. */
@media (max-width: 720px) { .zoom-slider { display: none; } }
/* Tab-color picker inside the sheet context menu. */
.menu-label { font-size: 11px; color: var(--oc-muted-text-color); padding: 2px 10px; text-transform: uppercase; letter-spacing: .04em; }
.swatch-row { display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 10px 8px; max-width: 176px; }
.swatch-row .swatch {
  width: 18px; height: 18px; padding: 0; border-radius: 50%; border: 1px solid rgba(0,0,0,.2); cursor: pointer;
}
.swatch-row .swatch:hover { outline: 2px solid var(--oc-accent-color); outline-offset: 1px; }
.swatch-row .swatch-none {
  background: var(--oc-background-color);
  background-image: linear-gradient(45deg, transparent 45%, var(--oc-danger-color) 45%, var(--oc-danger-color) 55%, transparent 55%);
}
/* Column filter checklist dropdown. */
.filter-menu { min-width: 200px; padding: 6px; }
.filter-list { max-height: 260px; overflow-y: auto; margin: 2px 0; }
.filter-item {
  display: flex; align-items: center; gap: 8px; padding: 4px 8px; border-radius: 5px;
  font-size: 13px; color: var(--oc-text-color); cursor: pointer; white-space: nowrap;
}
.filter-item:hover { background: var(--oc-popover-background-color); }
.filter-item input { accent-color: var(--oc-accent-color); cursor: pointer; }
.filter-all { font-weight: 600; border-bottom: 1px solid var(--oc-border-color); border-radius: 0; }
.filter-foot { display: flex; justify-content: flex-end; gap: 8px; padding: 6px 4px 2px; border-top: 1px solid var(--oc-border-color); margin-top: 4px; }
.filter-foot button { padding: 5px 14px; border-radius: 6px; font-size: 13px; font-weight: 600; cursor: pointer; border: 1px solid var(--oc-border-color); }
.filter-clear { background: var(--oc-background-color); color: var(--oc-muted-text-color); }
.filter-apply { background: var(--oc-accent-color); color: #fff; border-color: var(--oc-accent-color); }
.filter-menu { min-width: 230px; max-width: 320px; }
/* Search box above the checklist. The list can be long; typing is the only
   practical way to reach a value in a column of thousands. */
.filter-search {
  width: 100%; box-sizing: border-box; margin: 4px 0 2px; padding: 5px 8px;
  border: 1px solid var(--oc-border-color); border-radius: 6px; font-size: 13px;
  background: var(--oc-background-color); color: var(--oc-text-color);
}
.filter-search:focus-visible { outline: 2px solid var(--oc-accent-color); outline-offset: -1px; }
.filter-cond {
  display: block; width: 100%; text-align: left; padding: 5px 8px; border: 0;
  border-radius: 5px; background: none; color: var(--oc-text-color); font-size: 13px; cursor: pointer;
}
.filter-cond:hover { background: var(--oc-popover-background-color); }
/* Condition dialog: operator + operand on one line, twice. */
.filter-cond-row { display: flex; gap: 8px; margin: 6px 0; }
.filter-cond-row select { flex: 1 1 auto; min-width: 0; }
.filter-cond-row input { flex: 0 1 120px; min-width: 0; }
.filter-join { display: flex; gap: 16px; margin: 4px 0; font-size: 13px; color: var(--oc-muted-text-color); }
.filter-join label { display: flex; align-items: center; gap: 4px; cursor: pointer; }
/* Data-validation dropdown + editor. */
.dv-menu { max-height: 280px; overflow-y: auto; min-width: 120px; }
.dv-set { min-width: 240px; padding: 8px; }
.dv-input { width: 100%; margin: 4px 0 2px; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--oc-border-color); background: var(--oc-background-color); color: var(--oc-text-color); font-size: 13px; }
.dv-input:focus { outline: none; border-color: var(--oc-accent-color); }
/* Conditional-format form. */
.cf-set { min-width: 288px; padding: 8px; }
.cf-row { display: flex; gap: 6px; margin: 4px 0 8px; }
.cf-op { flex: 1; padding: 7px 8px; border-radius: 8px; border: 1px solid var(--oc-border-color); background: var(--oc-background-color); color: var(--oc-text-color); font-size: 13px; }
.cf-a, .cf-b { width: 70px; padding: 7px 8px; border-radius: 8px; border: 1px solid var(--oc-border-color); background: var(--oc-background-color); color: var(--oc-text-color); font-size: 13px; }
.cf-a:focus, .cf-b:focus { outline: none; border-color: var(--oc-accent-color); }
.swatch-row .swatch.on { outline: 2px solid var(--oc-accent-color); outline-offset: 1px; }
/* Cell comment tooltip + editor. */
.comment-tip {
  position: absolute; z-index: 45; max-width: 260px; padding: 8px 11px; border-radius: 8px;
  background: #fffbe6; color: #52410b; border: 1px solid #f0d98a; box-shadow: var(--oc-elevation-overlay);
  font-size: 13px; line-height: 1.45; white-space: pre-wrap; pointer-events: none;
}
@media (prefers-color-scheme: dark) { .comment-tip { background: #3a340f; color: #f4e7b0; border-color: #6b5c1e; } }
.cmt-set { min-width: 240px; padding: 8px; }
.cmt-input { width: 100%; margin: 4px 0 2px; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--oc-border-color); background: var(--oc-background-color); color: var(--oc-text-color); font: inherit; font-size: 13px; resize: vertical; }
.cmt-input:focus { outline: none; border-color: var(--oc-accent-color); }
/* Name manager. */
.nm-menu { min-width: 260px; max-width: 360px; max-height: 320px; overflow-y: auto; }
.nm-row { display: flex; align-items: stretch; gap: 4px; }
.nm-go { flex: 1; display: flex; flex-direction: column; align-items: flex-start; gap: 1px; padding: 6px 10px; background: none; border: 0; border-radius: 6px; cursor: pointer; text-align: left; }
.nm-go:hover { background: var(--oc-popover-background-color); }
.nm-go b { font-size: 13px; color: var(--oc-text-color); }
.nm-go span { font-size: 12px; color: var(--oc-muted-text-color); font-family: var(--oc-mono-font-family); }
.nm-del { width: 30px; border: 0; background: none; color: var(--oc-muted-text-color); font-size: 18px; cursor: pointer; border-radius: 6px; }
.nm-del:hover { background: var(--oc-danger-ring); color: var(--oc-danger-color); }
/* Formula function autocomplete. */
/* Fixed, not absolute: the same menu is anchored under the in-cell editor
   (inside the grid wrapper) and under the formula bar (outside it), so its
   position is computed in viewport coordinates. */
.ac-menu {
  position: fixed; z-index: 70; min-width: 260px; max-width: 360px;
  background: var(--oc-background-color); border: 1px solid var(--oc-border-color); border-radius: 8px;
  box-shadow: var(--oc-elevation-overlay); padding: 4px; overflow: hidden;
}
/* Argument hint: which parameter of the current call is being typed. */
.sig-tip {
  position: fixed; z-index: 70; max-width: 420px;
  background: var(--oc-popover-background-color); border: 1px solid var(--oc-control-border-color); border-radius: 8px;
  padding: 5px 9px; box-shadow: var(--oc-popover-shadow);
  font-family: ui-monospace, monospace; font-size: 12.5px; color: var(--oc-muted-text-color);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; pointer-events: none;
}
.sig-tip[hidden] { display: none; }
.sig-name { color: var(--oc-text-color); font-weight: 700; }
.sig-arg.active { color: var(--oc-text-color); font-weight: 700; text-decoration: underline; }
.ac-item { display: flex; align-items: baseline; gap: 8px; padding: 5px 9px; border-radius: 5px; cursor: pointer; }
.ac-item.active { background: var(--oc-accent-color); }
.ac-item.active .ac-name, .ac-item.active .ac-sig { color: #fff; }
.ac-name { font-weight: 700; font-size: 13px; color: var(--oc-text-color); font-family: ui-monospace, monospace; }
.ac-sig { font-size: 12px; color: var(--oc-muted-text-color); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Invalid-formula editor outline + status error text. */
/* Rejected-formula outline — on whichever surface the edit came from. */
.inline-edit.invalid, #formula-input.invalid {
  outline: 2px solid var(--oc-danger-color); box-shadow: 0 0 0 2px var(--oc-danger-ring);
}
#tb-status .err, .err { color: #e53935; font-weight: 600; }
.sheet-add {
  width: 26px; height: 26px; padding: 0; display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 7px; background: transparent; color: var(--oc-muted-text-color); cursor: pointer; flex: 0 0 auto;
}
.sheet-add:hover { background: var(--oc-background-color); color: var(--oc-text-color); box-shadow: var(--oc-control-shadow); }

/* The grid + optional right-docked tool panel sit side by side. */
.work-area { flex: 1 1 auto; display: flex; min-height: 0; min-width: 0; }
.grid-wrap { position: relative; flex: 1 1 auto; min-width: 0; overflow: hidden; background: var(--oc-background-color); }

/* Tool side panel (data validation / conditional formatting / notes). */
.side-panel {
  flex: 0 0 316px; min-width: 0; display: flex; flex-direction: column;
  border-left: 1px solid var(--oc-border-color); background: var(--oc-popover-background-color);
}
.side-panel[hidden] { display: none; }
.side-panel-head {
  display: flex; align-items: center; gap: 8px; height: 44px; padding: 0 8px 0 14px;
  border-bottom: 1px solid var(--oc-border-color); flex: 0 0 auto;
}
.side-panel-title { font-size: 13px; font-weight: 600; color: var(--oc-text-color); flex: 1 1 auto; }
.side-panel-close {
  width: 28px; height: 28px; padding: 0; border: 0; border-radius: 7px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; color: var(--oc-icon-color);
}
.side-panel-close:hover { background: var(--oc-surface-color); color: var(--oc-text-color); }
.side-panel-body { flex: 1 1 auto; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 12px; }

/* Shared panel form controls. */
.panel-section-label { font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--oc-muted-text-color); }
.panel-range { font-family: var(--oc-mono-font-family); font-size: 12px; color: var(--oc-text-color); background: var(--oc-surface-color); border: 1px solid var(--oc-border-color); border-radius: 7px; padding: 6px 9px; }
.panel-field, .panel-select {
  width: 100%; box-sizing: border-box; padding: 8px 10px; font-size: 13px;
  border: 1px solid var(--oc-control-border-color); border-radius: 8px; background: var(--oc-background-color); color: var(--oc-text-color);
}
.panel-field:focus, .panel-select:focus { outline: none; border-color: var(--oc-accent-color); }
.panel-hint { margin: 2px 0 6px; color: var(--oc-muted-text-color); font-size: 12px; line-height: 1.45; }
textarea.panel-field { resize: vertical; min-height: 68px; font-family: inherit; }
.panel-swatches { display: flex; flex-wrap: wrap; gap: 7px; }
.panel-swatches .swatch { width: 24px; height: 24px; padding: 0; border-radius: 6px; border: 2px solid transparent; cursor: pointer; }
.panel-swatches .swatch.on { border-color: var(--oc-text-color); }
.panel-actions { display: flex; gap: 8px; margin-top: 2px; }
.panel-actions button { flex: 1 1 auto; height: 34px; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; border: 1px solid var(--oc-control-border-color); }
.panel-btn-primary { background: var(--oc-accent-color); color: #fff; border-color: var(--oc-accent-color); }
.panel-btn-primary:hover { filter: brightness(1.05); }
.panel-btn-ghost { background: var(--oc-background-color); color: var(--oc-text-color); }
.panel-btn-ghost:hover { background: var(--oc-surface-color); }
.panel-hint { font-size: 11.5px; color: var(--oc-faint-text-color); line-height: 1.4; }

/* A refusal, shown beside the field it is about rather than only in the status
   bar — the failure `docs/82` found was a validation message nobody could see
   from where they typed. */
.panel-error {
  font-size: 12px; line-height: 1.45; color: var(--oc-danger-color);
  border: 1px solid var(--oc-danger-color); border-radius: 8px; padding: 7px 9px;
  background: color-mix(in srgb, var(--oc-danger-color) 9%, transparent);
}
.panel-field[aria-invalid="true"] { border-color: var(--oc-danger-color); box-shadow: 0 0 0 3px var(--oc-danger-ring); }

/* The cell a custom conditional-format formula is written for. It is the whole
   difference between `=$D2>100` and `=D2>100`, so it is stated rather than
   left to be worked out. */
.cf-formula { font-family: var(--oc-mono-font-family); }
.cf-anchor {
  font-size: 11.5px; line-height: 1.5; color: var(--oc-muted-text-color);
  border-left: 3px solid var(--oc-accent-color); padding: 2px 0 2px 9px;
}
/* Which of the two things Apply is about to do. Louder than a hint, because a
   silent rewrite of an existing rule is the one outcome nobody can undo by
   looking. */
.cf-editing { color: var(--oc-text-color); font-weight: 600; }
.cf-anchor-cell {
  font-family: var(--oc-mono-font-family); font-weight: 700; font-size: 12.5px;
  color: var(--oc-text-color);
}

.find-bar {
  position: absolute; top: 6px; right: 18px; z-index: 20; display: flex; align-items: center; gap: 6px;
  background: var(--oc-popover-background-color); border: 1px solid var(--oc-border-color); border-radius: 10px; padding: 6px 8px;
  box-shadow: var(--oc-elevation-overlay);
}
.find-bar[hidden] { display: none; }
.find-field {
  height: 28px; padding: 0 8px; border: 1px solid var(--oc-border-color); border-radius: 7px;
  background: var(--oc-background-color); color: var(--oc-text-color); font-size: 13px; width: 130px;
}
.find-count { color: var(--oc-muted-text-color); font-size: 12px; font-family: var(--oc-mono-font-family); min-width: 34px; text-align: center; }
.find-btn {
  width: 28px; height: 28px; padding: 0; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid transparent; border-radius: 7px; background: transparent; color: var(--oc-text-color); cursor: pointer;
}
.find-btn:hover { background: var(--oc-background-color); border-color: var(--oc-border-color); }
.find-action {
  height: 28px; padding: 0 10px; border: 1px solid var(--oc-border-color); border-radius: 7px;
  background: var(--oc-background-color); color: var(--oc-text-color); font-size: 12px; font-weight: 600; cursor: pointer;
}
.find-action:hover { border-color: var(--oc-accent-color); }
.find-case { display: inline-flex; align-items: center; gap: 3px; color: var(--oc-muted-text-color); font-size: 12px; cursor: pointer; }

/* Custom scrollbars — identical on every OS (no native scrollbar UI). */
.scrollbar { position: absolute; z-index: 6; }
.scrollbar.vscroll { top: 24px; right: 0; bottom: 14px; width: 14px; }
.scrollbar.hscroll { left: 46px; bottom: 0; right: 14px; height: 14px; }
.scrollbar .thumb { position: absolute; background: var(--oc-muted-text-color); opacity: 0.35; border-radius: 7px; transition: opacity 0.12s; }
.scrollbar .thumb:hover, .scrollbar .thumb.drag { opacity: 0.62; }
.scrollbar.vscroll .thumb { left: 3px; width: 8px; min-height: 28px; }
.scrollbar.hscroll .thumb { top: 3px; height: 8px; min-width: 28px; }
#grid { display: block; outline: none; }
/* Keyboard focus has to be visible: the grid takes focus and every shortcut
   depends on it having focus, so "which pane am I typing into" must be
   answerable without clicking. Inset so it is not clipped by the wrapper. */
/* Chrome counts a focusable <canvas> as focus-visible even on a mouse click,
   which would ring the whole grid every time you click a cell. The ring is for
   keyboard users, so it is gated on the last input being a key. */
:root[data-kbnav] #grid:focus, :host([data-kbnav]) #grid:focus { outline: 2px solid var(--oc-accent-color); outline-offset: -2px; }
/* Announcer for assistive tech — present in the a11y tree, not on screen. */
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
/* Cell mode (Ready / Enter / Edit / Point), as in Excel's status bar. */
.cell-mode {
  margin-left: 12px; padding: 2px 8px; border-radius: 6px;
  background: var(--oc-surface-color); color: var(--oc-muted-text-color); font-size: 12px; flex: 0 0 auto;
}
/* A <textarea> so a cell can hold a hard line break (Alt+Enter). Sized by
   positionInline(), which grows it past the cell once the value is multi-line —
   hence no resize grip and no scrollbars. */
.inline-edit {
  position: absolute; display: none; box-sizing: border-box;
  border: 2px solid var(--oc-accent-color); padding: 1px 4px; margin: 0;
  font: 13px var(--oc-mono-font-family); background: var(--oc-background-color); color: var(--oc-text-color); outline: none;
  resize: none; overflow: hidden; white-space: pre; line-height: 1.35;
}

/* Named cell-style gallery: each chip previews the style it applies. */
.style-gallery {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 8px; margin: 10px 0; max-height: 320px; overflow-y: auto;
}
.style-chip {
  padding: 10px 8px; border: 1px solid var(--oc-border-color); border-radius: 6px;
  background: var(--oc-background-color); color: var(--oc-text-color); cursor: pointer; font-size: 13px;
  text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.style-chip:hover { outline: 2px solid var(--oc-accent-color); outline-offset: 1px; }
.style-chip:focus-visible { outline: 2px solid var(--oc-accent-color); outline-offset: 1px; }

/* Manage-rules list: swatch, description, then the order/stop/delete controls. */
.cf-rules { display: flex; flex-direction: column; gap: 6px; margin: 10px 0; max-height: 340px; overflow-y: auto; }
.cf-rule-row {
  display: flex; align-items: center; gap: 8px; padding: 6px 8px;
  border: 1px solid var(--oc-border-color); border-radius: 6px; font-size: 13px;
}
.cf-rule-swatch { width: 16px; height: 16px; border-radius: 3px; border: 1px solid var(--oc-border-color); flex: 0 0 auto; }
.cf-rule-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cf-rule-row .oc-btn { padding: 3px 8px; font-size: 12px; }

/* The border button carries the chosen line colour as an underline, so the pick
   is visible without opening the palette. */
#tb-border { position: relative; }
#tb-border::after {
  content: ""; position: absolute; left: 6px; right: 6px; bottom: 4px; height: 2px;
  background: var(--oc-x-border-swatch, currentColor); border-radius: 1px;
}

/* The same mechanism, pointed the other way: these three mirror what the
   *selection* carries rather than what the palette is set to, so the active
   cell's fill, text colour and number format are readable without opening a
   menu. `refreshFormulaBar` sets the variables from `session_cell_format`.

   The 1px inset keeps an empty ("no fill") and a white bar distinguishable —
   without it, "no fill" and "filled white" look identical, which is the same
   class of lie as showing one cell's format for a whole range. */
#tb-fontcolor, #tb-fillcolor, #tb-numfmt { position: relative; }
#tb-fontcolor::after, #tb-fillcolor::after {
  content: ""; position: absolute; left: 6px; right: 6px; bottom: 4px; height: 3px;
  border-radius: 1px; box-shadow: inset 0 0 0 1px var(--oc-border-color);
}
#tb-fontcolor::after { background: var(--oc-x-font-swatch, var(--oc-text-color)); }
#tb-fillcolor::after { background: var(--oc-x-fill-swatch, transparent); }
/* #tb-numfmt has no colour to show, only "this selection has a number format at
   all" — every format without a button of its own (dates, accounting, custom)
   is otherwise invisible on the toolbar. Which format it is, is in the tooltip
   and checked in the menu. */
#tb-numfmt.has-nf::after, #tb-numfmt.tb-mixed::after {
  content: ""; position: absolute; left: 6px; right: 6px; bottom: 4px; height: 2px;
  border-radius: 1px; background: var(--oc-accent-color);
}
/* Indeterminate: the selected cells disagree, or there are more of them than
   the toolbar will read. Hatched rather than blank, because a blank bar reads
   as "none" — a wrong answer where this one is "not one answer". */
#tb-fontcolor.tb-mixed::after, #tb-fillcolor.tb-mixed::after, #tb-numfmt.tb-mixed::after {
  background: repeating-linear-gradient(
    45deg, var(--oc-muted-text-color) 0 2px, transparent 2px 4px);
}
/* A toggle that is neither on nor off across the selection. Dimmer than the
   pressed state and without its background, so "some of these are bold" cannot
   be mistaken for "all of these are bold". */
.toolbar .tb-btn[aria-pressed="mixed"] {
  color: var(--oc-accent-color); background: transparent;
  box-shadow: inset 0 0 0 1px var(--oc-border-color); opacity: .75;
}
/* The applied number format, checked in the menu that applies it — the same
   affordance the font/size comboboxes use for their current value. */
#numfmt-menu button { position: relative; padding-left: 26px; }
#numfmt-menu button.checked::before {
  content: "✓"; position: absolute; left: 9px; top: 50%; transform: translateY(-50%);
  font-size: 11px; color: var(--oc-accent-color); font-family: system-ui, sans-serif;
}

/* One place for the whole reduced-motion contract, rather than a media query per
   animated element. The marching-ants crawl is stopped in JS, since a canvas
   animation is a timer rather than a CSS transition. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}

/* Currency builder inside the custom-format dialog. */
.cf-currency { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 4px 0 10px; }
.cf-currency select { flex: 0 1 auto; }
.cf-redneg { display: flex; align-items: center; gap: 4px; font-size: 13px; color: var(--oc-muted-text-color); }

/* Alt mnemonics: the letter is always wrapped, but only underlined while Alt is
   held, so it does not read as a link the rest of the time. */
.menu-top .mn { text-decoration: none; }
#menubar.show-mnemonics .menu-top .mn { text-decoration: underline; text-underline-offset: 2px; }

/* A protected sheet's tab, and the all-sheets button. */
.sheet-tab.protected::after { content: "\1F512"; font-size: 10px; opacity: 0.7; }
.sheet-all { margin-left: 2px; }
.popmenu .menu-item.on { font-weight: 700; }

.panel-check { display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--oc-muted-text-color); margin: 4px 0; }

/* Chart panel: one block per series, its name and its values reference
   stacked so a long `Sheet1!$B$2:$B$400` is readable in a narrow column. */
.chart-series {
  display: flex; flex-direction: column; gap: 4px;
  padding: 6px; border: 1px solid var(--oc-control-border-color); border-radius: 8px;
  background: var(--oc-surface-color);
}
.chart-series-head { display: flex; gap: 6px; align-items: center; }
.chart-series-head .panel-field { flex: 1 1 auto; }

/* Pivot fields panel: a source list you drag from, four areas you drag into.
   The areas keep a minimum height whether or not they hold anything, so a
   drop target exists before there is a chip to aim beside. */
.pivot-fields { display: flex; flex-wrap: wrap; gap: 5px; }
.pivot-zone {
  display: flex; flex-direction: column; gap: 5px;
  min-height: 34px; padding: 6px; box-sizing: border-box;
  border: 1px dashed var(--oc-control-border-color); border-radius: 8px; background: var(--oc-surface-color);
}
.pivot-zone.over { border-color: var(--oc-accent-color); border-style: solid; background: var(--oc-background-color); }
.pivot-zone-empty { font-size: 12px; color: var(--oc-muted-text-color); padding: 2px 3px; }
.pivot-chip {
  display: flex; align-items: center; gap: 4px;
  font-size: 12.5px; color: var(--oc-text-color); background: var(--oc-background-color);
  border: 1px solid var(--oc-control-border-color); border-radius: 7px;
  padding: 4px 7px; cursor: grab; user-select: none;
}
.pivot-chip:active { cursor: grabbing; }
.pivot-chip-src.in-use { opacity: .45; }
.pivot-chip-set { justify-content: space-between; }
.pivot-chip-name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pivot-mini {
  flex: 0 0 auto; min-width: 22px; height: 22px; padding: 0 5px;
  font-size: 12px; line-height: 1; cursor: pointer;
  border: 1px solid var(--oc-control-border-color); border-radius: 6px;
  background: var(--oc-surface-color); color: var(--oc-muted-text-color);
}
.pivot-mini:hover { color: var(--oc-text-color); border-color: var(--oc-accent-color); }
.pivot-mini.on { background: var(--oc-accent-color); border-color: var(--oc-accent-color); color: #fff; }
.pivot-mini-wide { max-width: 108px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pivot-remove:hover { color: var(--oc-danger-color, #c0392b); border-color: currentColor; }
.pivot-agg {
  flex: 0 0 auto; max-width: 104px; height: 22px; font-size: 12px;
  border: 1px solid var(--oc-control-border-color); border-radius: 6px;
  background: var(--oc-surface-color); color: var(--oc-text-color);
}
/* The page-filter checklist sits inline under its chip rather than floating:
   the panel is already a narrow column, and a popup over it would cover the
   thing being filtered. */
.pivot-items {
  max-height: 190px; overflow-y: auto; padding: 4px 6px;
  border: 1px solid var(--oc-control-border-color); border-radius: 7px; background: var(--oc-background-color);
}

/* Reference tinting: a mirror element renders the formula text with coloured
   reference tokens, exactly behind the editing surface. The surface keeps its
   caret and selection but draws its own glyphs transparent, so what you see is
   the mirror and what you interact with is the real control. */
.ref-mirror {
  position: absolute; margin: 0; overflow: hidden; pointer-events: none;
  color: var(--oc-text-color); background: transparent; z-index: 0;
  border-style: solid; border-color: transparent;
}
/* Specificity has to clear `.formula-bar input#formula-input`, which is where
   the bar's own colour comes from — a plain `#formula-input.tinted` loses. */
.formula-bar input#formula-input.tinted, textarea.inline-edit.tinted {
  z-index: 1; background: transparent;
  /* Transparent glyphs, visible caret — `caret-color` is what keeps the caret
     from disappearing along with the text. */
  color: transparent; caret-color: var(--oc-text-color);
}
/* The z-index above needs a positioned box. The formula bar's input is static,
   so it is given one here; the in-cell editor is **already** `position:
   absolute` and must keep it.
   
   This rule used to set `position: relative` for both, and on the in-cell
   editor that was a real defect rather than a redundancy. `relative` leaves the
   element *in flow*: the textarea went back to being laid out after the
   full-height canvas, with its `top` merely shifting it visually, so the
   wrapper's scroll box grew by the height of the offset. The browser then
   scrolled that hidden-overflow container to reveal the focused editor, and
   since the canvas draws from `state.scrollY` rather than from the DOM, the two
   came apart — column headers off screen, a band of rows drawn, the editor
   floating near the bottom of the window.
   
   It only appeared once `.tinted` was added, which happens the moment a
   reference is recognised — so it read as "typing =2*A2 broke the grid". */
.formula-bar input#formula-input.tinted { position: relative; }
.formula-bar input#formula-input.tinted::selection,
textarea.inline-edit.tinted::selection {
  /* Selection has to stay visible even though the glyphs are transparent. */
  color: transparent; background: var(--oc-selection-color, rgba(47,109,246,.28));
}

/* Format Cells dialog: tab strip over stacked pages. */
.fc-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--oc-border-color); margin-bottom: 10px; }
.fc-tab {
  padding: 6px 12px; border: 0; background: none; color: var(--oc-muted-text-color);
  font-size: 13px; font-weight: 600; cursor: pointer; border-bottom: 2px solid transparent;
}
.fc-tab.on { color: var(--oc-text-color); border-bottom-color: var(--oc-accent-color); }
.fc-page { min-height: 180px; }
.fc-row { display: flex; flex-wrap: wrap; gap: 12px; margin: 6px 0; }
.fc-check { display: flex; align-items: center; gap: 5px; font-size: 13px; color: var(--oc-text-color); }
.fc-borders { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin: 6px 0; }
.cf-preset.on { outline: 2px solid var(--oc-accent-color); outline-offset: 1px; }

/* Name box + its defined-names caret. */
.name-box { position: relative; display: inline-flex; align-items: center; flex: 0 0 auto; }
.name-caret {
  position: absolute; right: 2px; width: 18px; height: 22px; padding: 0; border: 0;
  background: none; color: var(--oc-icon-color); cursor: pointer; display: flex;
  align-items: center; justify-content: center;
}
.name-caret:hover { color: var(--oc-text-color); }
.formula-bar .name-box .cell-ref { padding-right: 20px; }

/* `fx` is a button now, not decoration; plus the expand caret. */
button.fx {
  border: 0; background: none; color: var(--oc-muted-text-color); font-style: italic;
  font-size: 13px; cursor: pointer; padding: 2px 4px; border-radius: 5px;
}
button.fx:hover { color: var(--oc-text-color); background: var(--oc-popover-background-color); }
.fx-expand {
  border: 0; background: none; color: var(--oc-icon-color); cursor: pointer; padding: 2px;
  display: flex; align-items: center;
}
.fx-expand[aria-expanded="true"] svg { transform: rotate(180deg); }
/* Expanded formula bar: a multi-line box for formulas that do not fit on one. */
.formula-bar.expanded { height: auto; align-items: flex-start; padding-top: 6px; padding-bottom: 6px; }
.formula-bar.expanded input#formula-input { height: 84px; }
.fn-list { max-height: 260px; overflow-y: auto; margin: 8px 0; display: flex; flex-direction: column; gap: 2px; }
.fn-row {
  text-align: left; padding: 5px 8px; border: 0; border-radius: 5px; background: none;
  color: var(--oc-text-color); font-size: 13px; cursor: pointer;
}
.fn-row:hover { background: var(--oc-popover-background-color); }
.fn-row.on { background: var(--oc-accent-color); color: #fff; }

/* Fill-options button, parked at the corner of the last fill. */
.fill-options {
  position: fixed; width: 18px; height: 18px; padding: 0; z-index: 30;
  border: 1px solid var(--oc-border-color); border-radius: 4px; background: var(--oc-background-color);
  color: var(--oc-muted-text-color); font-size: 11px; line-height: 1; cursor: pointer;
}
.fill-options:hover { color: var(--oc-text-color); border-color: var(--oc-accent-color); }

/* Comment threads (UX-P12). The thread scrolls on its own so a long
   conversation never pushes the reply box off the panel — the box you type
   into has to stay put, or replying to a busy thread means scrolling first. */
.cmt-thread {
  max-height: 260px;
  overflow-y: auto;
  margin: 4px 0 8px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.cmt-entry {
  border-left: 2px solid var(--oc-control-border-color);
  padding: 2px 0 2px 8px;
}
/* Replies indent under the opening remark, which is what makes a thread read
   as a thread rather than as a list of unrelated notes. */
.cmt-entry:not(.cmt-root) { margin-left: 12px; }
.cmt-root { border-left-color: var(--oc-accent-color); }
.cmt-head {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin-bottom: 2px;
}
.cmt-who { font-size: 12px; font-weight: 600; }
.cmt-when { font-size: 11px; color: var(--oc-faint-text-color); }
.cmt-text { font-size: 12.5px; line-height: 1.45; white-space: pre-wrap; word-break: break-word; }
.cmt-resolved {
  align-self: flex-start;
  font-size: 11px;
  font-weight: 600;
  color: var(--oc-muted-text-color);
  border: 1px solid var(--oc-control-border-color);
  border-radius: 999px;
  padding: 1px 8px;
}
.cmt-author { margin-bottom: 6px; }

/* Table banding (FC-08). Faint by design: a band is a reading aid, and one
   strong enough to compete with a cell's own fill or a conditional format
   makes the user's formatting harder to see rather than easier. */
:root, :host {
  --oc-table-header-color: rgba(47, 109, 246, 0.16);
  --oc-table-band-color: rgba(127, 140, 170, 0.09);
}
:root[data-theme="dark"], :host([data-theme="dark"]), .theme-dark {
  --oc-table-header-color: rgba(96, 145, 255, 0.20);
  --oc-table-band-color: rgba(148, 163, 184, 0.10);
}

/* The table panel: name, banding checkboxes and the style swatches. */
.oc-table-checks { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 12px; margin-bottom: 12px; }
.oc-check { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--oc-text-color); }
.oc-table-styles { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
/* Each swatch is a four-row miniature of the table it would produce: header,
   plain row, banded row, plain row. */
.oc-style-swatch {
  display: grid; grid-template-rows: repeat(4, 6px);
  width: 46px; padding: 2px; gap: 0;
  border: 1px solid var(--oc-border-color); border-radius: 4px;
  background: var(--oc-surface-color); cursor: pointer;
  /* Chrome's UA stylesheet gives every `button` `align-items: flex-start`,
     which start-aligns each band to its own content height — and the bands
     have no content. Four 6px rows were reserved and nothing was drawn in
     them, so every swatch was a blank box and the picker offered eight
     identical options. The colours had been arriving correctly all along. */
  align-items: stretch;
}
/* `border-box` so the header band's 2px rule sits inside its 6px row rather
   than pushing the stack past the reserved height. */
.oc-style-swatch span { display: block; box-sizing: border-box; min-height: 0; }
.oc-style-swatch.sel { outline: 2px solid var(--oc-accent-color); outline-offset: 1px; }

/* The create-table modal (Ctrl+T). Two columns — label beside its control —
   rather than a stack of full-width fields: this is a four-line question, and
   at the modal's own density it should read as one, not as a web form. The
   error cell sits in the control column under the range box, so a refused
   range is answered where it was typed. */
.oc-table-form {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: 8px 10px; align-items: center; margin: 0 0 14px;
}
/* **A conditionally hidden element is never a direct child of this grid.**
   `hidden` is `display: none`, which takes the element out of the flow
   entirely — so it occupies no cell and every later child shifts one place.
   That is what put the checkbox in the label column, swelled the `auto` track
   to the full width, collapsed `minmax(0, 1fr)` to `0px`, and left the range
   input an 18px pill against the right edge with "Style" clipped off it. The
   form looked broken in its *default* state, because the error is hidden
   until the range is refused.

   The error goes inside a cell instead, and the cell is `flex`, not `grid`:
   flex has no cells to fall out of, so a `display: none` child shortens the
   stack and shifts nothing. Making this a grid reproduces the trap one level
   down — which it did, on the first attempt at this fix. */
.oc-form-cell { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.oc-form-label { font-size: 12px; color: var(--oc-muted-text-color); white-space: nowrap; }
.oc-form-label.top { align-self: start; padding-top: 3px; }
.oc-form-field {
  width: 100%; box-sizing: border-box; padding: 5px 8px; font-size: 13px;
  border: 1px solid var(--oc-control-border-color); border-radius: 6px;
  background: var(--oc-background-color); color: var(--oc-text-color);
}
.oc-form-field:focus { outline: none; border-color: var(--oc-accent-color); }
.oc-form-field.mono { font-family: var(--oc-mono-font-family); }
.oc-form-field.bad { border-color: var(--oc-danger-color); box-shadow: 0 0 0 2px var(--oc-danger-ring); }
.oc-form-error { font-size: 12px; line-height: 1.35; color: var(--oc-danger-color); }
.oc-form-error[hidden] { display: none; }
/* The swatch row is the last thing in the grid, so its own bottom margin would
   double the gap before the buttons. */
.oc-table-form .oc-table-styles { margin-bottom: 0; }
.oc-table-form .oc-check { cursor: pointer; }

/* Column width / row height (UX-DLG-02) — the same two-column form as
   `.oc-table-form`, because it is the same kind of question asked of the same
   modal, and one dialog idiom is the point. The field is a number's width
   rather than the column's: a box sized for a paragraph invites a paragraph.
   The unit sits beside it instead of inside the label, so the answer and what
   it is measured in read as one line. */
.oc-size-form {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: 8px 10px; align-items: center; margin: 0 0 14px;
}
/* The label lines up with the *field*, not with the middle of the cell: the
   cell grows downwards when a refusal appears under the box, and centring
   slid "Width" half an error message down the dialog. */
.oc-size-form .oc-form-label { align-self: start; padding-top: 7px; }
.oc-size-line { display: flex; align-items: baseline; gap: 6px; }
.oc-size-form .oc-size-field { width: 92px; flex: 0 0 auto; text-align: right; }
.oc-size-unit { font-size: 12px; color: var(--oc-muted-text-color); }

/* One row per table column in the totals picker: label, then its function. */
.oc-totals-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
  gap: 6px 8px; align-items: center; margin-bottom: 12px;
}
.oc-totals-col {
  font-size: 12px; color: var(--oc-muted-text-color);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.oc-totals-grid .panel-select { padding: 5px 8px; font-size: 12px; }

/* The data-validation input hint, pinned under its cell. */
.dv-prompt {
  position: fixed; z-index: 40; max-width: 260px;
  display: flex; flex-direction: column; gap: 2px;
  padding: 7px 10px; font-size: 12px; line-height: 1.4;
  color: var(--oc-text-color); background: var(--oc-surface-color);
  border: 1px solid var(--oc-control-border-color); border-radius: 8px;
  box-shadow: var(--oc-elevation-overlay);
  pointer-events: none;   /* a hint must never intercept a click on the grid */
}
.dv-prompt strong { font-size: 12px; }

/* The scope choice in a column filter (COL-32, docs/71).
   Sits above the footer so it is read before Apply is pressed rather than
   discovered afterwards — the two do very different things and only one of
   them is visible to everybody else in the document. */
.filter-scope {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px 2px;
  font-size: 12px;
  color: var(--oc-text-color);
  cursor: pointer;
  user-select: none;
}
.filter-scope input {
  margin: 0;
  accent-color: var(--oc-accent-color);
}
.filter-scope + .panel-hint {
  padding: 0 10px 6px 28px;
  margin: 0;
}

/* Gridlines, and honouring somebody who has asked for more contrast.
 *
 * The default was `#f0f1f4`, which is **1.13:1** against the canvas — lighter
 * than both Google Sheets (1.32:1) and Excel (~1.45:1), and far under the 3:1
 * WCAG 2.1 asks for a graphical object you need in order to understand the
 * content. A spreadsheet's grid is exactly that: it is what tells you which
 * cell you are looking at. Reported by somebody running the stack and finding
 * the grid hard to see, which is the only way this was ever going to surface —
 * no test asserts on how visible a hairline is.
 *
 * The default is now 1.44:1, in line with the category. It is deliberately not
 * 3:1: a hairline at 3:1 reads as a heavy table border and fights the data for
 * attention, which is why no spreadsheet ships one.
 *
 * So the accessible answer is not a darker default for everybody — it is
 * honouring the person who has said they want more. `prefers-contrast: more`
 * is that request, and here it clears 3:1 in both schemes.
 */
@media (prefers-contrast: more) {
  :root {
    --oc-gridline-color: #8b94a1;  /* 3.07:1 on #ffffff */
    --oc-border-color: #9aa2ae;
  }
  :root[data-theme="dark"] {
    --oc-gridline-color: #5b6675;  /* 3.22:1 on #0f1216 */
    --oc-border-color: #6b7686;
  }
}
@media (prefers-contrast: more) and (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --oc-gridline-color: #5b6675;
    --oc-border-color: #6b7686;
  }
}

/* ── Narrow screens ──────────────────────────────────────────────────────────
   The editor had no width breakpoint of any kind — only preference queries for
   dark, reduced motion and contrast. `.menubar` and `.toolbar` are unwrapped
   flex rows, so on a phone they simply ran past the edge and widened the whole
   document to 779px: the page slid sideways, and half the toolbar could only be
   reached by dragging the entire editor across (`UX-EDIT-01`).

   They scroll **within themselves** rather than wrapping. Wrapping would change
   the editor's height, and every pixel of that comes out of the grid — the one
   part of a spreadsheet nobody wants smaller. Excel's own answer on a narrow
   window is to collapse groups and then scroll, which is what the existing
   `.tb-collapsed` mechanism already starts; this is the step after it runs out
   of groups to fold.

   The scrollbar is hidden because these are chrome, not content: a visible
   track would eat 8-15px of a 48px bar on the platforms that reserve space for
   one. Touch and trackpad scrolling still work, and so do the keyboard paths —
   every command here is also on the menu bar or a shortcut. */
.menubar,
.toolbar {
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.menubar::-webkit-scrollbar,
.toolbar::-webkit-scrollbar { display: none; }

/* A flex child's default `min-width: auto` is what let these rows push the
   document wide in the first place; without this the overflow above never
   engages. The buttons keep their size instead of squashing into illegibility. */
.menubar > *,
.toolbar > * { flex: 0 0 auto; }
.toolbar { min-width: 0; }

/* ── Document properties ─────────────────────────────────────────────────────
   A form and a fact list, deliberately different in weight: the top half is
   yours to write and the bottom half is what the file says about itself, and a
   reader has to be able to tell which is which without reading the labels. */
.oc-props { display: grid; gap: 10px; margin-bottom: 18px; }
.oc-props-row { display: grid; grid-template-columns: 130px 1fr; align-items: center; gap: 12px; }
.oc-props-label { color: var(--oc-muted-text-color); font-size: 13px; }
.oc-props .oc-input { width: 100%; }
/* The file's own account, set apart by a rule rather than by a heading: it is a
   different kind of thing, not a different section. */
.oc-props-facts {
  display: grid; gap: 8px; padding-top: 14px; margin-bottom: 18px;
  border-top: 1px solid var(--oc-border-color);
}
.oc-props-fact { font-size: 13px; color: var(--oc-text-color); font-variant-numeric: tabular-nums; }

/* --- Chrome overflow: the last stage of the collapse ----------------------- */
/* Both bars fold their trailing end into a "⋯" rather than letting the
   viewport slice it. Neither bar may ever scroll or wrap — that rule predates
   this and is the reason the collapse exists at all; what changed is that
   running out of things to fold is now handled instead of ignored. */
/* Neither bar wraps. The menubar may clip — its drops live in the overlay host
   — but the toolbar must not: the group flyouts are its own children, and
   `overflow: hidden` here renders them as slivers inside the toolbar band
   instead of as panels below it. The reflow is what keeps it from overflowing;
   clipping would only hide the evidence that it had. */
.toolbar { flex-wrap: nowrap; }
.menubar { flex-wrap: nowrap; overflow: hidden; }
/* `hidden` is an attribute, and a bare `display:` rule silently outranks it —
   which is how the overflow flyout first shipped: laid out inside the toolbar,
   adding its own width to the very measurement that decides whether anything
   needs to move. The `:not([hidden])` guard is the pattern .tb-flyout already
   uses two hundred lines up, for the same reason. */
.tb-more-flyout:not([hidden]) {
  /* A column, like every other flyout. Laid out as a row first, which put three
     full-width labelled buttons side by side and ran "Underline (Ctrl+U)" off
     the panel — the labelled-list rule two hundred lines up exists precisely
     because a collapsed group is a menu, and that applies just as much when the
     thing collapsing is the rest of the toolbar. */
  display: flex; flex-direction: column; align-items: stretch; gap: 3px;
  padding: 5px; min-width: 190px; max-width: min(92vw, 320px);
}
/* The groups that moved in keep their own controls on one line, wrapping rather
   than overflowing when a group is wider than the panel. */
.tb-more-flyout .tb-group { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; }
.menu-more { font-size: 18px; line-height: 1; letter-spacing: 0; }
#menu-more-drop button { display: block; width: 100%; text-align: left; }
/* A collapsed group carries its own visible name ("Color ▾"), and the flyout's
   injected label repeats it — "Color ▾ Color". The injected label exists for
   icon-only buttons, which these are not. */
.tb-more-flyout .tb-collapsed::after { content: none; }
.tb-more-flyout .tb-collapsed { justify-content: flex-start; padding: 0 9px; }

/* The grid handles its own gestures, so the browser must not also try. Without
   this a pan competes with the page's own panning and a pinch triggers the
   viewport zoom instead of the sheet's. */
#grid { touch-action: none; }

/* The operating system is drawing the menu. The bar's nodes stay in the
   document — runCommand dispatches by clicking them, and the drops live in the
   overlay host anyway — but the strip itself gives its height to the sheet,
   which is the only reason hiding it is an improvement rather than a loss. */
.oc-chrome-native #menubar { display: none; }

/* --- Column stats ---------------------------------------------------------- */
/* Dense on purpose: this is a panel somebody reads at a glance while clicking
   through columns, not a report. Label left, figure right, aligned so the
   figures form a column you can scan down. */
.stats-range { font: 600 12px/1.4 var(--oc-mono-font-family); color: var(--oc-faint-text-color); margin: 0 0 8px; }
.stats-head {
  font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--oc-faint-text-color); margin: 10px 0 4px;
}
.stats-rows { display: flex; flex-direction: column; gap: 1px; }
.stats-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; padding: 2px 0; font-size: 12.5px; }
.stats-label { color: var(--oc-text-color); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stats-value { font-family: var(--oc-mono-font-family); font-variant-numeric: tabular-nums; flex: 0 0 auto; }
/* Numbers stored as text is a finding, not a statistic — it is the answer to
   "why doesn't my SUM add up", so it does not read like the rows around it. */
.stats-row.warn .stats-label, .stats-row.warn .stats-value { color: var(--oc-warn-text-color, #a9601a); font-weight: 600; }

/* --- Coarse pointers ------------------------------------------------------- */
/*
 * Measured on a phone (390x844) and a tablet (820x1180) with touch, before any
 * of this existed: **not one** of the editor's 27 pointer targets reached 44px,
 * the iOS minimum (Android asks 48). Eight were under 24px, the floor `docs/68`
 * already enforces elsewhere in this product:
 *
 *     fx-insert 16x16 | fx-expand 17x17 | name-box-list 18x22
 *     zoom-out 20x20  | zoom-in 20x20   | zoom-level 42x20
 *     hdr-collapse 26x22 | tb-brand 65x17 | zoom-slider 84x4 (tablet)
 *
 * `(pointer: coarse)` rather than a width breakpoint, because the question is
 * what is doing the pointing and not how wide the window is: a 390px *window*
 * on a desktop still has a mouse, and a tablet in landscape is 1180px wide and
 * still has a finger.
 *
 * **This must not take height from the grid, and it does not.** UX-EDIT-01
 * settled that argument for the toolbar — every pixel of chrome comes out of
 * the sheet — and it holds here. So the growth is spent where the room already
 * exists: the header is 52px and its buttons were 32, the toolbar is 48px and
 * its buttons were 30, the formula bar is 40px and its buttons were 16, and
 * every menu, submenu, context menu and overflow panel floats over the grid and
 * costs it nothing whatever. `#grid` is the same height before and after.
 *
 * **Two places are deliberately left alone**, because both need a decision
 * rather than a rule:
 *
 *   - The menu bar is 30px. Its buttons fill it, so 44px means taking 14px from
 *     the sheet, and 30px is awkward rather than unusable.
 *   - The status bar cannot do it at all. Its contents already do not fit at
 *     390px — `#sheet-tabs` is squeezed to **4px** there and the tabs draw over
 *     the mode label — so widening the zoom controls to 44px pushed the
 *     document to 439px and the page began to slide sideways. Growing them is
 *     the easy half of a bar that has to be laid out again.
 */
@media (pointer: coarse) {
  /* Menu rows — the ones that matter most, and the ones that cost nothing.
     Seventeen 33px rows in the Format menu alone, and a mis-tap there does not
     merely navigate: "Clear formatting" and "Merge cells" are two apart. With
     the toolbar folded into `⋯` on a phone, these panels are where nearly every
     command actually lives. */
  .menu-drop button,
  .menu-sub button,
  .ctx-menu button,
  .tb-more-flyout button { min-height: 44px; }

  /* Header, 52px tall; its buttons were 32. */
  .tb-icon { width: 44px; height: 44px; }
  .tb-brand { display: inline-flex; align-items: center; min-height: 44px; }

  /* Toolbar, 48px tall; its buttons were 30. Widening them cannot push the
     page sideways: `.toolbar` is its own overflow-x scroller and `reflowToolbar`
     folds whatever stops fitting into `⋯`. */
  .toolbar .tb-btn, .tb-file { width: 44px; height: 44px; }

  /* The menu bar's 30px is all there is without spending the grid's height, so
     this only lifts the collapse toggle over the 24px floor. */
  .menu-collapse { width: 30px; height: 30px; }

  /* Formula bar, 40px tall, holding the three smallest controls in the editor.
     `#formula-input` is `flex: 1`, so it gives up exactly what they take and
     the bar's own height is untouched. */
  .formula-bar .fx,
  button.fx { min-width: 40px; min-height: 40px; display: inline-flex; align-items: center; justify-content: center; padding: 0; }
  .fx-expand { min-width: 40px; min-height: 40px; justify-content: center; padding: 0; }
  .name-caret { width: 26px; height: 34px; }
  .formula-bar .name-box .cell-ref { padding-right: 26px; }
  .formula-bar .cell-ref { height: 34px; }
  /* `flex: 1` is not enough to give the width back. A flex item's default
     `min-width: auto` floors it at its own intrinsic minimum, so the 47px the
     three buttons above take had nowhere to come from and pushed the document
     to 426px on a 390px screen — the whole page then slides sideways, which is
     precisely the failure UX-EDIT-01 fixed for the toolbar. The formula bar is
     not a scroller and should not become one; the input is the one thing in it
     that can afford to be narrower. */
  .formula-bar input#formula-input { height: 34px; min-width: 0; }
}
