/* Polish layer, loaded last. Cross-cutting refinements that are not owned by one component
   file: icon weight, press feedback, popover entrances, AI-working states, the chat and the
   workspace Ask box. Only new rules here (no markup depends on this file), so it merges cleanly
   with work on chat.rs / settings.rs / ws_ai.rs. Every animation below is either feedback (press),
   a state change (popover opens, AI is working) or progress; all of it is switched off under
   prefers-reduced-motion by the global rule in style.css, and the few with layout-visible
   end-states have explicit static fallbacks at the bottom. */

/* ---------- Iconography: one stroke weight ---------- */
/* Icons come from several helpers with stroke widths 1.75 to 2.5; normalise the root weight.
   A child path that sets its own width (the pause bars, the step glyph) keeps it. */
.icon-btn svg, .ws-btn svg, .gc-btn svg, .gc-menu-item svg, .ws-fbtn svg, .btn svg, .link-btn svg { stroke-width: 1.75; }
.send-btn svg { stroke-width: 2; }

/* ---------- Press feedback on compact controls ---------- */
.icon-btn, .ws-btn, .gc-btn, .mode-btn, .ws-seg-btn, .side-tab, .chip-btn, .ws-chip, .ws-swatch, .tbl-btn {
  transition-property: background, color, border-color, box-shadow, transform, filter;
  transition-duration: var(--dur-1);
  transition-timing-function: var(--ease);
}
.icon-btn:active:not(:disabled), .ws-btn:active:not(:disabled), .gc-btn:active:not(:disabled),
.mode-btn:active, .ws-seg-btn:active, .side-tab:active, .chip-btn:active:not(:disabled),
.ws-chip:active, .ws-swatch:active, .send-btn:active:not(:disabled) { transform: var(--press); }

/* ---------- Focus: keep rings visible inside clipped or tight containers ---------- */
.ws-menu-item:focus-visible, .gc-menu-item:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.tbl-cell:focus-visible { outline-offset: -2px; }
.ws-swatch:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 50%; }
.ws-swatch:hover { transform: scale(1.08); }
.ws-swatch[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent); }

/* ---------- Popovers open with a short settle ---------- */
.ws-menu, .ws-settings, .ws-style { animation: pol-pop var(--dur-2) var(--ease-out); transform-origin: top left; }
.ws-menu.right, .ws-row-menu, .ws-settings { transform-origin: top right; }
@keyframes pol-pop { from { opacity: 0; transform: translateY(-4px) scale(0.98); } to { opacity: 1; transform: none; } }
.ws-toast { animation: pol-toast var(--dur-2) var(--ease-out); }
@keyframes pol-toast { from { opacity: 0; transform: translate(-50%, 8px); } to { opacity: 1; transform: translateX(-50%); } }
.ws-menu-item { display: flex; align-items: center; gap: var(--s-2); }

/* The style controls open inside the row itself. */
/* (no card: the controls are part of the opened row) */
.ws-style-sec + .ws-style-sec { padding-top: var(--s-3); border-top: 1px solid var(--border); }
.ws-show { font-size: var(--fs-sm); }

/* ---------- Empty and first-run states ---------- */
.graph-empty > * , .chat-empty > * { animation: pol-rise var(--dur-3) var(--ease-out) both; }
.graph-empty > :nth-child(2), .chat-empty > :nth-child(2) { animation-delay: 60ms; }
.graph-empty > :nth-child(3), .chat-empty > :nth-child(3) { animation-delay: 120ms; }
.graph-empty > :nth-child(n+4), .chat-empty > :nth-child(n+4) { animation-delay: 180ms; }
@keyframes pol-rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.graph-empty-title, .chat-empty-title { letter-spacing: -0.005em; text-wrap: balance; }
.graph-empty-sub { text-wrap: pretty; line-height: 1.55; }
.graph-empty .btn { margin-top: var(--s-4); }
/* the empty-state curve draws itself once */
.graph-empty .art-curve { stroke-dasharray: 2 8; }
.chat-empty .art-curve { stroke-dasharray: 160; stroke-dashoffset: 160; animation: pol-draw 900ms var(--ease-out) 150ms forwards; }
@keyframes pol-draw { to { stroke-dashoffset: 0; } }

/* ---------- Chat ---------- */
.chat-empty-sub { max-width: 34ch; line-height: 1.55; }
/* example prompts: the label is a short name, not an eyebrow */
.example { gap: 3px; padding: 11px 14px 12px; }
.example-label { text-transform: none; letter-spacing: 0; font-size: var(--fs-sm); font-weight: 650; color: var(--text); }
.example-text { color: var(--text-2); font-size: var(--fs-sm); line-height: 1.4; }
.example:hover .example-label { color: var(--accent-text); }
.example:active { transform: translateY(0) scale(0.99); }
.msg { line-height: 1.5; }
.msg-assistant { border-color: var(--border); }
.msg-meta { font-variant-numeric: tabular-nums; }

