/* Sheets' own chrome, on top of rastrillo's tokens.
 *
 * Every colour here is a token, never a literal — a literal does not
 * follow the person's light/dark choice, and cell fills resolve against
 * the theme, so one hard-coded grey stays wrong in one of the two
 * schemes forever.
 *
 * Token names are checked mechanically by
 * internal/guardstest/csstokens_test.go. Five invented ones shipped
 * here first (--rst-border, --rst-text-mut, --rst-warning,
 * --rst-font-mono, --rst-shell-bar-h): CSS resolves an undefined custom
 * property to its fallback in silence, so the page looked plausible and
 * was simply not themed. The real names are --rst-line,
 * --rst-text-muted, --rst-tone-warning-fg and --rst-font.
 */

/* The grid surface fills what is left of the viewport under the bar.
 *
 * The shell does that part now: the grid page renders
 * <div estilo-shell="fill">, and estilo's chrome.css makes that shell
 * exactly the viewport, holds the bar at its 52px and gives the grid's
 * <main> the rest (flex: 1, min-block-size: 0). This rule used to be
 * [rst-shell-topbar]:has([data-sheet-surface]) { height: 100dvh } here,
 * keyed on rastrillo's wrapper, and before that a guessed topbar height
 * that made a 771px document in a 720px viewport. What is left is the
 * surface's own column: the chrome strips and the grid. */
[data-sheet-surface] {
  display: flex;
  flex-direction: column;
}

/* ── The chrome ──────────────────────────────────────────────────────
 *
 * Three strips around the grid, and a budget: the whole lot is about
 * 7rem above the grid and 2rem below, against Google's ~9.5rem, because
 * the document title sits in the app's own top bar rather than in a row
 * of its own.
 *
 * Row B — the menu bar and the toolbar, sharing one row. Six short
 * words are about 300px, so a row holding only the menus wastes
 * two-thirds of itself. They wrap onto separate rows when they no
 * longer fit, which is also what keeps this inside a 320px viewport
 * with no horizontal scroll. */
[data-chrome] {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--rst-sp-1) var(--rst-sp-3);
  padding: 0 var(--rst-sp-3);
  border-bottom: 1px solid var(--rst-line);
  background: var(--rst-surface);
  color: var(--rst-text);
}

/* The menu bar is estilo's ([estilo-menubar], [estilo-menu], estilo
 * menu.css): square titles in the page flow, 9px floating panels, inset
 * rows and a shortcut column. What was here — rastrillo dropdowns
 * repositioned with an anchor-positioning override, 6px rounded titles
 * with no hover, a 17rem panel and inline accordion submenus — went with
 * it. Only what is Sheets' own is left below. */

/* An item that needs commands.js is rendered hidden until it can run.
 * estilo's rows are display: flex, which is an author rule and beats the
 * user agent's [hidden] { display: none } however weak its selector
 * looks, so hidden is restated. */
[data-menubar] [estilo-menu] > [hidden] {
  display: none;
}

/* The Format menu's two resize forms sit in the panel as rows: the same
 * inline padding as estilo's items, so their labels line up with the
 * item text above and below. */
[data-menubar] [estilo-menu] > [data-size-form] {
  padding: var(--rst-sp-1) var(--estilo-item-padding-inline, 0.65rem);
}

/* ── The keyboard shortcuts dialog ──────────────────────────────────
 *
 * Opened from the account menu's Help. A native modal <dialog>, so it is
 * in the top layer above the bar; a float, so it takes estilo's float
 * radius and shadow. */
[data-shortcuts] {
  inline-size: min(26rem, calc(100vw - 2rem));
  padding: var(--rst-sp-4);
  border: 1px solid var(--rst-line);
  border-radius: var(--estilo-float-radius, 9px);
  background: var(--rst-surface);
  color: var(--rst-text);
  box-shadow: var(--rst-shadow-pop);
}

[data-shortcuts]::backdrop {
  background: color-mix(in oklab, var(--rst-text) 30%, transparent);
}

[data-shortcuts] h2 {
  margin: 0 0 var(--rst-sp-3);
  font-size: 1.125rem;
}

[data-shortcuts] dl {
  align-items: baseline;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--rst-sp-1) var(--rst-sp-4);
  margin: 0;
}

[data-shortcuts] dt {
  text-align: end;
}

[data-shortcuts] dt kbd {
  font-family: var(--rst-font);
  font-size: var(--rst-fs-sm);
  font-variant-numeric: tabular-nums;
}

[data-shortcuts] dd {
  color: var(--rst-text-muted);
  font-size: var(--rst-fs-sm);
  margin: 0;
}

[data-shortcuts-foot] {
  display: flex;
  justify-content: flex-end;
  margin-block-start: var(--rst-sp-4);
}

/* ── The toolbar ─────────────────────────────────────────────────────
 *
 * `rst-btn="compact"` is not a rastrillo variant — the word appears in
 * tokens.css only inside comments — so a toolbar built on it would be
 * silently unstyled. These buttons are ours.
 *
 * 1.75rem square, clear of the 24px minimum target, and the pressed
 * state is a box-shadow ring rather than a border: a border costs
 * layout, and eleven of them would widen the strip by 22px against a
 * hard 320px reflow budget. A full ring rather than an underline,
 * because a one-sided inset shadow follows the border radius round the
 * corners and reads as a tab stem. */
[data-toolbar] {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: var(--rst-sp-1);
  padding: var(--rst-sp-1) 0;
}

/* Square (the suite's square rule, spec §11 and Paul's §13 answer 4):
 * these buttons are borderless, so their hover and pressed fill is an
 * in-flow highlight, not a bordered control. They were 6px. */
[data-tool] {
  align-items: center;
  background: transparent;
  border: 0;
  border-radius: 0;
  color: var(--rst-text-muted);
  cursor: pointer;
  display: inline-flex;
  font: inherit;
  font-size: var(--rst-fs-sm);
  gap: 0.1rem;
  justify-content: center;
  min-block-size: 1.75rem;
  min-inline-size: 1.75rem;
  padding: 0 var(--rst-sp-1);
  /* [rst-tip]::after is absolutely positioned and rastrillo does not
   * position the host, so the tooltip would anchor to the page. */
  position: relative;
}

