oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1/* Sheets' own chrome, on top of rastrillo's tokens.
2 *
3 * Every colour here is a token, never a literal — a literal does not
4 * follow the person's light/dark choice, and cell fills resolve against
5 * the theme, so one hard-coded grey stays wrong in one of the two
6 * schemes forever.
7 *
8 * Token names are checked mechanically by
9 * internal/guardstest/csstokens_test.go. Five invented ones shipped
10 * here first (--rst-border, --rst-text-mut, --rst-warning,
11 * --rst-font-mono, --rst-shell-bar-h): CSS resolves an undefined custom
12 * property to its fallback in silence, so the page looked plausible and
13 * was simply not themed. The real names are --rst-line,
14 * --rst-text-muted, --rst-tone-warning-fg and --rst-font.
15 */
16
17/* The grid surface fills what is left of the viewport under the bar.
18 *
19 * The shell does that part now: the grid page renders
20 * <div estilo-shell="fill">, and estilo's chrome.css makes that shell
21 * exactly the viewport, holds the bar at its 52px and gives the grid's
22 * <main> the rest (flex: 1, min-block-size: 0). This rule used to be
23 * [rst-shell-topbar]:has([data-sheet-surface]) { height: 100dvh } here,
24 * keyed on rastrillo's wrapper, and before that a guessed topbar height
25 * that made a 771px document in a 720px viewport. What is left is the
26 * surface's own column: the chrome strips and the grid. */
27[data-sheet-surface] {
28 display: flex;
29 flex-direction: column;
30}
31
32/* ── The chrome ──────────────────────────────────────────────────────
33 *
34 * Three strips around the grid, and a budget: the whole lot is about
35 * 7rem above the grid and 2rem below, against Google's ~9.5rem, because
36 * the document title sits in the app's own top bar rather than in a row
37 * of its own.
38 *
39 * Row B — the menu bar and the toolbar, sharing one row. Six short
40 * words are about 300px, so a row holding only the menus wastes
41 * two-thirds of itself. They wrap onto separate rows when they no
42 * longer fit, which is also what keeps this inside a 320px viewport
43 * with no horizontal scroll. */
44[data-chrome] {
45 display: flex;
46 align-items: center;
47 flex-wrap: wrap;
48 gap: var(--rst-sp-1) var(--rst-sp-3);
49 padding: 0 var(--rst-sp-3);
50 border-bottom: 1px solid var(--rst-line);
51 background: var(--rst-surface);
52 color: var(--rst-text);
53}
54
55/* The menu bar is estilo's ([estilo-menubar], [estilo-menu], estilo
56 * menu.css): square titles in the page flow, 9px floating panels, inset
57 * rows and a shortcut column. What was here — rastrillo dropdowns
58 * repositioned with an anchor-positioning override, 6px rounded titles
59 * with no hover, a 17rem panel and inline accordion submenus — went with
60 * it. Only what is Sheets' own is left below. */
61
62/* An item that needs commands.js is rendered hidden until it can run.
63 * estilo's rows are display: flex, which is an author rule and beats the
64 * user agent's [hidden] { display: none } however weak its selector
65 * looks, so hidden is restated. */
66[data-menubar] [estilo-menu] > [hidden] {
67 display: none;
68}
69
70/* The Format menu's two resize forms sit in the panel as rows: the same
71 * inline padding as estilo's items, so their labels line up with the
72 * item text above and below. */
73[data-menubar] [estilo-menu] > [data-size-form] {
74 padding: var(--rst-sp-1) var(--estilo-item-padding-inline, 0.65rem);
75}
76
77/* ── The keyboard shortcuts dialog ──────────────────────────────────
78 *
79 * Opened from the account menu's Help. A native modal <dialog>, so it is
80 * in the top layer above the bar; a float, so it takes estilo's float
81 * radius and shadow. */
82[data-shortcuts] {
83 inline-size: min(26rem, calc(100vw - 2rem));
84 padding: var(--rst-sp-4);
85 border: 1px solid var(--rst-line);
86 border-radius: var(--estilo-float-radius, 9px);
87 background: var(--rst-surface);
88 color: var(--rst-text);
89 box-shadow: var(--rst-shadow-pop);
90}
91
92[data-shortcuts]::backdrop {
93 background: color-mix(in oklab, var(--rst-text) 30%, transparent);
94}
95
96[data-shortcuts] h2 {
97 margin: 0 0 var(--rst-sp-3);
98 font-size: 1.125rem;
99}
100
101[data-shortcuts] dl {
102 align-items: baseline;
103 display: grid;
104 grid-template-columns: auto 1fr;
105 gap: var(--rst-sp-1) var(--rst-sp-4);
106 margin: 0;
107}
108
109[data-shortcuts] dt {
110 text-align: end;
111}
112
113[data-shortcuts] dt kbd {
114 font-family: var(--rst-font);
115 font-size: var(--rst-fs-sm);
116 font-variant-numeric: tabular-nums;
117}
118
119[data-shortcuts] dd {
120 color: var(--rst-text-muted);
121 font-size: var(--rst-fs-sm);
122 margin: 0;
123}
124
125[data-shortcuts-foot] {
126 display: flex;
127 justify-content: flex-end;
128 margin-block-start: var(--rst-sp-4);
129}
130
131/* ── The toolbar ─────────────────────────────────────────────────────
132 *
133 * `rst-btn="compact"` is not a rastrillo variant — the word appears in
134 * tokens.css only inside comments — so a toolbar built on it would be
135 * silently unstyled. These buttons are ours.
136 *
137 * 1.75rem square, clear of the 24px minimum target, and the pressed
138 * state is a box-shadow ring rather than a border: a border costs
139 * layout, and eleven of them would widen the strip by 22px against a
140 * hard 320px reflow budget. A full ring rather than an underline,
141 * because a one-sided inset shadow follows the border radius round the
142 * corners and reads as a tab stem. */
143[data-toolbar] {
144 align-items: center;
145 display: flex;
146 flex-wrap: wrap;
147 gap: var(--rst-sp-1);
148 padding: var(--rst-sp-1) 0;
149}
150
151/* Square (the suite's square rule, spec §11 and Paul's §13 answer 4):
152 * these buttons are borderless, so their hover and pressed fill is an
153 * in-flow highlight, not a bordered control. They were 6px. */
154[data-tool] {
155 align-items: center;
156 background: transparent;
157 border: 0;
158 border-radius: 0;
159 color: var(--rst-text-muted);
160 cursor: pointer;
161 display: inline-flex;
162 font: inherit;
163 font-size: var(--rst-fs-sm);
164 gap: 0.1rem;
165 justify-content: center;
166 min-block-size: 1.75rem;
167 min-inline-size: 1.75rem;
168 padding: 0 var(--rst-sp-1);
169 /* [rst-tip]::after is absolutely positioned and rastrillo does not
170 * position the host, so the tooltip would anchor to the page. */
171 position: relative;
172}
173
174[data-tool]:hover {
175 background: var(--rst-surface-2);
176 color: var(--rst-text);
177}
178
179[data-tool][aria-pressed="true"] {
180 background: var(--rst-accent-soft);
181 box-shadow: inset 0 0 0 1px currentColor;
182 color: var(--rst-accent);
183}
184
185[data-tool]:focus-visible {
186 outline: 2px solid var(--rst-accent);
187 outline-offset: 2px;
188}
189
190[data-tool] svg {
191 block-size: 1rem;
192 fill: none;
193 inline-size: 1rem;
194 stroke: currentColor;
195 stroke-linecap: round;
196 stroke-width: 1.5;
197}
198
199[data-tool-rule] {
200 align-self: stretch;
201 border-inline-start: 1px solid var(--rst-line);
202 margin: var(--rst-sp-1) var(--rst-sp-1);
203}
204
205[data-tool-menu] > summary {
206 padding: 0;
207}
208
209[data-tool-menu] > summary::-webkit-details-marker {
210 display: none;
211}
212
213/* The same pair as the menu bar's, for the same reason and with the
214 * same two branches. A toolbar's colour picker opens under its own
215 * button, not off the right edge of the window. */
216[data-tool-menu] [rst-dropdown-menu] {
217 inset-inline-start: 0;
218 inset-inline-end: auto;
219}
220
221@supports (position-area: block-end) and (position-try-fallbacks: flip-block) {
222 [data-tool-menu] [rst-dropdown-menu] {
223 inset: auto;
224 position-area: block-end span-inline-end;
225 }
226}
227
228/* A picker's tooltip names the button; once its panel is open it only
229 * covers the panel's first row ("No fill"), so it steps aside. */
230[data-tool-menu][open] > [rst-tip]::after {
231 opacity: 0;
232}
233
234[data-tool] [rst-caret] svg {
235 block-size: 0.75rem;
236 inline-size: 0.75rem;
237}
238
239/* The two colour chips on the toolbar show what the anchor cell has,
240 * so the button says what it will do rather than only what it is.
241 * --sheet-h and --sheet-c are set by commands.js from the selection. */
242[data-fill-chip],
243[data-ink-chip] {
244 border-radius: 2px;
245 display: block;
246 inline-size: 1rem;
247 line-height: 1;
248 text-align: center;
249}
250
251[data-fill-chip] {
252 block-size: 1rem;
253 border: 1px solid var(--rst-line);
254 background: var(--rst-surface-2);
255}
256
257[data-fill-chip][data-set] {
258 background: oklch(var(--sheet-fill-l) var(--sheet-c) var(--sheet-h));
259}
260
261[data-ink-chip] {
262 border-bottom: 3px solid var(--rst-text-muted);
263 font-weight: 700;
264}
265
266[data-ink-chip][data-set] {
267 border-bottom-color: oklch(var(--sheet-ink-l) var(--sheet-kc) var(--sheet-kh));
268}
269
270/* A swatch button is its colour with its name beside it. Named, not
271 * colour alone: a picker that only shows colour is unusable to somebody
272 * who cannot tell two of them apart, and the name is also what the
273 * server is sent. */
274[data-swatches] {
275 min-inline-size: 11rem;
276}
277
278/* A swatch button is its colour and then its name, reading left to
279 * right as one thing. The menu's general rule pushes an item's trailing
280 * content to the far end so a keyboard shortcut lines up in a column —
281 * right for "Bold … Ctrl B", wrong here, where it stranded each name
282 * against the opposite edge from its own colour.
283 *
284 * Written at the full depth on purpose: that general rule is
285 * [data-menubar] [rst-dropdown-menu] button, which is (0,2,1), and the
286 * obvious [data-swatches] button is (0,1,1) and loses. */
287[data-menubar] [rst-dropdown-menu] [data-swatches] button {
288 gap: 0;
289 justify-content: flex-start;
290}
291
292[data-swatch]::before {
293 border: 1px solid var(--rst-line);
294 border-radius: 2px;
295 content: "";
296 display: inline-block;
297 block-size: 0.85rem;
298 inline-size: 0.85rem;
299 margin-inline-end: var(--rst-sp-2);
300 vertical-align: -2px;
301 background: oklch(var(--sheet-fill-l) var(--sheet-c) var(--sheet-h));
302}
303
304[data-ink-swatch]::before {
305 background: oklch(var(--sheet-ink-l) var(--sheet-kc) var(--sheet-kh));
306}
307
308/* ── Row C: the name box and the formula bar ─────────────────────────
309 *
310 * The formula bar gets the whole row and all the slack. It used to
311 * share a strip with five links and was capped at 40rem so they had
312 * somewhere to sit; on its own row a cap only truncates long formulas
313 * for no gain. */
314[data-formula-row] {
315 align-items: center;
316 background: var(--rst-surface);
317 border-bottom: 1px solid var(--rst-line);
318 display: flex;
319 gap: var(--rst-sp-2);
320 padding: var(--rst-sp-1) var(--rst-sp-3);
321}
322
323[data-name-box] {
324 flex: 0 0 auto;
325 inline-size: 6rem;
326 text-align: center;
327}
328
329[data-fx] {
330 color: var(--rst-text-faint);
331 flex: 0 0 auto;
332 font-size: var(--rst-fs-sm);
333}
334
335[data-formula-input] {
336 flex: 1;
337 min-inline-size: 0;
338}
339
340/* ── Row D: the tab strip ────────────────────────────────────────────
341 *
342 * One tab today, and deliberately no "+": nothing in this build
343 * creates, names or switches a tab, and spec §14 makes "a second tab is
344 * reachable in the UI" the trigger for multi-worksheet import. */
345[data-tabstrip] {
346 align-items: stretch;
347 background: var(--rst-surface-2);
348 border-top: 1px solid var(--rst-line);
349 display: flex;
350 gap: var(--rst-sp-3);
351 min-block-size: 2rem;
352 padding-inline: var(--rst-sp-3);
353}
354
355[data-sheet-tabs] {
356 display: flex;
357}
358
359[data-sheet-tabs] a {
360 align-items: center;
361 border-bottom: 2px solid transparent;
362 color: var(--rst-text-muted);
363 display: flex;
364 font-size: var(--rst-fs-sm);
365 padding-inline: var(--rst-sp-3);
366 text-decoration: none;
367}
368
369[data-sheet-tabs] a:hover {
370 color: var(--rst-text);
371}
372
373[data-sheet-tabs] a[aria-current] {
374 background: var(--rst-surface);
375 border-bottom-color: var(--rst-accent);
376 color: var(--rst-text);
377 font-weight: 600;
378}
379
380/* The size of the selection, not a sum of it. A total would be the
381 * client doing arithmetic on strings the server rendered, which is the
382 * one thing this design keeps out of JavaScript. */
383[data-selection-size] {
384 align-self: center;
385 color: var(--rst-text-faint);
386 font-size: var(--rst-fs-xs);
387}
388
389/* The transient note. Empty almost always, and the app's only way of
390 * saying something happened or did not: today, that a browser refused
391 * the menu's clipboard read, or that a paste is larger than one write
392 * can carry.
393 *
394 * It takes the slack rather than the live indicator, which is why the
395 * auto margin moved here — otherwise a note appearing shoves the
396 * connection state sideways every time one is shown. Truncates rather
397 * than wrapping: this strip is 2rem and a two-line message would push
398 * the grid. */
399/* A note the person has to act on. Muted grey in a strip under the
400 * grid is the right weight for "12 cells were not pasted" and the
401 * wrong one for "the menu could not paste, use the keyboard" — the
402 * second went unread on the live instance while the menu item read as
403 * simply broken. */
404[data-sheet-note][data-tone="warn"] {
405 color: var(--rst-tone-warning-fg);
406 font-weight: 550;
407}
408
409[data-sheet-note] {
410 align-self: center;
411 color: var(--rst-text-muted);
412 font-size: var(--rst-fs-xs);
413 margin-inline-start: auto;
414 overflow: hidden;
415 text-overflow: ellipsis;
416 white-space: nowrap;
417}
418
419/* The live indicator. It says connected or reconnecting and nothing
420 * else — presence is a later slice, and a dot that claims more than it
421 * knows is worse than no dot. */
422[data-sheet-live] {
423 align-self: center;
424 font-size: var(--rst-fs-xs);
425 color: var(--rst-tone-positive-fg);
426}
427
428[data-sheet-live][data-state="off"] {
429 color: var(--rst-tone-warning-fg);
430}
431
432/* isolation: the vendored grid's stylesheets stack with z-index values
433 * up to 10001. Contained here they compete only with each other, under
434 * the bar's 45 and the menus that float over the grid (spec §5.3). */
435[data-sheet-grid] {
436 flex: 1;
437 min-height: 0;
438 overflow: auto;
439 background: var(--rst-bg);
440 isolation: isolate;
441}
442
443/* ── The vendored grid ───────────────────────────────────────────────
444 *
445 * Jspreadsheet paints its own colours, hard-coded: #fff for the table,
446 * #f3f3f3 for the headers, #ccc for every border. None of them follow a
447 * theme, so all of them are restated here in tokens.
448 *
449 * The selectors are `.jss_*`. Jspreadsheet v5 renamed everything from
450 * v4's `.jexcel`, and this block was first written against `.jexcel` —
451 * so it matched nothing at all, and the grid stayed white inside dark
452 * chrome. Check the vendored stylesheet, not a memory of the last major
453 * version, when these stop working.
454 *
455 * Every rule that sets a background on something carrying text also
456 * sets a color. The library sets background-color on the headers and no
457 * color, so the text is inherited from the page: in dark mode that put
458 * light grey text on a light grey header and the row and column names
459 * became invisible. A background without a foreground beside it is the
460 * bug, not the colour. (The exceptions are the things with no text in
461 * them — the fill handle is a 1px knob.) */
462[data-sheet-grid] .jss_worksheet {
463 background-color: var(--rst-bg);
464 color: var(--rst-text);
465 border-right-color: var(--rst-line);
466 border-bottom-color: var(--rst-line);
467}
468
469[data-sheet-grid] .jss_worksheet > tbody > tr > td {
470 background-color: var(--rst-bg);
471 color: var(--rst-text);
472 border-top-color: var(--rst-line);
473 border-left-color: var(--rst-line);
474}
475
476/* The row and column names. Both headers, one look. */
477[data-sheet-grid] .jss_worksheet > thead > tr > td,
478[data-sheet-grid] .jss_worksheet > tbody > tr > td:first-child {
479 background-color: var(--rst-surface-2);
480 color: var(--rst-text-muted);
481 border-top-color: var(--rst-line);
482 border-left-color: var(--rst-line);
483 font-weight: 600;
484}
485
486/* The names of the selected column and row. The library puts .selected
487 * on the thead td but on the tbody TR — not on its first cell — so the
488 * two halves need different selectors. Written as `td:first-child.selected`
489 * the second half matched nothing at all, and a rule that matches nothing
490 * fails exactly like a rule nobody wrote: the vendored #dcdcdc stood, and
491 * a dark grid grew a light-grey slab down its left edge carrying
492 * --rst-text-muted (#a8b0ba) at 1.60:1. */
493[data-sheet-grid] .jss_worksheet > thead > tr > td.selected,
494[data-sheet-grid] .jss_worksheet > tbody > tr.selected > td:first-child {
495 background-color: var(--rst-accent-soft);
496 color: var(--rst-text);
497}
498
499/* ── The selection ───────────────────────────────────────────────────
500 *
501 * A range is a fill plus an outline. This file used to restate only the
502 * fill, and the outline is where every hard-coded colour in the vendored
503 * stylesheet lives:
504 *
505 * .highlight-right/-bottom border: 1px solid #000
506 * .highlight-top/-left border: 1px solid #000, plus a decorative
507 * box-shadow of #ccc just outside it
508 *
509 * In dark that renders backwards. The black borders disappear into the
510 * grid; the two that do not are overwritten by our own
511 * `td { border-top-color: var(--rst-line) }` above, which is one
512 * specificity step higher than the library's `.jss_worksheet .highlight-top`
513 * — so they become ordinary grid lines, while the #ccc shadow, which
514 * exists only to mask the grid line the border sits on, survives as the
515 * one bright thing on screen. What you get is a navy slab lit down its
516 * top and left and nowhere else, with no way to see where it ends.
517 *
518 * ── Why every rule below is scoped `> tbody > tr > td` ────────────────
519 * Two reasons, and the second is the one that bites.
520 *
521 * 1. The grid-line rule above is (0,2,3). A restatement written at the
522 * library's own `.jss_worksheet .highlight-top` is (0,2,0) and loses
523 * to it — a rule that parses, uses a token, and paints nothing.
524 * 2. sheet.html links the vendored stylesheets in the head block, which
525 * layout.html renders AFTER sheets.css. The library is later in
526 * source order, so equal specificity loses too. Ours must be
527 * strictly higher, never merely equal. That is also the real reason
528 * the vendored #dcdcdc beat the header rule above: both are (0,3,3),
529 * and the vendor came last.
530 *
531 * So: one outline colour, --rst-accent, on all four sides, above both.
532 * Measured on the fill it sits on, 6.59:1 in dark and 4.75:1 in light —
533 * both past 1.4.11's 3:1 for a component boundary. The shadow goes:
534 * with the outline in a colour that reads, masking the line underneath
535 * it buys nothing and costs the selection its symmetry.
536 *
537 * The library gives each of its three range states a different border
538 * STYLE — solid for the selection, dashed for the copy marquee, dotted
539 * for the fill-handle drag preview — and the style survives our
540 * colour-only override. That is the distinction doing the work, so all
541 * three take the same accent and stay told apart by shape. */
542[data-sheet-grid] .jss_worksheet > tbody > tr > td.highlight {
543 background-color: var(--rst-accent-soft);
544 color: var(--rst-text);
545}
546
547/* A link inside a selected cell. Vendored as the browser's own `blue`,
548 * which on the fill in dark is 2.4:1. */
549[data-sheet-grid] .jss_worksheet > tbody > tr > td.highlight > a {
550 color: var(--rst-accent);
551}
552
553[data-sheet-grid] .jss_worksheet > tbody > tr > td.highlight-top,
554[data-sheet-grid] .jss_worksheet > tbody > tr > td.selection-top,
555[data-sheet-grid] .jss_worksheet > tbody > tr > td.copying-top {
556 border-top-color: var(--rst-accent);
557 box-shadow: none;
558}
559
560[data-sheet-grid] .jss_worksheet > tbody > tr > td.highlight-left,
561[data-sheet-grid] .jss_worksheet > tbody > tr > td.selection-left,
562[data-sheet-grid] .jss_worksheet > tbody > tr > td.copying-left {
563 border-left-color: var(--rst-accent);
564 box-shadow: none;
565}
566
567[data-sheet-grid] .jss_worksheet > tbody > tr > td.highlight-right,
568[data-sheet-grid] .jss_worksheet > tbody > tr > td.selection-right,
569[data-sheet-grid] .jss_worksheet > tbody > tr > td.copying-right {
570 border-right-color: var(--rst-accent);
571}
572
573[data-sheet-grid] .jss_worksheet > tbody > tr > td.highlight-bottom,
574[data-sheet-grid] .jss_worksheet > tbody > tr > td.selection-bottom,
575[data-sheet-grid] .jss_worksheet > tbody > tr > td.copying-bottom {
576 border-bottom-color: var(--rst-accent);
577}
578
579/* The rows the fill handle is being dragged over — the preview of what
580 * a release would fill, NOT the range a formula is pointing at. There is
581 * separate formula reference chrome below. `parseFormulas: false` keeps
582 * calculation on the server. The library
583 * adds these classes from updateCopySelection, off `selectedCorner`.
584 * Vendored as rgba(0,0,0,0.05) — a darkening, which on a dark grid is
585 * nothing at all. */
586[data-sheet-grid] .jss_worksheet > tbody > tr > td.selection {
587 background-color: var(--rst-accent-soft);
588 color: var(--rst-text);
589}
590
591/* The anchor — the one cell the keyboard is on. The library paints it
592 * transparent on purpose so it reads out of the fill; our .highlight
593 * rule was opaque and took that back, leaving a range with no visible
594 * cursor in either scheme.
595 *
596 * It comes LAST among the SELECTION rules on purpose. It ties
597 * .highlight and .selection at (0,3,3), and the anchor is inside both
598 * while you drag the fill handle — so on a tie it has to be the later
599 * rule or the cursor disappears at exactly the moment you are aiming
600 * with it.
601 *
602 * **The ring is what actually marks it, and the background no longer
603 * can.** Cell fills are painted further down this file, at the same
604 * (0,3,3) and therefore later, so on a filled range a background-only
605 * anchor is indistinguishable from the rest of the selection — the
606 * exact failure the paragraph above guards against, arriving by a new
607 * route. An inset shadow survives any fill because it is drawn over
608 * one, and it costs no layout, so it cannot shift a cell by a pixel
609 * when the cursor lands on it.
610 *
611 * The background stays for the unfilled case, where it is what makes
612 * the anchor read as "not part of the tint". */
613[data-sheet-grid] .jss_worksheet > tbody > tr > td.highlight-selected {
614 background-color: var(--rst-bg);
615 color: var(--rst-text);
616 box-shadow: inset 0 0 0 2px var(--rst-accent);
617}
618
619/* The fill handle at the corner of the range: a black knob ringed in
620 * white, which in dark reads as a white dot with a dark centre — the
621 * inverse of the thing it is meant to be. It is 1px plus its ring, so
622 * it is the accent or it is invisible. */
623[data-sheet-grid] .jss_corner {
624 background-color: var(--rst-accent);
625 border-color: var(--rst-bg);
626}
627
628/* ── The right-click menu ────────────────────────────────────────────
629 *
630 * Not Jspreadsheet's: jsuites', which it binds to `contextmenu` whether
631 * or not the host app configures one. grid.js turns it off (its stock
632 * items bypass our write paths), so today it never opens. The restatement
633 * stays because the element is still mounted, and TestVendorPaintIsRestated
634 * lists it: vendored it is #fff with #555 text — a white card on a dark
635 * grid — should anything ever open it again.
636 *
637 * It is `position: fixed` and mounted inside the grid, so it is scoped
638 * the same way everything else here is. */
639[data-sheet-grid] .jcontextmenu {
640 background: var(--rst-surface);
641 color: var(--rst-text);
642 border-color: var(--rst-line-strong);
643 box-shadow: var(--rst-shadow-pop);
644}
645
646[data-sheet-grid] .jcontextmenu > div a {
647 color: var(--rst-text);
648}
649
650[data-sheet-grid] .jcontextmenu > div span {
651 color: var(--rst-text-muted);
652}
653
654[data-sheet-grid] .jcontextmenu > div:hover {
655 background: var(--rst-accent-soft);
656 color: var(--rst-text);
657}
658
659[data-sheet-grid] .jcontextmenu .jcontextmenu-disabled a,
660[data-sheet-grid] .jcontextmenu .jcontextmenu-disabled::before {
661 color: var(--rst-text-faint);
662}
663
664[data-sheet-grid] .jcontextmenu hr {
665 border-color: var(--rst-line);
666}
667
668/* The cell editor is an input the library drops into the td. Left
669 * alone it inherits the browser's own white. */
670[data-sheet-grid] .jss_worksheet td input,
671[data-sheet-grid] .jss_worksheet td textarea {
672 font: inherit;
673 letter-spacing: inherit;
674 text-align: inherit;
675 background-color: inherit;
676 color: inherit;
677}
678
679/* iOS zooms the page into any text control under 16px, and tokens.css
680 * brings controls up to 16px where the pointer is coarse. Two of ours
681 * set `font: inherit` with a selector that outweighs tokens.css's bare
682 * `input`, and fell back to 14px: the cell editor above, and estilo's
683 * title field in the bar. Same rule, same max(), restated here at their
684 * weight. TestNoZoomOnTap (make test-browser) measures both. */
685@media (pointer: coarse) {
686 [data-sheet-grid] .jss_worksheet td input,
687 [data-sheet-grid] .jss_worksheet td textarea,
688 [estilo-bar-title-input] {
689 font-size: max(1rem, 1em);
690 }
691}
692
693[data-sheet-grid] .jss_container,
694[data-sheet-grid] .jss_content {
695 background-color: var(--rst-bg);
696}
697
698/* A cell whose imported fill fails WCAG AA against its ink. Spec §11.1
699 * ruled that we retain such a fill rather than silently correcting it,
700 * so the fill stays and this marks it instead: a corner flag that costs
701 * nothing to ignore and is impossible to miss once you look. */
702[data-sheet-grid] td[data-contrast="low"] {
703 position: relative;
704}
705
706[data-sheet-grid] td[data-contrast="low"]::after {
707 content: "";
708 position: absolute;
709 inset-block-start: 0;
710 inset-inline-end: 0;
711 border-block-start: 0.5rem solid var(--rst-tone-warning-fg);
712 border-inline-start: 0.5rem solid transparent;
713 /* A halo in the page's own surface colour, so the flag survives the
714 * fill it is flagging.
715 *
716 * The flag marks a cell whose IMPORTED fill and ink fail AA, and the
717 * fill is an arbitrary colour out of somebody's file — including, in
718 * the case this app cites by name, Excel's #FFEB9C, against which an
719 * amber triangle is invisible in dark mode. A warning that disappears
720 * on exactly the cells it is about is not a warning.
721 *
722 * drop-shadow rather than a border or an outline: those two follow
723 * the box, and this triangle is drawn out of borders, so there is no
724 * box to put an edge on. filter traces the rendered shape. */
725 filter: drop-shadow(0 0 1px var(--rst-surface))
726 drop-shadow(0 0 1px var(--rst-surface));
727}
728
729/* ── The scriptless table ────────────────────────────────────────────
730 *
731 * Not a fallback in the apologetic sense: it is the only view that
732 * works everywhere, and every affordance the grid has exists here too. */
733[data-cell-table] {
734 border-collapse: collapse;
735 width: 100%;
736}
737
738[data-cell-table] th,
739[data-cell-table] td {
740 border: 1px solid var(--rst-line);
741 padding: var(--rst-sp-1) var(--rst-sp-2);
742 text-align: start;
743 vertical-align: top;
744}
745
746[data-cell-table] th {
747 background: var(--rst-surface-2);
748 color: var(--rst-text-muted);
749 font-weight: 600;
750}
751
752/* The cell link, which is every cell.
753 *
754 * Each cell is an anchor, because the link IS the edit affordance in a
755 * view with no script. Left at the browser's default that meant roughly
756 * 650 blue underlined links per page — data and emptiness in the same
757 * colour and the same weight, so `94200` and a blank cell differed only
758 * in glyph width. A spreadsheet you can only read cell by cell is not a
759 * spreadsheet, and this is the view the spec calls the product.
760 *
761 * So the cell reads as a cell and the affordance shows on approach. The
762 * anchor takes the cell's full width and a line of height, so the
763 * target is the whole line rather than the few pixels of the glyph —
764 * which is also what lets an empty cell be clickable without a &nbsp;
765 * standing in for content. It does not stretch to a row made taller by
766 * a wrapped neighbour; that was true of the &nbsp; it replaces too.
767 *
768 * :focus-visible carries the same treatment as :hover deliberately.
769 * Keyboard is the primary way through this view, and a focus ring alone
770 * on a 26-column grid is easy to lose. */
771[data-cell-table] td > a {
772 color: inherit;
773 text-decoration: none;
774 display: block;
775 min-block-size: 1.25rem;
776 /* Square, and the whole cell: the link reaches out over the cell's own
777 * padding and pads itself back in, so its hover fills the cell edge to
778 * edge. It was a 6px rounded pill round the text inside a square cell
779 * — on an empty cell a small floating blob (the square rule, spec §11:
780 * an in-flow highlight is square). */
781 margin: calc(-1 * var(--rst-sp-1)) calc(-1 * var(--rst-sp-2));
782 padding: var(--rst-sp-1) var(--rst-sp-2);
783}
784
785[data-cell-table] td > a:hover,
786[data-cell-table] td > a:focus-visible {
787 background: var(--rst-accent-soft);
788 color: var(--rst-accent);
789}
790
791/* Numbers right, text left — the alignment IS the type signal in a
792 * spreadsheet, and it is the one the grid gives for free and this view
793 * had to be told.
794 *
795 * Both spellings, because there are two of these tables. The editable
796 * one wraps every cell in an anchor, so the alignment has to reach the
797 * block anchor rather than the cell around it; the anonymous link view
798 * has no anchors, because there is nothing on it to edit. One rule for
799 * each, rather than one selector that quietly covers only the surface
800 * that happened to get tested. */
801[data-cell-table] td[data-kind="number"],
802[data-cell-table] td[data-kind="number"] > a {
803 text-align: end;
804}
805
806[data-cell-table] td form {
807 display: flex;
808 gap: var(--rst-sp-1);
809}
810
811[data-cell-table] td input[type="text"] {
812 inline-size: 100%;
813 min-inline-size: 6ch;
814}
815
816/* [data-cell-value] used to be styled here and is now gone. Nothing in
817 * any template or module has ever written that attribute, so the rule
818 * matched no element for the life of the branch — the same defect as
819 * the contrast flag two sections up, which had a rule and no attribute
820 * and therefore warned about cells it never marked.
821 *
822 * A selector that matches nothing is invisible to every test we have
823 * and to every screenshot, which is why it is worth saying it was here
824 * rather than deleting it silently: the next person to want a secondary
825 * line under a table cell should write the markup and the rule
826 * together, not find this and assume it works. */
827
828
829/* Import drop zone ───────────────────────────────────────────────────
830 *
831 * The dashed outline is the whole visual: it says "this box takes a
832 * file" without becoming a second, competing button beside the one that
833 * is already there. The field and its button inside it are untouched
834 * rastrillo, because with scripting off they are the entire feature and
835 * a drop zone that restyles them would be decorating something that
836 * cannot happen.
837 *
838 * The highlight is an ATTRIBUTE toggled by drop.js, not a class: it is
839 * transient state on one element, and the attribute spelling is what
840 * the rest of this app's own hooks use (data-sheet-surface,
841 * data-cell-table).
842 *
843 * transition on border-color only — a person dragging a file across the
844 * page crosses this box repeatedly, and animating the outline is the
845 * difference between a box that acknowledges the drag and one that
846 * flashes. */
847/* :not([data-drop-page]) on both, because there are two zones now and
848 only one of them is a box. The overlay on the sheet list carries
849 data-drop too — it has to, it is what drop.js looks for — and these
850 two selectors are 0-2-0 and 0-3-0, so without the exclusion they beat
851 [rst-modal-overlay]'s 0-1-0 and paint the sheet of glass over the
852 whole page in accent-soft. It renders as a flat blue screen with the
853 list gone: a defect a browser found and no test could. */
854[data-drop]:not([data-drop-page]) {
855 border: 1px dashed var(--rst-line);
856 border-radius: var(--rst-radius);
857 padding: var(--rst-sp-3);
858 transition: border-color 120ms ease, background-color 120ms ease;
859}
860
861[data-drop][data-drop-over]:not([data-drop-page]) {
862 border-color: var(--rst-accent);
863 background: var(--rst-accent-soft);
864}
865
866/* Somebody who has asked for less motion still needs to see the box
867 respond — so the change stays and only the animation goes. */
868@media (prefers-reduced-motion: reduce) {
869 [data-drop] {
870 transition: none;
871 }
872}
873
874/* ── The sheet list's drop overlay ───────────────────────────────────
875 *
876 * The list has no drop box. It has a full-window target that does not
877 * exist until a file is over the window, and drop.js is what reveals
878 * it — see the comment on [data-drop-page] in sheets.html.
879 *
880 * It borrows rastrillo's modal paint rather than growing its own, so
881 * the layer that appears over the list looks like every other layer in
882 * the suite. Only the four rules below are ours.
883 *
884 * THIS FIRST RULE IS LOAD-BEARING. [rst-modal-overlay] sets
885 * display: flex, and an author declaration beats the user agent's
886 * [hidden] { display: none } no matter how weak its selector looks.
887 * Without it the overlay covers the sheet list permanently and the page
888 * is unusable — with scripting off, where nothing would ever come along
889 * to hide it again. TestTheDropOverlayIsHiddenAtRest pins the markup;
890 * this pins the paint. */
891[data-drop-page][hidden] {
892 display: none;
893}
894
895/* A full-window overlay sits above the bar (spec §5.3): rastrillo's
896 * modal overlay is z-index 10, under the bar's 45, so without this the
897 * bar would paint over the top of the drop target. */
898[data-drop-page] {
899 z-index: 50;
900}
901
902[data-drop-page] [rst-modal-panel] {
903 max-width: 24rem;
904 transition: border-color 120ms ease;
905}
906
907/* The panel answers the drag, the same way the box on /sheets/import
908 does and with the same token, so the two read as one gesture. */
909[data-drop-page][data-drop-over] [rst-modal-panel] {
910 border-color: var(--rst-accent);
911}
912
913[data-drop-page-body] {
914 display: grid;
915 gap: var(--rst-sp-3);
916 justify-items: center;
917 padding: var(--rst-sp-5);
918 text-align: center;
919 width: 100%;
920}
921
922[data-drop-page-title] {
923 font-size: var(--rst-fs-base);
924 font-weight: 600;
925 margin: 0;
926}
927
928/* The chooser is for afterwards.
929 *
930 * While a file is over the window the panel has one thing to say, and a
931 * "Choose File" button beside "Drop it to import" offers a second way to
932 * do the thing the person is already doing. It appears when the overlay
933 * is up WITHOUT a drag over it, which is exactly the state a refused
934 * drop leaves behind — and then it is the way out of that refusal. */
935[data-drop-page-recover] {
936 display: grid;
937 gap: var(--rst-sp-3);
938 justify-items: center;
939 width: 100%;
940}
941
942[data-drop-page][data-drop-over] [data-drop-page-recover] {
943 display: none;
944}
945
946@media (prefers-reduced-motion: reduce) {
947 [data-drop-page] [rst-modal-panel] {
948 transition: none;
949 }
950}
951
952/* ── Cell formatting ─────────────────────────────────────────────────
953 *
954 * The paint for a cell's stored Format: bold, italic, alignment, fill
955 * and ink. Every rule is here, in the file the guards read, rather than
956 * in a stylesheet JavaScript generates — a generated sheet is invisible
957 * to TestOurStylesheetHasNoColourLiterals, TestEveryTokenWeUseExists
958 * and TestVendorPaintIsRestated alike, which is three reasons not to
959 * have one. paint.js only sets attributes and two custom properties.
960 *
961 * Specificity: [data-sheet-grid] .jss_worksheet > tbody > tr > td[attr]
962 * is (0,3,3) — one step above the grid-line rule at (0,2,3), as the
963 * restatement rule requires, and equal to the selection rules. So these
964 * are placed AFTER them: a filled cell keeps its fill while selected,
965 * and the selection outline does the work of showing the selection,
966 * which is what every spreadsheet does.
967 *
968 * A colour a person picks HERE is a hue and a chroma with no lightness
969 * — see store/models.go — and the lightness comes from the theme below.
970 * A colour an imported FILE authored is painted exactly as authored,
971 * in both themes, because spec §11.1 says we render a document as
972 * written and warn rather than silently correcting it. */
973
974/* light-dark() is a colour function and cannot hold a number, so these
975 * are written the long way. They still resolve before first paint:
976 * data-theme is server-rendered into the first bytes of the response
977 * (layout.html), so there is no flash of the wrong lightness. */
978:root {
979 --sheet-fill-l: 0.92;
980 --sheet-ink-l: 0.42;
981}
982
983@media (prefers-color-scheme: dark) {
984 :root:not([data-theme="light"]) {
985 --sheet-fill-l: 0.30;
986 --sheet-ink-l: 0.80;
987 }
988}
989
990:root[data-theme="dark"] {
991 --sheet-fill-l: 0.30;
992 --sheet-ink-l: 0.80;
993}
994
995[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-b] {
996 font-weight: 600;
997}
998
999[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-i] {
1000 font-style: italic;
1001}
1002
1003/* ── Alignment, and why these three rules carry !important ───────────
1004 *
1005 * Jspreadsheet writes `text-align` as an INLINE style on every data
1006 * cell, from the column's `align` or from `defaultColAlign`. An inline
1007 * declaration outranks any selector, at any specificity, so the
1008 * ordinary version of these rules set the attribute, reflected
1009 * aria-pressed on the button, and changed nothing on screen. The
1010 * toolbar looked like it worked. Only a browser could see it, and the
1011 * branch's own drive did not check alignment — bold and fill, which
1012 * both paint from properties the library does not touch, passed.
1013 *
1014 * !important is the correct tool here and not a shortcut: it is what
1015 * outranks an inline style, and there is no other. These are new paint
1016 * rather than a restatement of a vendored rule, so the restatement
1017 * guard is unaffected.
1018 *
1019 * The numeric rule is important for the same reason and comes FIRST,
1020 * so an explicit alignment beats it on source order — both are
1021 * important and (0,3,3), so nothing else separates them. */
1022[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-n] {
1023 text-align: end !important;
1024}
1025
1026[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-a="left"] {
1027 text-align: start !important;
1028}
1029
1030[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-a="center"] {
1031 text-align: center !important;
1032}
1033
1034[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-a="right"] {
1035 text-align: end !important;
1036}
1037
1038/* Ours: hue and chroma from the cell, lightness from the theme. */
1039[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-fill] {
1040 background-color: oklch(var(--sheet-fill-l) var(--sheet-c) var(--sheet-h));
1041}
1042
1043[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-ink] {
1044 color: oklch(var(--sheet-ink-l) var(--sheet-kc) var(--sheet-kh));
1045}
1046
1047/* The author's: painted as the file wrote it. --sheet-fx and --sheet-k
1048 * carry a hex that came from a document, which is why a literal is
1049 * acceptable here and nowhere else in this file — it is data, set by
1050 * script from the document, not a colour this stylesheet chose. */
1051[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-fx] {
1052 background-color: var(--sheet-fx);
1053}
1054
1055[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-k] {
1056 color: var(--sheet-k);
1057}
1058
1059/* ── The table view paints what the grid paints ──────────────────────
1060 *
1061 * The same attributes, because the server writes the same attributes
1062 * (tablepaint.go). PRODUCT.md's second principle is why: the scriptless
1063 * path is the product, and a screen that can SET bold but cannot SHOW
1064 * it is the worse half of the feature.
1065 *
1066 * Written as its own block rather than by widening the grid's selectors
1067 * to cover both. Those carry `.jss_worksheet` and `!important` for
1068 * reasons that belong entirely to the vendored library — an inline
1069 * text-align the library writes on every data cell — and none of that
1070 * is true here. A shared selector would carry the library's problem
1071 * onto a table that does not have it, and would make the restatement
1072 * guard's job ambiguous.
1073 *
1074 * No !important, and that is the tell that these are new paint rather
1075 * than a restatement: nothing writes an inline text-align on this
1076 * table. The one inline style a cell here carries is the custom
1077 * properties, which set values rather than win a cascade. */
1078[data-cell-table] td[data-b],
1079[data-cell-table] td[data-b] > a {
1080 font-weight: 600;
1081}
1082
1083[data-cell-table] td[data-i],
1084[data-cell-table] td[data-i] > a {
1085 font-style: italic;
1086}
1087
1088/* The numeric rule comes first so an explicit alignment beats it on
1089 * source order, the same ordering the grid's block relies on. Both are
1090 * (0,2,1) here, so nothing else separates them.
1091 *
1092 * The existing [data-kind="number"] rule above already right-aligns a
1093 * numeric cell; these three are the explicit override, and they have to
1094 * come after it in the file for the same reason. */
1095[data-cell-table] td[data-a="left"],
1096[data-cell-table] td[data-a="left"] > a {
1097 text-align: start;
1098}
1099
1100[data-cell-table] td[data-a="center"],
1101[data-cell-table] td[data-a="center"] > a {
1102 text-align: center;
1103}
1104
1105[data-cell-table] td[data-a="right"],
1106[data-cell-table] td[data-a="right"] > a {
1107 text-align: end;
1108}
1109
1110/* Ours: hue and chroma from the cell, lightness from the theme, so a
1111 * fill somebody picked follows their light/dark choice. Identical to
1112 * the grid's rule and deliberately so — a fill must not be one colour
1113 * on one screen and another on the other. */
1114[data-cell-table] td[data-fill] {
1115 background-color: oklch(var(--sheet-fill-l) var(--sheet-c) var(--sheet-h));
1116}
1117
1118[data-cell-table] td[data-ink],
1119[data-cell-table] td[data-ink] > a {
1120 color: oklch(var(--sheet-ink-l) var(--sheet-kc) var(--sheet-kh));
1121}
1122
1123/* The author's, painted as the file wrote it (spec §11.1). --sheet-fx
1124 * and --sheet-k carry a hex that came out of a document — data, checked
1125 * by previewsvg.go's safeHex before it was ever written into the
1126 * attribute, and not a colour this stylesheet chose. */
1127[data-cell-table] td[data-fx] {
1128 background-color: var(--sheet-fx);
1129}
1130
1131[data-cell-table] td[data-k],
1132[data-cell-table] td[data-k] > a {
1133 color: var(--sheet-k);
1134}
1135
1136/* The cell edit form's formatting row. Six selects and a button, on
1137 * their own line under the value field: side by side they would push
1138 * the table's column wider than the page. */
1139[data-cell-format] {
1140 display: flex;
1141 flex-wrap: wrap;
1142 gap: var(--rst-sp-1);
1143 margin-block-start: var(--rst-sp-1);
1144}
1145
1146[data-cell-format] select {
1147 font-size: var(--rst-fs-xs);
1148 padding-block: 0.15rem;
1149}
1150
1151/* ── The list's thumbnails ───────────────────────────────────────────
1152 *
1153 * A 26x34 slot at the row's left edge, the same box in the same place
1154 * as the Docs app's, so the two file lists have the same row height and
1155 * read as one product rather than two apps that both grew thumbnails.
1156 *
1157 * The slot is reserved whether or not there is anything to draw: an
1158 * empty sheet renders the empty bordered box, so a row never changes
1159 * height when a sheet gains its first cell. */
1160/* The list card is a <ul> and rastrillo's [rst-list] is written for a
1161 * <div> — its comment says "a list card holds rows", and every rule it
1162 * declares is background, border and radius. It resets nothing, because
1163 * a <div> has nothing to reset.
1164 *
1165 * A <ul> does. The UA stylesheet gives it `padding-inline-start: 40px`
1166 * and `list-style: disc`, and both survived: the 40px is the gap Paul
1167 * saw to the left of the thumbnail on 2026-09-03, and it is 40px of
1168 * card that every row is indented inside. The markers were invisible
1169 * only because they render in that padding, outside the row's own box.
1170 *
1171 * The element stays a <ul>. A list of sheets is a list, and the fix for
1172 * a framework rule that assumed a <div> is to reset what the <div>
1173 * never had — not to drop the semantics that make the list announce as
1174 * one to a screen reader.
1175 *
1176 * The block margin is REPLACED rather than removed. Nothing in
1177 * rastrillo stacks the children of [rst-page] — there is no
1178 * `> * + *` rule — so what separated this card from the import form
1179 * above it was the <ul>'s own UA `margin-block: 1em`. Zeroing the
1180 * shorthand would have closed that gap as a side effect of fixing the
1181 * padding, so the margin is restated from the token scale, which is
1182 * where every other gap on the page comes from. */
1183[data-sheet-list] {
1184 list-style: none;
1185 margin-block: var(--rst-sp-4);
1186 margin-inline: 0;
1187 padding: 0;
1188}
1189
1190/* [rst-lrow] is a grid, and --rst-cols is rastrillo's own hook for
1191 * saying what its columns are. Setting it is how a row gains a
1192 * thumbnail without overriding anything: without it the row has one
1193 * column and the picture stacks above the title. */
1194[data-sheet-list] [rst-lrow] {
1195 --rst-cols: 26px 1fr;
1196}
1197
1198/* Below 800px rastrillo sets grid-template-columns on [rst-lrow]
1199 * DIRECTLY rather than through --rst-cols, so the hook above stops
1200 * being read and the thumbnail lands in the title's column with an
1201 * empty one trailing. This is (0,2,0) against the framework's (0,1,0),
1202 * so it wins wherever it sits in the file. */
1203@media (max-width: 800px) {
1204 [data-sheet-list] [rst-lrow] {
1205 grid-template-columns: 26px minmax(0, 1fr);
1206 }
1207}
1208
1209/* The share page's two lists: links and people. The same reset the
1210 * sheet list needs — rastrillo's [rst-list] keeps the browser's 40px
1211 * list indent — and two columns, so "Remove" and "Turn off" sit beside
1212 * the row instead of dropping under its name: [rst-lrow] is a one-column
1213 * grid until --rst-cols says otherwise. Below 800px rastrillo sets the
1214 * columns directly, so the narrow rule restates them. */
1215[data-share-list] {
1216 list-style: none;
1217 margin-block: var(--rst-sp-3) var(--rst-sp-4);
1218 margin-inline: 0;
1219 padding: 0;
1220}
1221
1222[data-share-list] > [rst-lrow] {
1223 --rst-cols: minmax(0, 1fr) auto;
1224}
1225
1226@media (max-width: 800px) {
1227 [data-share-list] > [rst-lrow] {
1228 grid-template-columns: minmax(0, 1fr) auto;
1229 }
1230}
1231
1232[data-share-list] form {
1233 margin: 0;
1234}
1235
1236[data-preview-slot] {
1237 display: block;
1238 block-size: 34px;
1239 inline-size: 26px;
1240}
1241
1242[data-preview] {
1243 display: block;
1244}
1245
1246/* The Format menu's two resize forms. A row rather than a stack: the
1247 * label, the number and the button read as one control, and the panel
1248 * is already 15rem wide enough to hold them. */
1249[data-size-form] {
1250 align-items: center;
1251 display: flex;
1252 gap: var(--rst-sp-2);
1253 padding: var(--rst-sp-1) var(--rst-sp-2);
1254}
1255
1256/* nowrap is the point of the three rules below, and they are sized
1257 * backwards from it. The panel is 15rem, so a row has 224px of content
1258 * box; at 4rem of field and a button padded to its word, the label is
1259 * left about 100px and "Column width" sets on one line. At the numbers
1260 * this started with — a 4.5rem field and a default-padded button — it
1261 * had 48px and broke over two lines, and the two forms then disagreed
1262 * about their own height. */
1263/* A fixed label column, not flex: 1. The two forms are siblings and
1264 * cannot share a grid, so the only thing that lines their fields and
1265 * their two Set buttons up is both labels reserving the same width —
1266 * with flex: 1 each label took its own text's width and the rows sat
1267 * 33px out of step with each other. */
1268[data-size-form] label {
1269 color: var(--rst-text-muted);
1270 flex: 0 0 7rem;
1271 font-size: var(--rst-fs-sm);
1272 white-space: nowrap;
1273}
1274
1275[data-size-form] input {
1276 inline-size: 4rem;
1277}
1278
1279[data-size-form] button {
1280 padding-inline: var(--rst-sp-2);
1281}
1282
1283/* On a touch screen the field is 16px type (no zoom on tap) and the
1284 * button is padded for a finger, and the row no longer fits: Set hung
1285 * 11-21px out of the panel's right edge. The label column gives the room
1286 * back; "Column width" still sets on one line in 5.5rem, and both rows
1287 * keep the same column, so they stay in step. */
1288@media (pointer: coarse) {
1289 [data-size-form] label {
1290 flex-basis: 5.5rem;
1291 }
1292}
1293
1294/* The referenced range is separate from the cell receiving the formula. */
1295[data-sheet-grid] .jss_worksheet > tbody > tr > td[data-formula-reference] {
1296 outline: 2px solid var(--rst-accent);
1297 outline-offset: -2px;
1298}
1299
1300/* One entry in a comment thread (comments.html): author and time, then
1301 the text, always shown. Entries after the first get a rule between. */
1302[data-comment] > p {
1303 margin: 0;
1304}
1305
1306[data-comment] > p + p {
1307 margin-block-start: var(--rst-sp-1);
1308 white-space: pre-line;
1309}
1310
1311[data-comment] + [data-comment] {
1312 border-block-start: 1px solid var(--rst-line);
1313 margin-block-start: var(--rst-sp-3);
1314 padding-block-start: var(--rst-sp-3);
1315}
1316
1317[data-comment] [rst-row-sub] {
1318 display: inline;
1319 margin-inline-start: var(--rst-sp-2);
1320}
1321