/* The AI is working: the reply bubble gets a moving accent edge and the elapsed time stays steady. */
.generating { position: relative; overflow: hidden; border-color: var(--accent-soft); }
.generating::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: linear-gradient(var(--accent), var(--accent-soft), var(--accent)) 0 0 / 100% 200%;
  animation: pol-edge 1.6s linear infinite;
}
@keyframes pol-edge { to { background-position: 0 -200%; } }
.generating-text { font-weight: 600; color: var(--text); }
.elapsed { font-variant-numeric: tabular-nums; color: var(--text-3); }
.progress { height: 4px; border-radius: 2px; }
.progress i { background: linear-gradient(90deg, var(--accent-soft), var(--accent)); border-radius: 2px; }
.dots i { animation-name: pol-dot; animation-duration: 1.2s; animation-timing-function: var(--ease); }
@keyframes pol-dot { 0%, 100% { opacity: 0.35; transform: translateY(0); } 40% { opacity: 1; transform: translateY(-3px); } }

.composer { padding: var(--s-3) var(--s-3) var(--s-3) var(--s-3); }
.composer-input { transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease); }
.composer-input:hover:not(:focus) { border-color: var(--text-3); }
.send-btn:not(:disabled) { box-shadow: 0 2px 6px rgb(42 69 184 / 0.28); }
.suggest .chip-btn, .ws-ask-chips .chip-btn { border-color: transparent; }
.suggest .chip-btn:disabled, .ws-ask-chips .chip-btn:disabled { opacity: 0.5; }

/* ---------- Workspace Ask box ---------- */
.ws-ask { position: relative; gap: var(--s-2); padding-top: var(--s-3); }
.ws-ask-input { border-radius: var(--r-pill); padding-inline: 16px; }
.ws-ask-input:hover:not(:focus) { border-color: var(--text-3); }
.ws-ask-form .btn-primary { border-radius: var(--r-pill); padding-inline: 16px; min-height: 38px; }
.ws-ask-reply { border-radius: var(--r-md); box-shadow: var(--shadow-1); animation: pol-rise var(--dur-2) var(--ease-out); }
.ws-ask-text { line-height: 1.45; }
/* while the model works: an indeterminate accent line runs along the box's top edge, and the
   status line reads as a status, not as body text */