[data-tool]:hover {
  background: var(--rst-surface-2);
  color: var(--rst-text);
}

[data-tool][aria-pressed="true"] {
  background: var(--rst-accent-soft);
  box-shadow: inset 0 0 0 1px currentColor;
  color: var(--rst-accent);
}

[data-tool]:focus-visible {
  outline: 2px solid var(--rst-accent);
  outline-offset: 2px;
}

[data-tool] svg {
  block-size: 1rem;
  fill: none;
  inline-size: 1rem;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-width: 1.5;
}

[data-tool-rule] {
  align-self: stretch;
  border-inline-start: 1px solid var(--rst-line);
  margin: var(--rst-sp-1) var(--rst-sp-1);
}

[data-tool-menu] > summary {
  padding: 0;
}

[data-tool-menu] > summary::-webkit-details-marker {
  display: none;
}

/* The same pair as the menu bar's, for the same reason and with the
 * same two branches. A toolbar's colour picker opens under its own
 * button, not off the right edge of the window. */
[data-tool-menu] [rst-dropdown-menu] {
  inset-inline-start: 0;
  inset-inline-end: auto;
}

@supports (position-area: block-end) and (position-try-fallbacks: flip-block) {
  [data-tool-menu] [rst-dropdown-menu] {
    inset: auto;
    position-area: block-end span-inline-end;
  }
}

/* A picker's tooltip names the button; once its panel is open it only
 * covers the panel's first row ("No fill"), so it steps aside. */
[data-tool-menu][open] > [rst-tip]::after {
  opacity: 0;
}

[data-tool] [rst-caret] svg {
  block-size: 0.75rem;
  inline-size: 0.75rem;
}

/* The two colour chips on the toolbar show what the anchor cell has,
 * so the button says what it will do rather than only what it is.
 * --sheet-h and --sheet-c are set by commands.js from the selection. */
[data-fill-chip],
[data-ink-chip] {
  border-radius: 2px;
  display: block;
  inline-size: 1rem;
  line-height: 1;
  text-align: center;
}

[data-fill-chip] {
  block-size: 1rem;
  border: 1px solid var(--rst-line);
  background: var(--rst-surface-2);
}

[data-fill-chip][data-set] {
  background: oklch(var(--sheet-fill-l) var(--sheet-c) var(--sheet-h));
}

[data-ink-chip] {
  border-bottom: 3px solid var(--rst-text-muted);
  font-weight: 700;
}

[data-ink-chip][data-set] {
  border-bottom-color: oklch(var(--sheet-ink-l) var(--sheet-kc) var(--sheet-kh));
}

/* A swatch button is its colour with its name beside it. Named, not
 * colour alone: a picker that only shows colour is unusable to somebody
 * who cannot tell two of them apart, and the name is also what the
 * server is sent. */
[data-swatches] {
  min-inline-size: 11rem;
}

/* A swatch button is its colour and then its name, reading left to
 * right as one thing. The menu's general rule pushes an item's trailing
 * content to the far end so a keyboard shortcut lines up in a column —
 * right for "Bold … Ctrl B", wrong here, where it stranded each name
 * against the opposite edge from its own colour.
 *
 * Written at the full depth on purpose: that general rule is
 * [data-menubar] [rst-dropdown-menu] button, which is (0,2,1), and the
 * obvious [data-swatches] button is (0,1,1) and loses. */
[data-menubar] [rst-dropdown-menu] [data-swatches] button {
  gap: 0;
  justify-content: flex-start;
}

[data-swatch]::before {
  border: 1px solid var(--rst-line);
  border-radius: 2px;
  content: "";
  display: inline-block;
  block-size: 0.85rem;
  inline-size: 0.85rem;
  margin-inline-end: var(--rst-sp-2);
  vertical-align: -2px;
  background: oklch(var(--sheet-fill-l) var(--sheet-c) var(--sheet-h));
}

[data-ink-swatch]::before {
  background: oklch(var(--sheet-ink-l) var(--sheet-kc) var(--sheet-kh));
}

/* ── Row C: the name box and the formula bar ─────────────────────────
 *
 * The formula bar gets the whole row and all the slack. It used to
 * share a strip with five links and was capped at 40rem so they had
 * somewhere to sit; on its own row a cap only truncates long formulas
 * for no gain. */
[data-formula-row] {
  align-items: center;
  background: var(--rst-surface);
  border-bottom: 1px solid var(--rst-line);
  display: flex;
  gap: var(--rst-sp-2);
  padding: var(--rst-sp-1) var(--rst-sp-3);
}

[data-name-box] {
  flex: 0 0 auto;
  inline-size: 6rem;
  text-align: center;
}

[data-fx] {
  color: var(--rst-text-faint);
  flex: 0 0 auto;
  font-size: var(--rst-fs-sm);
}

[data-formula-input] {
  flex: 1;
  min-inline-size: 0;
}

/* ── Row D: the tab strip ────────────────────────────────────────────
 *
 * One tab today, and deliberately no "+": nothing in this build
 * creates, names or switches a tab, and spec §14 makes "a second tab is
 * reachable in the UI" the trigger for multi-worksheet import. */
[data-tabstrip] {
  align-items: stretch;
  background: var(--rst-surface-2);
  border-top: 1px solid var(--rst-line);
  display: flex;
  gap: var(--rst-sp-3);
  min-block-size: 2rem;
  padding-inline: var(--rst-sp-3);
}

[data-sheet-tabs] {
  display: flex;
}

[data-sheet-tabs] a {
  align-items: center;
  border-bottom: 2px solid transparent;
  color: var(--rst-text-muted);
  display: flex;
  font-size: var(--rst-fs-sm);
  padding-inline: var(--rst-sp-3);
  text-decoration: none;
}

[data-sheet-tabs] a:hover {
  color: var(--rst-text);
}

[data-sheet-tabs] a[aria-current] {
  background: var(--rst-surface);
  border-bottom-color: var(--rst-accent);
  color: var(--rst-text);
  font-weight: 600;
}

/* The size of the selection, not a sum of it. A total would be the
 * client doing arithmetic on strings the server rendered, which is the
 * one thing this design keeps out of JavaScript. */
[data-selection-size] {
  align-self: center;
  color: var(--rst-text-faint);
  font-size: var(--rst-fs-xs);
}

/* The transient note. Empty almost always, and the app's only way of
 * saying something happened or did not: today, that a browser refused
 * the menu's clipboard read, or that a paste is larger than one write
 * can carry.
 *
 * It takes the slack rather than the live indicator, which is why the
 * auto margin moved here — otherwise a note appearing shoves the
 * connection state sideways every time one is shown. Truncates rather
 * than wrapping: this strip is 2rem and a two-line message would push
 * the grid. */
/* A note the person has to act on. Muted grey in a strip under the
 * grid is the right weight for "12 cells were not pasted" and the
 * wrong one for "the menu could not paste, use the keyboard" — the
 * second went unread on the live instance while the menu item read as
 * simply broken. */
[data-sheet-note][data-tone="warn"] {
  color: var(--rst-tone-warning-fg);
  font-weight: 550;
}