.ws-ask:has(.ws-ask-status)::before {
  content: ""; position: absolute; left: 0; right: 0; top: -1px; height: 2px;
  background: linear-gradient(90deg, transparent, var(--accent) 40%, var(--accent) 60%, transparent) -40% 0 / 40% 100% no-repeat, var(--accent-soft);
  animation: pol-scan 1.4s var(--ease) infinite;
}
@keyframes pol-scan { to { background-position: 140% 0, 0 0; } }
.ws-ask-status { min-height: 28px; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
.ws-ask-status .btn { margin-left: auto; }
/* the canvas side shows the same "working" state, so the eye on the graph sees it too */
.app:has(.ws-ask-status) .ws-toolbar { box-shadow: inset 0 -2px 0 var(--accent-soft); }

/* ---------- Settings dialogs (modal shell only; the form content is left alone) ---------- */
.modal { overflow: hidden; }
.modal-head h2 { letter-spacing: -0.005em; }
.ws-settings-head h2 { letter-spacing: -0.005em; }
.ws-settings-sec + .ws-settings-sec { padding-top: var(--s-3); border-top: 1px solid var(--border); }
.ws-win-row .ws-input { font-variant-numeric: tabular-nums; }

/* ---------- Gallery cards ---------- */
.graph-card { transition: box-shadow var(--dur-2) var(--ease), border-color var(--dur-2) var(--ease), transform var(--dur-2) var(--ease); }
.graph-card-head h3 { letter-spacing: -0.005em; }
.gc-count { font-variant-numeric: tabular-nums; }
.gc-eqs code { box-shadow: inset 0 0 0 1px var(--border); background: var(--bg); }

/* ---------- Header ---------- */
.brand { padding: 2px 4px 2px 2px; transition: background var(--dur-1) var(--ease); }
.brand:hover .logo-tile { filter: brightness(1.08); }
.side-tab { min-width: 4.5rem; }

/* ---------- Static fallbacks for reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  .chat-empty .art-curve { stroke-dashoffset: 0; animation: none; }
  .graph-empty > *, .chat-empty > * { animation: none; }
  .ws-ask:has(.ws-ask-status)::before { animation: none; background: var(--accent); }
  .generating::before { animation: none; background: var(--accent); }
  .ws-pending::before { animation: none; background-size: 100% 100%; }
  .vt:has(.vt-status) .vt-grid:empty::before, .vt:has(.vt-status) .vt-grid:empty::after { animation: none; }
  .vt-card.vt-regen .vt-thumb::after { animation: none; background: rgb(255 255 255 / 0.35); }
  .icon-btn:active, .ws-btn:active, .gc-btn:active, .mode-btn:active, .ws-seg-btn:active, .side-tab:active,
  .chip-btn:active, .ws-chip:active, .ws-swatch:active, .send-btn:active, .ws-dot:active, .ws-fbtn:active { transform: none; }
}

/* ---------- Windows high contrast / forced colours ---------- */
@media (forced-colors: active) {
  .ws-dot span { background: CanvasText; }
  .ws-row:focus-within::after { box-shadow: inset 0 0 0 2px Highlight; }
}

/* ---------- Phones: a compact AI footer, so the open sheet keeps room for the rows ---------- */
@media (max-width: 767px) {
  /* quick requests become one scrolling line instead of a wrapping block */
  .ws-ask-chips { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; padding-bottom: 2px; }
  .ws-ask-chips li { flex: 0 0 auto; }
  .ws-ask { padding-top: var(--s-2); gap: var(--s-1); }
  .ws-ask-reply { max-height: 6rem; }
  /* the automatic-change / "no model" notes: smaller, two lines of text at most */
  .pnudge { padding: var(--s-2) var(--s-3); gap: var(--s-1); }
  .pnudge-text {
    font-size: var(--fs-xs); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
  }
  /* its buttons stay on one scrolling row; the pill below repeats what the note says, so it hides */
  .pnudge, .pnudge-text, .pnudge .pactions { min-width: 0; }
  .pnudge .pactions { gap: var(--s-1); flex-wrap: wrap; }
  .pnudge .pactions .btn { flex: 0 1 auto; min-width: 0; white-space: normal; text-align: center; min-height: 40px; }
  aside.left:has(.pnudge) .provider-row { display: none; }
  .provider-row { padding-top: 0; }
}
/* Short phones (a browser with toolbars on a small screen): drop the quick-request chips, which
   are suggestions only; the provider pill and the Ask box stay. */
@media (max-width: 767px) and (max-height: 760px) {
  .ws-ask-chips { display: none; }
}

/* ---------- Touch: 44 px hit areas without moving anything ---------- */
@media (pointer: coarse) {
  .ws-dot, .ppill { position: relative; }  /* the grip is already positioned */
  .ws-grip::after, .ws-dot::after, .ppill::after { content: ""; position: absolute; }
  .ws-grip::after { inset: -8px -11px; }
  .ws-dot::after { inset: -6px; }
  .ppill::after { inset: -10px -4px; }
  .ws-ask-form .btn-primary, .ws-ask-form .btn { min-height: var(--touch); }
  .ws-name { min-height: var(--touch); }
  .ppill { min-height: 28px; }
  .skip-link { min-height: var(--touch); display: inline-flex; align-items: center; }
  .pnudge .pactions .btn { min-height: var(--touch); }
}

/* ---------- Phones: menus open as a scrollable panel above the bottom edge, not clipped by the sheet ---------- */
@media (max-width: 767px) {
  .ws-menu, .ws-menu.right, .ws-row-menu {
    position: fixed; left: var(--s-3); right: var(--s-3); top: auto; bottom: var(--s-3);
    min-width: 0; max-height: 70dvh; overflow-y: auto; overscroll-behavior: contain; transform-origin: bottom center;
  }
}

/* ---------- Round 4: the list gets the panel; AI notice and chips step aside ---------- */
/* The first-run notice is a one-line strip; the quick-request chips scroll on one line and appear
   only while the Ask box has focus, so at 1280x720 the expression list keeps at least four rows. */
.ws-ask .pnudge { margin: 0; padding: var(--s-1) var(--s-3); flex-direction: row; align-items: center; flex-wrap: wrap; gap: var(--s-1) var(--s-2); }
.ws-ask .pnudge-text { flex: 1 1 14rem; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.ws-ask-chips { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; list-style: none; margin: 0; padding: 0 0 2px; }
.ws-ask-chips li { flex: 0 0 auto; }
.ws-ask:not(:focus-within) .ws-ask-chips { display: none; }
.ws-ask-reply { max-height: 5rem; }
.ws-list { min-height: 12rem; }
@media (min-width: 768px) {
  aside.left .ws-list { flex: 1 1 auto; min-height: 232px; }
}

/* 375 phones: every control is at least 40 px, not only under pointer:coarse. */
@media (max-width: 767px) {
  .ws-tb-btn { min-height: 40px; min-width: 40px; }
  .ws-fbtn { width: 40px; height: 40px; }
  .icon-btn, .icon-btn.lg { width: 40px; height: 40px; }
  .mode-btn { min-height: 40px; min-width: 40px; }
  .side-tab { min-height: 40px; }
  .btn-sm { min-height: 40px; height: auto; padding-inline: 14px; }
  .ws-tools .ws-btn.icon, .ws-row .ws-tools .ws-btn.icon { width: 40px; min-height: 40px; }
  .ws-toolbar { max-width: 100%; }
}
@media (pointer: coarse) {
  .ws-tools .ws-btn.icon, .ws-row .ws-tools .ws-btn.icon { width: var(--touch); min-height: var(--touch); }
  .ws-grip { width: var(--touch); height: var(--touch); }
}

/* ---------- Round 5: phones keep rows on screen; touch size on any narrow viewport; resting tools ---------- */
@media (max-width: 767px) {
  /* The AI notice, provider pill and quick chips live in the AI Chat tab and the settings; the
     Manual sheet keeps only the one-line Ask box so three rows and "Add expression" stay visible. */
  .ws-ask .pnudge, .ws-ask .provider-row, .ws-ask-chips { display: none; }
  .ws-ask { padding: var(--s-1) var(--s-3) var(--s-2); }
  /* The try-these hint is a first-run aid only. */
  .ws-list:not(:has(.ws-rows > .ws-row:only-child)) .ws-hint { display: none; }
  /* Undo / redo / Add / name on one line, 48px. */
  .ws-list-head { min-height: 48px; padding: 2px var(--s-2); gap: 2px; flex-wrap: nowrap; }
  .ws-list-head .ws-name { min-height: 40px; }
}
@media (max-width: 640px), (pointer: coarse) {
  .ws-list-head .ws-add-btn, .ws-list-head .ws-btn.icon, .ws-add-btn { min-height: var(--touch); }
  .ws-list-head .ws-btn.icon, .ws-tools .ws-btn.icon, .ws-row .ws-tools .ws-btn.icon { width: var(--touch); min-height: var(--touch); }
  .ws-row .ws-dot { width: var(--touch); height: var(--touch); min-width: var(--touch); min-height: var(--touch); flex: 0 0 var(--touch); box-sizing: border-box; }
  .ws-row .ws-grip { width: var(--touch); height: var(--touch); }
  .ws-swatch { width: var(--touch); height: var(--touch); }
  .ws-slice-close { width: var(--touch) !important; min-height: var(--touch) !important; }
  .ws-slice-val input, .ws-seg-btn { min-height: var(--touch); }
  .ws-gk-btn { width: var(--touch); height: var(--touch); }
  .gc-btn { min-width: var(--touch); height: var(--touch); }
}

/* ---------- Round 6: mobile (375x667) ---------- */
@media (max-width: 767px) {
  /* The open sheet is sized to its content (up to about 45vh) and sits in the page flow under the graph,
     so the graph keeps every pixel the sheet does not use. Closed, it is still the fixed peek bar. */
  .app.sheet-open .app-main { flex-direction: column; }
  .app.sheet-open aside.left {
    position: relative; order: 2; flex: 0 1 auto; height: auto; max-height: 42vh; max-height: 42dvh;
    transform: none; box-shadow: 0 -4px 16px rgb(0 0 0 / 0.12);
  }
  .app.sheet-open .right, .app.sheet-open .graph-panel { order: 1; flex: 1 1 0; min-height: 0; height: auto; }
  .app.sheet-open .graph-panel, .app.sheet-open .ws-right { padding-bottom: var(--s-3); }
  .app.sheet-open .ws-right { padding-bottom: 0; }
  /* one slim 44 px handle row (tabs + chevron), then the 44 px Add / name / undo / redo row */
  .side-head { height: 44px; padding: 0 var(--s-2); }
  .side-head::before { top: 2px; }
  .side-tabs { padding: 2px; }
  .side-tab { min-height: 36px; padding: 4px 14px; }
  .app.sheet-open .side-body, .app.sheet-open .side-pane { min-height: 0; }
  .ws-list { min-height: 0; flex: 0 1 auto; }
  .ws-list-head { min-height: 44px; padding: 0 var(--s-2); }
  .ws-ask { padding-top: var(--s-1); padding-bottom: var(--s-1); }
  /* No dead space under "Add expression": the hint and empty fill collapse. */
  .ws-new-row { margin-bottom: 0; }
  /* canvas controls: one horizontal row on the graph, nothing hidden by the sheet */
  .ws-float { flex-direction: row; align-items: flex-start; flex-wrap: nowrap; gap: 6px; top: 6px; right: 6px; }
  .ws-float .ws-fgroup { flex-direction: row; }
  .ws-fgroup .ws-fbtn + .ws-fbtn { border-top: 0; border-left: 1px solid var(--plate-line); }
  .ws-float .ws-fbtn { width: 40px; height: 40px; }
  /* the first-run AI notice: two lines and one scrolling row of buttons, dismissible */
  .chat-scroll > .pnudge { margin: var(--s-2) var(--s-3) 0; padding: var(--s-1) var(--s-2); gap: 4px; }
  .chat-scroll > .pnudge .pnudge-text { -webkit-line-clamp: 2; }
  .chat-scroll > .pnudge .pactions { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .chat-scroll > .pnudge .pactions .btn { flex: 0 0 auto; white-space: nowrap; }
}

/* Coarse pointers: 44 px wins over the 40 px phone sizes above. */
@media (pointer: coarse) {
  .icon-btn, .icon-btn.lg { width: var(--touch); height: var(--touch); }
  .side-tab, .mode-btn, .ws-tb-btn, .ws-name { min-height: var(--touch); }
  .mode-btn, .ws-tb-btn { min-width: var(--touch); }
  .ws-fbtn, .ws-float .ws-fbtn { width: var(--touch); height: var(--touch); }
  .ws-slice-range { height: var(--touch); }
  .pnudge .pactions .btn, .chat-scroll > .pnudge .pactions .btn { min-height: var(--touch); }
}
@media (max-width: 767px) and (pointer: coarse) {
  .side-tabs { padding: 0; background: transparent; gap: 4px; }
  .ws-list-head { min-height: var(--touch); }
  .ws-list-head .ws-name { min-height: var(--touch); }
}

/* ---------- Round 7: phones, one 44 px head row, a sheet sized to its rows, a slim canvas chrome ---------- */
.ws-ph-only { display: none !important; }
@media (max-width: 767px) {
  :root { --peek-h: 44px; }
  .ws-ph-only.ws-btn { display: inline-flex !important; }
  .ws-ph-only.ws-menu-item { display: block !important; }
  /* Manual, open: the tab strip folds into the Add / name / undo / redo row (AI + hide buttons live there). */
  .app.sheet-open.mode-manual .side-head { display: none; }
  .ws-list-head { gap: 0; padding: 0 2px; }
  .ws-list-head .ws-ph-ai { min-width: 40px; padding: 0 6px; font-weight: 700; color: var(--accent-text); }
  .ws-list-head .ws-add-btn { padding: 4px 10px; }
  .ws-list-head .ws-add-btn > span { display: none; }
  .ws-list-head .ws-name { font-size: var(--fs-md, 1rem); padding-inline: 6px; }
  /* The open sheet fits its rows: tighter rows, no dead space under "Add expression", a capped height
     that always leaves the canvas at least 270 px (header 52 + toolbar 51 + 270). */
  .app.sheet-open aside.left { max-height: min(45vh, calc(100vh - 376px)); max-height: min(45dvh, calc(100dvh - 376px)); }
  .ws-row-main { min-height: 52px; }
  .ws-body { padding-block: 0; }
  .ws-body math-field, .ws-body .mathfield-fallback { padding-block: 2px; min-height: 36px; }
  .ws-body .mathfield-host { min-height: 36px; }
  .ws-new-row { min-height: 52px; border-bottom: 0; }
  .ws-list { padding-bottom: 0; }
  .ws-empty { margin: var(--s-2) var(--s-3); padding: var(--s-2) var(--s-3); }
  .ws-empty-sub { display: none; }
  /* top toolbar: one scroll-free 48 px row; Share moves into the Export menu */
  .ws-toolbar { padding: 2px var(--s-2); min-height: 48px; flex-wrap: nowrap; }
  .ws-toolbar .ws-share-btn { display: none; }
  .ws-toolbar .ws-tb-label { display: none; }
  .ws-toolbar .ws-tb-btn { padding-inline: 8px; }
  .ws-split { flex: 0 0 auto; }
  /* the slice settings: a single row (axis, value, range); the status only shows when it is an error */
  .ws-slice-panel { flex-wrap: nowrap; gap: var(--s-2); padding: 2px var(--s-2); overflow-x: auto; min-height: 44px; scrollbar-width: none; }
  .ws-slice-val input { width: 4.5em; }
  .ws-slice-range { min-width: 90px; }
  .ws-slice-panel .ws-seg-btn { min-height: 40px; }
  .ws-slice-status { flex: 0 0 auto; flex-basis: auto; white-space: nowrap; }
  .ws-slice-status:not(.err) { display: none; }
  /* with the slice row open the sheet gives up its share of the height again */
  .app.sheet-open:has(.ws-slice-panel) aside.left { max-height: min(45vh, calc(100vh - 430px)); max-height: min(45dvh, calc(100dvh - 430px)); }
  /* a table row keeps its header (number and name) pinned above the grid while its cells are edited */
  .ws-row:has(.tbl):focus-within > .ws-row-main {
    position: sticky; top: 44px; z-index: 15; background: var(--surface); box-shadow: 0 1px 0 var(--border);
  }
  .tbl { padding-bottom: 4px; }
  /* the AI notice in the chat tab: compact, scrolls with the messages and can be dismissed */
  .chat-scroll > .pnudge { margin-block: var(--s-1) 0; }
}
@media (max-width: 767px) and (pointer: coarse) {
  .ws-list-head .ws-ph-ai, .ws-list-head .ws-ph-close, .ws-list-head .ws-add-btn { min-width: var(--touch); min-height: var(--touch); }
  .ws-toolbar { min-height: 52px; }
  .app.sheet-open:has(.ws-slice-panel) aside.left { max-height: min(45vh, calc(100vh - 436px)); max-height: min(45dvh, calc(100dvh - 436px)); }
  .app.sheet-open aside.left { max-height: min(45vh, calc(100vh - 380px)); max-height: min(45dvh, calc(100dvh - 380px)); }
}

/* ---------- Round 8: 375 phones, one 48 px chrome row and a full-height canvas ---------- */
@media (max-width: 640px) {
  /* Manual: the header and the graph toolbar share one 48 px row (logo only, then the graph tools).
     The theme switch moves into the Export menu; AI settings stay on the sheet's AI button. */
  .app.mode-manual .app-header { height: 48px; padding: 0 var(--s-2); gap: 0; position: relative; z-index: 26; }
  .app.mode-manual .brand-name, .app.mode-manual .header-right { display: none; }
  .app.mode-manual .brand { min-width: 40px; min-height: 40px; justify-content: center; }
  .app.mode-manual .ws-toolbar {
    position: fixed; top: 0; left: 48px; right: 0; height: 48px; min-height: 48px; z-index: 27; box-sizing: border-box;
    padding: 2px var(--s-2) 2px 0; gap: 4px; background: transparent; border-bottom: 0; overflow-x: auto; scrollbar-width: none;
  }
  .app.mode-manual .ws-toolbar > * { flex: 0 0 auto; }
  .app.mode-manual .ws-toolbar .ws-grow { flex: 1 1 0; min-width: 0; }
  .app.mode-manual .ws-toolbar .mode-seg { padding: 0; gap: 0; }
  .app.mode-manual .ws-toolbar .ws-tb-btn { padding-inline: 6px; }
  /* Closed sheet: a 44 px round chevron at the bottom right, over the canvas, not a bar under it. */
  .app.mode-manual:not(.sheet-open) .ws-right, .app.mode-manual:not(.sheet-open) .graph-panel { padding-bottom: 0; }
  .app.mode-manual:not(.sheet-open) aside.left {
    transform: none; left: auto; right: 8px; bottom: 8px; width: 44px; height: 44px; max-height: none;
    border: 1px solid var(--border); border-radius: 22px; box-shadow: var(--shadow-3); overflow: hidden;
  }
  .app.mode-manual:not(.sheet-open) aside.left .side-body,
  .app.mode-manual:not(.sheet-open) aside.left .side-tabs { display: none; }
  .app.mode-manual:not(.sheet-open) .side-head { width: 44px; height: 44px; padding: 0; justify-content: center; }
  .app.mode-manual:not(.sheet-open) .side-head::before { display: none; }
  .app.mode-manual:not(.sheet-open) .side-head .sheet-toggle { width: 44px; height: 44px; }
  /* Open sheet: the last row ("Add expression") scrolls clear of the Ask bar. */
  .app.sheet-open .ws-list { scroll-padding-bottom: 56px; padding-bottom: 12px; }
  /* A 28 px gutter for the row number, so it no longer sits under the 44 px colour dot. */
  .ws-row-main { grid-template-columns: 72px minmax(0, 1fr) auto; }
  .ws-gutter { padding-left: 28px; box-sizing: border-box; }
  .ws-num { left: 6px; }
}
@media (max-width: 640px) and (pointer: coarse) {
  .app.mode-manual .ws-toolbar { min-height: 48px; }
  .app.mode-manual .ws-toolbar .ws-tb-btn { min-width: var(--touch); }
}
@media (max-width: 767px) {
  /* a new table scrolls so its x1 / y1 header sits under the sticky Add row and table title */
  .ws-row .tbl-grid thead { scroll-margin-top: 96px; }
}

/* ---------- Round 9: roomier expression field, a sheet that fits its rows, compact AI notice ---------- */
/* Desktop Manual: the notice is one line plus Dismiss; the providers live behind the AI settings pill. */
.ws-ask .pnudge-text { -webkit-line-clamp: 1; }
.ws-ask .pnudge .pactions .btn-outline { display: none; }
.ws-ask .pnudge .pactions { flex: 0 0 auto; flex-wrap: nowrap; }

@media (max-width: 767px) {
  /* the sheet sizes to its rows (never below ~272 px, so "Add expression" stays on screen at 360x640) */
  .app.sheet-open aside.left {
    display: flex; flex-direction: column; height: auto;
    max-height: max(min(45vh, calc(100vh - 376px)), 272px); max-height: max(min(45dvh, calc(100dvh - 376px)), 272px);
  }
  .app.sheet-open aside.left .side-body, .app.sheet-open aside.left .side-pane { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
  .app.sheet-open .ws-list { height: auto; max-height: none; min-height: 0; flex: 0 1 auto; overflow-y: auto; }
  .app.sheet-open .ws-ask { flex-shrink: 0; }
  /* the Add row is a footer of the list: it sticks to the bottom edge instead of scrolling off */
  .ws-new-row { position: static; background: var(--surface); box-shadow: 0 -1px 0 var(--border); }
  .ws-row:has(.tbl):focus-within { min-height: 190px; }
  .ws-row:has(.tbl):focus-within .tbl { padding-bottom: 8px; }
}
@media (max-width: 640px) {
  /* the row gives the field the width: slimmer gutter, dot, grip and tool buttons (mathfield 210 px+ at 375) */
  .ws-row-main { grid-template-columns: 56px minmax(0, 1fr) auto; }
  .ws-gutter { padding-left: 20px; }
  .ws-num { left: 4px; }
  .ws-row .ws-dot { width: 36px; height: 36px; min-width: 36px; min-height: 36px; flex: 0 0 36px; }
  .ws-row .ws-grip { width: 20px; height: 36px; }
  .ws-row .ws-tools { padding: 0 2px; }
  .ws-row .ws-tools .ws-btn.icon { width: 34px; min-width: 0; min-height: var(--touch); padding-inline: 0; }
  .ws-body { padding-left: 0; }
  .ws-body math-field, .ws-body .mathfield-fallback { padding-inline: 6px; }
}
@media (max-width: 400px) {
  .ws-body .mathfield-host, .ws-body math-field { overflow-x: auto; }
  .ws-body math-field, .ws-body .mathfield-fallback { font-size: 15px; }
  /* the 3D toolbar fits in 327 px: tighter buttons, the Persp / Ortho pair shrinks */
  .app.mode-manual .ws-toolbar { gap: 2px; }
  .app.mode-manual .ws-toolbar .mode-btn { min-width: 36px; padding-inline: 6px; }
  .app.mode-manual .ws-toolbar .ws-tb-btn { padding-inline: 4px; min-width: 36px; }
  .app.mode-manual .ws-toolbar .ws-seg-btn { padding-inline: 5px; font-size: 12px; min-width: 0; }
}
@media (max-width: 400px) and (pointer: coarse) {
  .app.mode-manual .ws-toolbar .mode-btn, .app.mode-manual .ws-toolbar .ws-tb-btn { min-width: var(--touch); }
}

/* ---------- Round 10: the sheet fits three rows, the Add row follows the last row ---------- */
@media (max-width: 767px) {
  /* content-sized; up to 55vh but never below a 270 px canvas under the 48 px chrome */
  .app.sheet-open aside.left,
  .app.sheet-open:has(.ws-slice-panel) aside.left {
    height: auto; max-height: min(55vh, calc(100vh - 48px - 290px)); max-height: min(55dvh, calc(100dvh - 48px - 290px));
  }
  .app.sheet-open:has(.ws-slice-panel) aside.left { max-height: min(55vh, calc(100vh - 100px - 270px)); max-height: min(55dvh, calc(100dvh - 100px - 270px)); }
  .app.sheet-open .ws-list { scroll-padding-bottom: 0; padding-bottom: 0; }
  /* the Ask box is a fixed 49 px strip with no pill or notice, and stays while a table row is focused */
  .ws-ask { display: flex; flex: 0 0 49px; height: 49px; min-height: 49px; box-sizing: border-box; overflow: hidden; }
  .ws-ask .ppill, .ws-ask .pnudge, .ws-ask .provider-row { display: none; }
  /* 3D toolbar: Persp / Ortho shrink to P / O so Slice stays on screen */
  .ws-seg[aria-label="Projection"] .ws-seg-btn { font-size: 0; min-width: 40px; padding-inline: 0; }
  .ws-seg[aria-label="Projection"] .ws-seg-btn::before { font-size: 13px; }
  .ws-seg[aria-label="Projection"] .ws-seg-btn:first-child::before { content: "P"; }
  .ws-seg[aria-label="Projection"] .ws-seg-btn:last-child::before { content: "O"; }
  .app.mode-manual .ws-toolbar .ws-seg[aria-label="Projection"] .ws-seg-btn { font-size: 0; padding-inline: 0; min-width: 36px; }
}
@media (pointer: coarse) {
  .ws-row .ws-tools .ws-eye, .ws-row .ws-tools .ws-del, .ws-row .ws-tools .ws-more,
  .ws-eye, .ws-del, .ws-more { min-width: var(--touch); }
  .ws-row .ws-dot { width: var(--touch); height: var(--touch); min-width: var(--touch); min-height: var(--touch); flex: 0 0 var(--touch); }
}

/* ---------- Round 11: mobile sheet leftovers ---------- */
@media (max-width: 767px) {
  /* a table row keeps the canvas at least 290 px at 375x667 (header 48 + canvas 290 + sheet) */
  .app.sheet-open:has(.ws-row .tbl) aside.left { max-height: min(56vh, calc(100vh - 300px)); max-height: min(56dvh, calc(100dvh - 300px)); }
  .ws-row:has(.tbl):focus-within { min-height: 120px; }
  /* when a table makes the list scroll, Add expression stays pinned above the Ask bar */
  .app.sheet-open:has(.ws-row .tbl) .ws-new-row { position: sticky; bottom: 0; z-index: 2; }
  /* toolbar menus (Export, Save) open under the 48 px chrome row and above the sheet */
  .ws-toolbar:has(.ws-menu) { z-index: 60; }
  .ws-toolbar .ws-menu, .ws-toolbar .ws-menu.right {
    position: fixed; top: 52px; right: 8px; left: auto; bottom: auto; z-index: 70; min-width: 200px; max-width: calc(100vw - 16px);
    transform-origin: top right;
  }
}
@media (max-width: 640px) {
  /* Share is in the Export menu on phones, so Send to gallery / More save options stay on screen */
  .app.mode-manual .ws-toolbar .ws-share-btn { display: none; }
  .app.mode-manual .ws-toolbar .ws-split { margin-left: auto; scroll-snap-align: end; }
  .app.mode-manual .ws-toolbar { scroll-snap-type: x proximity; }
}
@media (pointer: coarse) {
  .ws-row .ws-tools .ws-btn.icon.ws-eye, .ws-row .ws-tools .ws-btn.icon.ws-del, .ws-row .ws-tools .ws-btn.icon.ws-more { min-width: var(--touch); }
}
/* back at desktop width the sheet layout lets go (no stuck 324 px aside after a resize) */
@media (min-width: 768px) {
  .app.sheet-open .app-main { flex-direction: row; }
  .app.sheet-open aside.left, .app.sheet-open:has(.ws-slice-panel) aside.left {
    position: static; order: 0; flex: 0 0 auto; height: 100%; max-height: none; transform: none; box-shadow: none;
  }
  .app.sheet-open .right, .app.sheet-open .graph-panel { order: 0; flex: 1; height: auto; }
}

/* ---------- Round 12: coarse-pointer sheet height, notice, empty state, toolbar width ---------- */
@media (max-width: 767px) and (pointer: coarse) {
  /* the coarse toolbar is 48 px (44 px buttons): keep a 290 px canvas at 667 and 640 high */
  .app.sheet-open aside.left, .app.sheet-open:has(.ws-slice-panel) aside.left {
    max-height: max(min(45vh, calc(100vh - 384px)), 264px); max-height: max(min(45dvh, calc(100dvh - 384px)), 264px);
  }
  .app.sheet-open:has(.ws-slice-panel) aside.left { max-height: min(55vh, calc(100vh - 100px - 290px)); max-height: min(55dvh, calc(100dvh - 100px - 290px)); }
  .app.sheet-open:has(.ws-row .tbl) aside.left { max-height: min(56vh, calc(100vh - 300px)); max-height: min(56dvh, calc(100dvh - 300px)); }
}
@media (max-width: 767px) {
  /* the AI notice is one 44 px row: text (ellipsis) then buttons; the chat column cannot squeeze it */
  .chat-scroll > .pnudge { flex: 0 0 auto; flex-direction: row; flex-wrap: wrap; align-items: center; padding-block: var(--s-1); }
  .chat-scroll > .pnudge .pnudge-text { flex: 1 1 100%; min-width: 0; width: auto; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; white-space: normal; overflow: hidden; }
  .chat-scroll > .pnudge .pactions { flex: 1 1 100%; flex-wrap: nowrap; overflow: visible; }
  .chat-scroll > .pnudge .pactions .btn { flex: 0 0 auto; white-space: nowrap; padding-inline: 8px; }
  /* Add expression stays pinned above the Ask bar for every sheet, not only table sheets */
  .app.sheet-open .ws-list { scroll-padding-bottom: 56px; }
  .app.sheet-open .ws-new-row { position: sticky; bottom: 0; z-index: 2; }
  /* tighter empty state so Add expression clears the Ask bar */
  .ws-empty { margin: var(--s-2) var(--s-3); padding: var(--s-2) var(--s-3); }
  .ws-empty-sub { margin-bottom: var(--s-2); }
  .ws-empty-ex { gap: var(--s-1); }
  /* no dead rail to the right of a table */
  .ws-row .tbl { padding-right: 0; }
}
@media (max-width: 420px) and (pointer: coarse) {
  .app.mode-manual .ws-toolbar, .ws-toolbar { gap: 0; }
}

/* 3D in dark mode: the plate (3D canvas, tick labels and slice inset) deliberately stays one clean white
   sheet in both themes, so the surfaces, tick halos and the orange cut keep one contrast. No dimming
   filter (no grey cast): it sits on the dark chrome as a white card with a 1px keyline, and the inset
   carries the same keyline plus a deep shadow. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ws-stage.gc-white { box-shadow: inset 0 0 0 1px var(--border-strong); }
  :root:not([data-theme="light"]) .ws-slice-frame { border-color: var(--border-strong); box-shadow: 0 0 0 1px rgb(255 255 255 / 0.18), 0 4px 16px rgb(0 0 0 / 0.6); }
}
:root[data-theme="dark"] .ws-stage.gc-white { box-shadow: inset 0 0 0 1px var(--border-strong); }
:root[data-theme="dark"] .ws-slice-frame { border-color: var(--border-strong); box-shadow: 0 0 0 1px rgb(255 255 255 / 0.18), 0 4px 16px rgb(0 0 0 / 0.6); }
/* the toolbar scrolls on a phone, but never snaps away from the 1D button at the start */
@media (max-width: 640px) {
  .app.mode-manual .ws-toolbar { scroll-snap-type: none; }
}