[data-sheet-note] {
  align-self: center;
  color: var(--rst-text-muted);
  font-size: var(--rst-fs-xs);
  margin-inline-start: auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The live indicator. It says connected or reconnecting and nothing
 * else — presence is a later slice, and a dot that claims more than it
 * knows is worse than no dot. */
[data-sheet-live] {
  align-self: center;
  font-size: var(--rst-fs-xs);
  color: var(--rst-tone-positive-fg);
}

[data-sheet-live][data-state="off"] {
  color: var(--rst-tone-warning-fg);
}

/* isolation: the vendored grid's stylesheets stack with z-index values
 * up to 10001. Contained here they compete only with each other, under
 * the bar's 45 and the menus that float over the grid (spec §5.3). */
[data-sheet-grid] {
  flex: 1;
  min-height: 0;
  overflow: auto;
  background: var(--rst-bg);
  isolation: isolate;
}

/* ── The vendored grid ───────────────────────────────────────────────
 *
 * Jspreadsheet paints its own colours, hard-coded: #fff for the table,
 * #f3f3f3 for the headers, #ccc for every border. None of them follow a
 * theme, so all of them are restated here in tokens.
 *
 * The selectors are `.jss_*`. Jspreadsheet v5 renamed everything from
 * v4's `.jexcel`, and this block was first written against `.jexcel` —
 * so it matched nothing at all, and the grid stayed white inside dark
 * chrome. Check the vendored stylesheet, not a memory of the last major
 * version, when these stop working.
 *
 * Every rule that sets a background on something carrying text also
 * sets a color. The library sets background-color on the headers and no
 * color, so the text is inherited from the page: in dark mode that put
 * light grey text on a light grey header and the row and column names
 * became invisible. A background without a foreground beside it is the
 * bug, not the colour. (The exceptions are the things with no text in
 * them — the fill handle is a 1px knob.) */
[data-sheet-grid] .jss_worksheet {
  background-color: var(--rst-bg);
  color: var(--rst-text);
  border-right-color: var(--rst-line);
  border-bottom-color: var(--rst-line);
}

[data-sheet-grid] .jss_worksheet > tbody > tr > td {
  background-color: var(--rst-bg);
  color: var(--rst-text);
  border-top-color: var(--rst-line);
  border-left-color: var(--rst-line);
}

/* The row and column names. Both headers, one look. */
[data-sheet-grid] .jss_worksheet > thead > tr > td,
[data-sheet-grid] .jss_worksheet > tbody > tr > td:first-child {
  background-color: var(--rst-surface-2);
  color: var(--rst-text-muted);
  border-top-color: var(--rst-line);
  border-left-color: var(--rst-line);
  font-weight: 600;
}

/* The names of the selected column and row. The library puts .selected
 * on the thead td but on the tbody TR — not on its first cell — so the
 * two halves need different selectors. Written as `td:first-child.selected`
 * the second half matched nothing at all, and a rule that matches nothing
 * fails exactly like a rule nobody wrote: the vendored #dcdcdc stood, and
 * a dark grid grew a light-grey slab down its left edge carrying
 * --rst-text-muted (#a8b0ba) at 1.60:1. */
[data-sheet-grid] .jss_worksheet > thead > tr > td.selected,
[data-sheet-grid] .jss_worksheet > tbody > tr.selected > td:first-child {
  background-color: var(--rst-accent-soft);
  color: var(--rst-text);
}

/* ── The selection ───────────────────────────────────────────────────
 *
 * A range is a fill plus an outline. This file used to restate only the
 * fill, and the outline is where every hard-coded colour in the vendored
 * stylesheet lives:
 *
 *   .highlight-right/-bottom   border: 1px solid #000
 *   .highlight-top/-left       border: 1px solid #000, plus a decorative
 *                              box-shadow of #ccc just outside it
 *
 * In dark that renders backwards. The black borders disappear into the
 * grid; the two that do not are overwritten by our own
 * `td { border-top-color: var(--rst-line) }` above, which is one
 * specificity step higher than the library's `.jss_worksheet .highlight-top`
 * — so they become ordinary grid lines, while the #ccc shadow, which
 * exists only to mask the grid line the border sits on, survives as the
 * one bright thing on screen. What you get is a navy slab lit down its
 * top and left and nowhere else, with no way to see where it ends.
 *
 * ── Why every rule below is scoped `> tbody > tr > td` ────────────────
 * Two reasons, and the second is the one that bites.
 *
 * 1. The grid-line rule above is (0,2,3). A restatement written at the
 *    library's own `.jss_worksheet .highlight-top` is (0,2,0) and loses
 *    to it — a rule that parses, uses a token, and paints nothing.
 * 2. sheet.html links the vendored stylesheets in the head block, which
 *    layout.html renders AFTER sheets.css. The library is later in
 *    source order, so equal specificity loses too. Ours must be
 *    strictly higher, never merely equal. That is also the real reason
 *    the vendored #dcdcdc beat the header rule above: both are (0,3,3),
 *    and the vendor came last.
 *
 * So: one outline colour, --rst-accent, on all four sides, above both.
 * Measured on the fill it sits on, 6.59:1 in dark and 4.75:1 in light —
 * both past 1.4.11's 3:1 for a component boundary. The shadow goes:
 * with the outline in a colour that reads, masking the line underneath
 * it buys nothing and costs the selection its symmetry.
 *
 * The library gives each of its three range states a different border
 * STYLE — solid for the selection, dashed for the copy marquee, dotted
 * for the fill-handle drag preview — and the style survives our
 * colour-only override. That is the distinction doing the work, so all
 * three take the same accent and stay told apart by shape. */
[data-sheet-grid] .jss_worksheet > tbody > tr > td.highlight {
  background-color: var(--rst-accent-soft);
  color: var(--rst-text);
}

/* A link inside a selected cell. Vendored as the browser's own `blue`,
 * which on the fill in dark is 2.4:1. */
[data-sheet-grid] .jss_worksheet > tbody > tr > td.highlight > a {
  color: var(--rst-accent);
}

[data-sheet-grid] .jss_worksheet > tbody > tr > td.highlight-top,
[data-sheet-grid] .jss_worksheet > tbody > tr > td.selection-top,
[data-sheet-grid] .jss_worksheet > tbody > tr > td.copying-top {
  border-top-color: var(--rst-accent);
  box-shadow: none;
}

[data-sheet-grid] .jss_worksheet > tbody > tr > td.highlight-left,
[data-sheet-grid] .jss_worksheet > tbody > tr > td.selection-left,
[data-sheet-grid] .jss_worksheet > tbody > tr > td.copying-left {
  border-left-color: var(--rst-accent);
  box-shadow: none;
}

[data-sheet-grid] .jss_worksheet > tbody > tr > td.highlight-right,
[data-sheet-grid] .jss_worksheet > tbody > tr > td.selection-right,
[data-sheet-grid] .jss_worksheet > tbody > tr > td.copying-right {
  border-right-color: var(--rst-accent);
}

[data-sheet-grid] .jss_worksheet > tbody > tr > td.highlight-bottom,
[data-sheet-grid] .jss_worksheet > tbody > tr > td.selection-bottom,
[data-sheet-grid] .jss_worksheet > tbody > tr > td.copying-bottom {
  border-bottom-color: var(--rst-accent);
}

/* The rows the fill handle is being dragged over — the preview of what
 * a release would fill, NOT the range a formula is pointing at. There is
 * separate formula reference chrome below. `parseFormulas: false` keeps
 * calculation on the server. The library
 * adds these classes from updateCopySelection, off `selectedCorner`.
 * Vendored as rgba(0,0,0,0.05) — a darkening, which on a dark grid is
 * nothing at all. */
[data-sheet-grid] .jss_worksheet > tbody > tr > td.selection {
  background-color: var(--rst-accent-soft);
  color: var(--rst-text);
}

/* The anchor — the one cell the keyboard is on. The library paints it
 * transparent on purpose so it reads out of the fill; our .highlight
 * rule was opaque and took that back, leaving a range with no visible
 * cursor in either scheme.
 *
 * It comes LAST among the SELECTION rules on purpose. It ties
 * .highlight and .selection at (0,3,3), and the anchor is inside both
 * while you drag the fill handle — so on a tie it has to be the later
 * rule or the cursor disappears at exactly the moment you are aiming
 * with it.
 *
 * **The ring is what actually marks it, and the background no longer
 * can.** Cell fills are painted further down this file, at the same
 * (0,3,3) and therefore later, so on a filled range a background-only
 * anchor is indistinguishable from the rest of the selection — the
 * exact failure the paragraph above guards against, arriving by a new
 * route. An inset shadow survives any fill because it is drawn over
 * one, and it costs no layout, so it cannot shift a cell by a pixel
 * when the cursor lands on it.
 *
 * The background stays for the unfilled case, where it is what makes
 * the anchor read as "not part of the tint". */
[data-sheet-grid] .jss_worksheet > tbody > tr > td.highlight-selected {
  background-color: var(--rst-bg);
  color: var(--rst-text);
  box-shadow: inset 0 0 0 2px var(--rst-accent);
}

/* The fill handle at the corner of the range: a black knob ringed in
 * white, which in dark reads as a white dot with a dark centre — the
 * inverse of the thing it is meant to be. It is 1px plus its ring, so
 * it is the accent or it is invisible. */
[data-sheet-grid] .jss_corner {
  background-color: var(--rst-accent);
  border-color: var(--rst-bg);
}

/* ── The right-click menu ────────────────────────────────────────────
 *
 * Not Jspreadsheet's: jsuites', which it binds to `contextmenu` whether
 * or not the host app configures one. grid.js turns it off (its stock
 * items bypass our write paths), so today it never opens. The restatement
 * stays because the element is still mounted, and TestVendorPaintIsRestated
 * lists it: vendored it is #fff with #555 text — a white card on a dark
 * grid — should anything ever open it again.
 *
 * It is `position: fixed` and mounted inside the grid, so it is scoped
 * the same way everything else here is. */
[data-sheet-grid] .jcontextmenu {
  background: var(--rst-surface);
  color: var(--rst-text);
  border-color: var(--rst-line-strong);
  box-shadow: var(--rst-shadow-pop);
}

[data-sheet-grid] .jcontextmenu > div a {
  color: var(--rst-text);
}

[data-sheet-grid] .jcontextmenu > div span {
  color: var(--rst-text-muted);
}

[data-sheet-grid] .jcontextmenu > div:hover {
  background: var(--rst-accent-soft);
  color: var(--rst-text);
}

[data-sheet-grid] .jcontextmenu .jcontextmenu-disabled a,
[data-sheet-grid] .jcontextmenu .jcontextmenu-disabled::before {
  color: var(--rst-text-faint);
}

[data-sheet-grid] .jcontextmenu hr {
  border-color: var(--rst-line);
}

/* The cell editor is an input the library drops into the td. Left
 * alone it inherits the browser's own white. */
[data-sheet-grid] .jss_worksheet td input,
[data-sheet-grid] .jss_worksheet td textarea {
  font: inherit;
  letter-spacing: inherit;
  text-align: inherit;
  background-color: inherit;
  color: inherit;
}

/* iOS zooms the page into any text control under 16px, and tokens.css
 * brings controls up to 16px where the pointer is coarse. Two of ours
 * set `font: inherit` with a selector that outweighs tokens.css's bare
 * `input`, and fell back to 14px: the cell editor above, and estilo's
 * title field in the bar. Same rule, same max(), restated here at their
 * weight. TestNoZoomOnTap (make test-browser) measures both. */
@media (pointer: coarse) {
  [data-sheet-grid] .jss_worksheet td input,
  [data-sheet-grid] .jss_worksheet td textarea,
  [estilo-bar-title-input] {
    font-size: max(1rem, 1em);
  }
}

[data-sheet-grid] .jss_container,
[data-sheet-grid] .jss_content {
  background-color: var(--rst-bg);
}

/* A cell whose imported fill fails WCAG AA against its ink. Spec §11.1
 * ruled that we retain such a fill rather than silently correcting it,
 * so the fill stays and this marks it instead: a corner flag that costs
 * nothing to ignore and is impossible to miss once you look. */
[data-sheet-grid] td[data-contrast="low"] {
  position: relative;
}

[data-sheet-grid] td[data-contrast="low"]::after {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-end: 0;
  border-block-start: 0.5rem solid var(--rst-tone-warning-fg);
  border-inline-start: 0.5rem solid transparent;
  /* A halo in the page's own surface colour, so the flag survives the
   * fill it is flagging.
   *
   * The flag marks a cell whose IMPORTED fill and ink fail AA, and the
   * fill is an arbitrary colour out of somebody's file — including, in
   * the case this app cites by name, Excel's #FFEB9C, against which an
   * amber triangle is invisible in dark mode. A warning that disappears
   * on exactly the cells it is about is not a warning.
   *
   * drop-shadow rather than a border or an outline: those two follow
   * the box, and this triangle is drawn out of borders, so there is no
   * box to put an edge on. filter traces the rendered shape. */
  filter: drop-shadow(0 0 1px var(--rst-surface))
          drop-shadow(0 0 1px var(--rst-surface));
}

/* ── The scriptless table ────────────────────────────────────────────
 *
 * Not a fallback in the apologetic sense: it is the only view that
 * works everywhere, and every affordance the grid has exists here too. */
[data-cell-table] {
  border-collapse: collapse;
  width: 100%;
}

[data-cell-table] th,
[data-cell-table] td {
  border: 1px solid var(--rst-line);
  padding: var(--rst-sp-1) var(--rst-sp-2);
  text-align: start;
  vertical-align: top;
}

[data-cell-table] th {
  background: var(--rst-surface-2);
  color: var(--rst-text-muted);
  font-weight: 600;
}

/* The cell link, which is every cell.
 *
 * Each cell is an anchor, because the link IS the edit affordance in a
 * view with no script. Left at the browser's default that meant roughly
 * 650 blue underlined links per page — data and emptiness in the same
 * colour and the same weight, so `94200` and a blank cell differed only
 * in glyph width. A spreadsheet you can only read cell by cell is not a
 * spreadsheet, and this is the view the spec calls the product.
 *
 * So the cell reads as a cell and the affordance shows on approach. The
 * anchor takes the cell's full width and a line of height, so the
 * target is the whole line rather than the few pixels of the glyph —
 * which is also what lets an empty cell be clickable without a &nbsp;
 * standing in for content. It does not stretch to a row made taller by
 * a wrapped neighbour; that was true of the &nbsp; it replaces too.
 *
 * :focus-visible carries the same treatment as :hover deliberately.
 * Keyboard is the primary way through this view, and a focus ring alone
 * on a 26-column grid is easy to lose. */
[data-cell-table] td > a {
  color: inherit;
  text-decoration: none;
  display: block;
  min-block-size: 1.25rem;
  /* Square, and the whole cell: the link reaches out over the cell's own
   * padding and pads itself back in, so its hover fills the cell edge to
   * edge. It was a 6px rounded pill round the text inside a square cell
   * — on an empty cell a small floating blob (the square rule, spec §11:
   * an in-flow highlight is square). */
  margin: calc(-1 * var(--rst-sp-1)) calc(-1 * var(--rst-sp-2));
  padding: var(--rst-sp-1) var(--rst-sp-2);
}

[data-cell-table] td > a:hover,
[data-cell-table] td > a:focus-visible {
  background: var(--rst-accent-soft);
  color: var(--rst-accent);
}

/* Numbers right, text left — the alignment IS the type signal in a
 * spreadsheet, and it is the one the grid gives for free and this view
 * had to be told.
 *
 * Both spellings, because there are two of these tables. The editable
 * one wraps every cell in an anchor, so the alignment has to reach the
 * block anchor rather than the cell around it; the anonymous link view
 * has no anchors, because there is nothing on it to edit. One rule for
 * each, rather than one selector that quietly covers only the surface
 * that happened to get tested. */
[data-cell-table] td[data-kind="number"],
[data-cell-table] td[data-kind="number"] > a {
  text-align: end;
}

[data-cell-table] td form {
  display: flex;
  gap: var(--rst-sp-1);
}

[data-cell-table] td input[type="text"] {
  inline-size: 100%;
  min-inline-size: 6ch;
}

/* [data-cell-value] used to be styled here and is now gone. Nothing in
 * any template or module has ever written that attribute, so the rule
 * matched no element for the life of the branch — the same defect as
 * the contrast flag two sections up, which had a rule and no attribute
 * and therefore warned about cells it never marked.
 *
 * A selector that matches nothing is invisible to every test we have
 * and to every screenshot, which is why it is worth saying it was here
 * rather than deleting it silently: the next person to want a secondary
 * line under a table cell should write the markup and the rule
 * together, not find this and assume it works. */


/* Import drop zone ───────────────────────────────────────────────────
 *
 * The dashed outline is the whole visual: it says "this box takes a
 * file" without becoming a second, competing button beside the one that
 * is already there. The field and its button inside it are untouched
 * rastrillo, because with scripting off they are the entire feature and
 * a drop zone that restyles them would be decorating something that
 * cannot happen.
 *
 * The highlight is an ATTRIBUTE toggled by drop.js, not a class: it is
 * transient state on one element, and the attribute spelling is what
 * the rest of this app's own hooks use (data-sheet-surface,
 * data-cell-table).
 *
 * transition on border-color only — a person dragging a file across the
 * page crosses this box repeatedly, and animating the outline is the
 * difference between a box that acknowledges the drag and one that
 * flashes. */
/* :not([data-drop-page]) on both, because there are two zones now and
   only one of them is a box. The overlay on the sheet list carries
   data-drop too — it has to, it is what drop.js looks for — and these
   two selectors are 0-2-0 and 0-3-0, so without the exclusion they beat
   [rst-modal-overlay]'s 0-1-0 and paint the sheet of glass over the
   whole page in accent-soft. It renders as a flat blue screen with the
   list gone: a defect a browser found and no test could. */
[data-drop]:not([data-drop-page]) {
  border: 1px dashed var(--rst-line);
  border-radius: var(--rst-radius);
  padding: var(--rst-sp-3);
  transition: border-color 120ms ease, background-color 120ms ease;
}

[data-drop][data-drop-over]:not([data-drop-page]) {
  border-color: var(--rst-accent);
  background: var(--rst-accent-soft);
}

/* Somebody who has asked for less motion still needs to see the box
   respond — so the change stays and only the animation goes. */
@media (prefers-reduced-motion: reduce) {
  [data-drop] {
    transition: none;
  }
}

/* ── The sheet list's drop overlay ───────────────────────────────────
 *
 * The list has no drop box. It has a full-window target that does not
 * exist until a file is over the window, and drop.js is what reveals
 * it — see the comment on [data-drop-page] in sheets.html.
 *
 * It borrows rastrillo's modal paint rather than growing its own, so
 * the layer that appears over the list looks like every other layer in
 * the suite. Only the four rules below are ours.
 *
 * THIS FIRST RULE IS LOAD-BEARING. [rst-modal-overlay] sets
 * display: flex, and an author declaration beats the user agent's
 * [hidden] { display: none } no matter how weak its selector looks.
 * Without it the overlay covers the sheet list permanently and the page
 * is unusable — with scripting off, where nothing would ever come along
 * to hide it again. TestTheDropOverlayIsHiddenAtRest pins the markup;
 * this pins the paint. */
[data-drop-page][hidden] {
  display: none;
}

/* A full-window overlay sits above the bar (spec §5.3): rastrillo's
 * modal overlay is z-index 10, under the bar's 45, so without this the
 * bar would paint over the top of the drop target. */
[data-drop-page] {
  z-index: 50;
}

[data-drop-page] [rst-modal-panel] {
  max-width: 24rem;
  transition: border-color 120ms ease;
}

/* The panel answers the drag, the same way the box on /sheets/import
   does and with the same token, so the two read as one gesture. */
[data-drop-page][data-drop-over] [rst-modal-panel] {
  border-color: var(--rst-accent);
}

[data-drop-page-body] {
  display: grid;
  gap: var(--rst-sp-3);
  justify-items: center;
  padding: var(--rst-sp-5);
  text-align: center;
  width: 100%;
}

[data-drop-page-title] {
  font-size: var(--rst-fs-base);
  font-weight: 600;
  margin: 0;
}

/* The chooser is for afterwards.
 *
 * While a file is over the window the panel has one thing to say, and a
 * "Choose File" button beside "Drop it to import" offers a second way to
 * do the thing the person is already doing. It appears when the overlay
 * is up WITHOUT a drag over it, which is exactly the state a refused
 * drop leaves behind — and then it is the way out of that refusal. */
[data-drop-page-recover] {
  display: grid;
  gap: var(--rst-sp-3);
  justify-items: center;
  width: 100%;
}

[data-drop-page][data-drop-over] [data-drop-page-recover] {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  [data-drop-page] [rst-modal-panel] {
    transition: none;
  }
}

/* ── Cell formatting ─────────────────────────────────────────────────
 *
 * The paint for a cell's stored Format: bold, italic, alignment, fill
 * and ink. Every rule is here, in the file the guards read, rather than
 * in a stylesheet JavaScript generates — a generated sheet is invisible
 * to TestOurStylesheetHasNoColourLiterals, TestEveryTokenWeUseExists
 * and TestVendorPaintIsRestated alike, which is three reasons not to
 * have one. paint.js only sets attributes and two custom properties.
 *
 * Specificity: [data-sheet-grid] .jss_worksheet > tbody > tr > td[attr]
 * is (0,3,3) — one step above the grid-line rule at (0,2,3), as the
 * restatement rule requires, and equal to the selection rules. So these
 * are placed AFTER them: a filled cell keeps its fill while selected,
 * and the selection outline does the work of showing the selection,
 * which is what every spreadsheet does.
 *
 * A colour a person picks HERE is a hue and a chroma with no lightness
 * — see store/models.go — and the lightness comes from the theme below.
 * A colour an imported FILE authored is painted exactly as authored,
 * in both themes, because spec §11.1 says we render a document as
 * written and warn rather than silently correcting it. */

/* light-dark() is a colour function and cannot hold a number, so these
 * are written the long way. They still resolve before first paint:
 * data-theme is server-rendered into the first bytes of the response
 * (layout.html), so there is no flash of the wrong lightness. */
:root {
  --sheet-fill-l: 0.92;
  --sheet-ink-l: 0.42;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --sheet-fill-l: 0.30;
    --sheet-ink-l: 0.80;
  }
}

:root[data-theme="dark"] {
  --sheet-fill-l: 0.30;
  --sheet-ink-l: 0.80;
}

[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-b] {
  font-weight: 600;
}

[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-i] {
  font-style: italic;
}

/* ── Alignment, and why these three rules carry !important ───────────
 *
 * Jspreadsheet writes `text-align` as an INLINE style on every data
 * cell, from the column's `align` or from `defaultColAlign`. An inline
 * declaration outranks any selector, at any specificity, so the
 * ordinary version of these rules set the attribute, reflected
 * aria-pressed on the button, and changed nothing on screen. The
 * toolbar looked like it worked. Only a browser could see it, and the
 * branch's own drive did not check alignment — bold and fill, which
 * both paint from properties the library does not touch, passed.
 *
 * !important is the correct tool here and not a shortcut: it is what
 * outranks an inline style, and there is no other. These are new paint
 * rather than a restatement of a vendored rule, so the restatement
 * guard is unaffected.
 *
 * The numeric rule is important for the same reason and comes FIRST,
 * so an explicit alignment beats it on source order — both are
 * important and (0,3,3), so nothing else separates them. */
[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-n] {
  text-align: end !important;
}

[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-a="left"] {
  text-align: start !important;
}

[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-a="center"] {
  text-align: center !important;
}

[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-a="right"] {
  text-align: end !important;
}

/* Ours: hue and chroma from the cell, lightness from the theme. */
[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-fill] {
  background-color: oklch(var(--sheet-fill-l) var(--sheet-c) var(--sheet-h));
}

[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-ink] {
  color: oklch(var(--sheet-ink-l) var(--sheet-kc) var(--sheet-kh));
}

/* The author's: painted as the file wrote it. --sheet-fx and --sheet-k
 * carry a hex that came from a document, which is why a literal is
 * acceptable here and nowhere else in this file — it is data, set by
 * script from the document, not a colour this stylesheet chose. */
[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-fx] {
  background-color: var(--sheet-fx);
}

[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-k] {
  color: var(--sheet-k);
}

/* ── The table view paints what the grid paints ──────────────────────
 *
 * The same attributes, because the server writes the same attributes
 * (tablepaint.go). PRODUCT.md's second principle is why: the scriptless
 * path is the product, and a screen that can SET bold but cannot SHOW
 * it is the worse half of the feature.
 *
 * Written as its own block rather than by widening the grid's selectors
 * to cover both. Those carry `.jss_worksheet` and `!important` for
 * reasons that belong entirely to the vendored library — an inline
 * text-align the library writes on every data cell — and none of that
 * is true here. A shared selector would carry the library's problem
 * onto a table that does not have it, and would make the restatement
 * guard's job ambiguous.
 *
 * No !important, and that is the tell that these are new paint rather
 * than a restatement: nothing writes an inline text-align on this
 * table. The one inline style a cell here carries is the custom
 * properties, which set values rather than win a cascade. */
[data-cell-table] td[data-b],
[data-cell-table] td[data-b] > a {
  font-weight: 600;
}

[data-cell-table] td[data-i],
[data-cell-table] td[data-i] > a {
  font-style: italic;
}

/* The numeric rule comes first so an explicit alignment beats it on
 * source order, the same ordering the grid's block relies on. Both are
 * (0,2,1) here, so nothing else separates them.
 *
 * The existing [data-kind="number"] rule above already right-aligns a
 * numeric cell; these three are the explicit override, and they have to
 * come after it in the file for the same reason. */
[data-cell-table] td[data-a="left"],
[data-cell-table] td[data-a="left"] > a {
  text-align: start;
}

[data-cell-table] td[data-a="center"],
[data-cell-table] td[data-a="center"] > a {
  text-align: center;
}

[data-cell-table] td[data-a="right"],
[data-cell-table] td[data-a="right"] > a {
  text-align: end;
}

/* Ours: hue and chroma from the cell, lightness from the theme, so a
 * fill somebody picked follows their light/dark choice. Identical to
 * the grid's rule and deliberately so — a fill must not be one colour
 * on one screen and another on the other. */
[data-cell-table] td[data-fill] {
  background-color: oklch(var(--sheet-fill-l) var(--sheet-c) var(--sheet-h));
}

[data-cell-table] td[data-ink],
[data-cell-table] td[data-ink] > a {
  color: oklch(var(--sheet-ink-l) var(--sheet-kc) var(--sheet-kh));
}

/* The author's, painted as the file wrote it (spec §11.1). --sheet-fx
 * and --sheet-k carry a hex that came out of a document — data, checked
 * by previewsvg.go's safeHex before it was ever written into the
 * attribute, and not a colour this stylesheet chose. */
[data-cell-table] td[data-fx] {
  background-color: var(--sheet-fx);
}

[data-cell-table] td[data-k],
[data-cell-table] td[data-k] > a {
  color: var(--sheet-k);
}

/* The cell edit form's formatting row. Six selects and a button, on
 * their own line under the value field: side by side they would push
 * the table's column wider than the page. */
[data-cell-format] {
  display: flex;
  flex-wrap: wrap;
  gap: var(--rst-sp-1);
  margin-block-start: var(--rst-sp-1);
}

[data-cell-format] select {
  font-size: var(--rst-fs-xs);
  padding-block: 0.15rem;
}

/* ── The list's thumbnails ───────────────────────────────────────────
 *
 * A 26x34 slot at the row's left edge, the same box in the same place
 * as the Docs app's, so the two file lists have the same row height and
 * read as one product rather than two apps that both grew thumbnails.
 *
 * The slot is reserved whether or not there is anything to draw: an
 * empty sheet renders the empty bordered box, so a row never changes
 * height when a sheet gains its first cell. */
/* The list card is a <ul> and rastrillo's [rst-list] is written for a
 * <div> — its comment says "a list card holds rows", and every rule it
 * declares is background, border and radius. It resets nothing, because
 * a <div> has nothing to reset.
 *
 * A <ul> does. The UA stylesheet gives it `padding-inline-start: 40px`
 * and `list-style: disc`, and both survived: the 40px is the gap Paul
 * saw to the left of the thumbnail on 2026-09-03, and it is 40px of
 * card that every row is indented inside. The markers were invisible
 * only because they render in that padding, outside the row's own box.
 *
 * The element stays a <ul>. A list of sheets is a list, and the fix for
 * a framework rule that assumed a <div> is to reset what the <div>
 * never had — not to drop the semantics that make the list announce as
 * one to a screen reader.
 *
 * The block margin is REPLACED rather than removed. Nothing in
 * rastrillo stacks the children of [rst-page] — there is no
 * `> * + *` rule — so what separated this card from the import form
 * above it was the <ul>'s own UA `margin-block: 1em`. Zeroing the
 * shorthand would have closed that gap as a side effect of fixing the
 * padding, so the margin is restated from the token scale, which is
 * where every other gap on the page comes from. */
[data-sheet-list] {
  list-style: none;
  margin-block: var(--rst-sp-4);
  margin-inline: 0;
  padding: 0;
}

/* [rst-lrow] is a grid, and --rst-cols is rastrillo's own hook for
 * saying what its columns are. Setting it is how a row gains a
 * thumbnail without overriding anything: without it the row has one
 * column and the picture stacks above the title. */
[data-sheet-list] [rst-lrow] {
  --rst-cols: 26px 1fr;
}

/* Below 800px rastrillo sets grid-template-columns on [rst-lrow]
 * DIRECTLY rather than through --rst-cols, so the hook above stops
 * being read and the thumbnail lands in the title's column with an
 * empty one trailing. This is (0,2,0) against the framework's (0,1,0),
 * so it wins wherever it sits in the file. */
@media (max-width: 800px) {
  [data-sheet-list] [rst-lrow] {
    grid-template-columns: 26px minmax(0, 1fr);
  }
}

/* The share page's two lists: links and people. The same reset the
 * sheet list needs — rastrillo's [rst-list] keeps the browser's 40px
 * list indent — and two columns, so "Remove" and "Turn off" sit beside
 * the row instead of dropping under its name: [rst-lrow] is a one-column
 * grid until --rst-cols says otherwise. Below 800px rastrillo sets the
 * columns directly, so the narrow rule restates them. */
[data-share-list] {
  list-style: none;
  margin-block: var(--rst-sp-3) var(--rst-sp-4);
  margin-inline: 0;
  padding: 0;
}

[data-share-list] > [rst-lrow] {
  --rst-cols: minmax(0, 1fr) auto;
}

@media (max-width: 800px) {
  [data-share-list] > [rst-lrow] {
    grid-template-columns: minmax(0, 1fr) auto;
  }
}

[data-share-list] form {
  margin: 0;
}

[data-preview-slot] {
  display: block;
  block-size: 34px;
  inline-size: 26px;
}

[data-preview] {
  display: block;
}

/* The Format menu's two resize forms. A row rather than a stack: the
 * label, the number and the button read as one control, and the panel
 * is already 15rem wide enough to hold them. */
[data-size-form] {
  align-items: center;
  display: flex;
  gap: var(--rst-sp-2);
  padding: var(--rst-sp-1) var(--rst-sp-2);
}

/* nowrap is the point of the three rules below, and they are sized
 * backwards from it. The panel is 15rem, so a row has 224px of content
 * box; at 4rem of field and a button padded to its word, the label is
 * left about 100px and "Column width" sets on one line. At the numbers
 * this started with — a 4.5rem field and a default-padded button — it
 * had 48px and broke over two lines, and the two forms then disagreed
 * about their own height. */
/* A fixed label column, not flex: 1. The two forms are siblings and
 * cannot share a grid, so the only thing that lines their fields and
 * their two Set buttons up is both labels reserving the same width —
 * with flex: 1 each label took its own text's width and the rows sat
 * 33px out of step with each other. */
[data-size-form] label {
  color: var(--rst-text-muted);
  flex: 0 0 7rem;
  font-size: var(--rst-fs-sm);
  white-space: nowrap;
}

[data-size-form] input {
  inline-size: 4rem;
}

[data-size-form] button {
  padding-inline: var(--rst-sp-2);
}

/* On a touch screen the field is 16px type (no zoom on tap) and the
 * button is padded for a finger, and the row no longer fits: Set hung
 * 11-21px out of the panel's right edge. The label column gives the room
 * back; "Column width" still sets on one line in 5.5rem, and both rows
 * keep the same column, so they stay in step. */
@media (pointer: coarse) {
  [data-size-form] label {
    flex-basis: 5.5rem;
  }
}

/* The referenced range is separate from the cell receiving the formula. */
[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-formula-reference] {
  outline: 2px solid var(--rst-accent);
  outline-offset: -2px;
}

/* One entry in a comment thread (comments.html): author and time, then
   the text, always shown. Entries after the first get a rule between. */
[data-comment] > p {
  margin: 0;
}

[data-comment] > p + p {
  margin-block-start: var(--rst-sp-1);
  white-space: pre-line;
}

[data-comment] + [data-comment] {
  border-block-start: 1px solid var(--rst-line);
  margin-block-start: var(--rst-sp-3);
  padding-block-start: var(--rst-sp-3);
}

[data-comment] [rst-row-sub] {
  display: inline;
  margin-inline-start: var(--rst-sp-2);
}
