/* ============================================================================
   shell.css — CheckIt  (task #892)
   The floating-pane shell: unbroken chrome (title bar + status bar), an
   activity rail at 640+, a bottom nav on phones, and the view floating in a
   rounded pane inset by a gutter.

   ── WHAT THIS REPLACES ───────────────────────────────────────────────────
   CheckIt shipped TWO nav surfaces and a 1200px desktop breakpoint:
     .sidenav  — display:none until 1200px
     .tabbar   — the phone bottom bar, visible below 1200px
   So every tablet from 640 to 1199 — i.e. EVERY iPad, in both orientations —
   got the phone layout and phone chrome. That is the bug #892 exists for.
   Both <nav> blocks are gone from index.html; app.css's .sidenav / .tabbar /
   .tab rules are left alone (nothing matches them now, and app.css is
   hand-edited by Shaun — this retrofit must stay revertable by deleting two
   <link> tags).

   ── CURATED, NOT WHOLESALE. AND NO kit.css. ──────────────────────────────
   Same call LinkIt made at #816, for the same measured reason: a shared
   component kit and app.css both write rules for the same ~24 classes
   (.btn .input .icon-btn .chip .badge .modal .toast .switch .row .segmented
   plus the modifier words .primary/.ghost/.danger/.muted that CheckIt
   composes onto .btn). Loading one would put two stylesheets in a specificity
   fight over every control in the app. So: FRAME ONLY. app.css keeps every
   component rule, unchanged.

   Deliberately left out — CheckIt has no content for them: pane tab strips,
   resizable seams, multi-pane layouts, inspector/side panes, pane-head/foot,
   breadcrumbs.

   Load order (index.html):
     reset.css -> theme.css -> theme-cyan.css -> shell-tokens.css
     -> app.css -> shell.css
   shell.css loads LAST so the frame selectors win; app.css still owns every
   component.

   ⚠️ NEW FILE — must be in the SHELL array in service-worker.js or it 404s
   offline. `npm run check:shell` now covers CSS and will say so.
   ========================================================================= */

/* ── Frame ────────────────────────────────────────────────────────────────
   reset.css already does box-sizing and html,body{height:100%}; app.css
   already does .hidden{display:none!important}. Not repeated. */

/* app.css makes <body> the fixed flex frame (display:flex, 100dvh, overflow
   hidden) and its 1200px block flips it to flex-direction:row for the old
   side nav. .app-frame is the frame now, so body goes back to being a plain
   block — which also makes that flex-direction:row rule inert rather than
   something to hunt down and delete. */
body {
  display: block;
  margin: 0;
  background: var(--frame);       /* beats app.css's background: var(--bg) */
  overflow: hidden;               /* the PAGE never scrolls — the pane does */
  overscroll-behavior: none;      /* stops iOS rubber-banding the whole frame */
}

/* Skip link — first focusable element, jumps into the pane. CheckIt never had
   one; a fixed frame with three chrome regions in front of the content is
   exactly the case it exists for. */
.skip {
  position: fixed; top: -100px; left: var(--sp-2); z-index: 999;
  padding: 10px var(--sp-3); border-radius: var(--r-sm);
  background: var(--accent); color: var(--accent-ink);
  font-weight: 800; text-decoration: none;
  transition: top var(--dur-1) var(--ease-out);
}
.skip:focus { top: var(--sp-2); }

/* The title bar and status bar span BOTH columns — that is the structural
   reason nothing can cut the chrome.

   FOUR rows, not three. The bottom nav is a real GRID ROW, not a fixed
   overlay, because that is the property CheckIt's old .tabbar had and it is
   worth keeping (app.css §2): the scroll region genuinely ENDS where the nav
   begins, so no view has to reserve bottom padding to avoid being covered,
   and hiding the nav inside a run hands that space straight back to the list
   with no viewport arithmetic. At 640+ --bottomnav-h is 0 and .bottomnav is
   display:none, so the row collapses to nothing. */
.app-frame {
  height: 100dvh;                 /* dvh, not vh — the URL bar. app.css §1 explains. */
  display: grid;
  grid-template-columns: var(--rail-w) 1fr;
  grid-template-rows:
    calc(var(--titlebar-h) + env(safe-area-inset-top))
    1fr
    var(--statusbar-h)
    calc(var(--bottomnav-h) + env(safe-area-inset-bottom));
  grid-template-areas:
    "title  title"
    "rail   work"
    "status status"
    "bottom bottom";
  overflow: hidden;
  min-height: 0;
}
.titlebar  { grid-area: title;  z-index: var(--z-chrome); }
.rail      { grid-area: rail;   z-index: var(--z-rail); }
.workspace { grid-area: work;   min-width: 0; min-height: 0; display: flex; }
.statusbar { grid-area: status; z-index: var(--z-chrome); }
.bottomnav { grid-area: bottom; z-index: var(--z-chrome); }

/* ── Title bar ────────────────────────────────────────────────────────────
   NOTE: this element does NOT carry .app-header, so app.css's bordered
   --header-height rules do not apply. Its CHILDREN (.brand, .brand-logo,
   .header-tools, .icon-btn, .btn, .sync-dot-btn) are still styled entirely by
   app.css — which is how the chrome ends up in CheckIt's cyan, not a
   borrowed palette. */

.titlebar {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 0 var(--sp-2);
  padding-top: env(safe-area-inset-top);
  padding-left: max(var(--sp-2), env(safe-area-inset-left));
  padding-right: max(var(--sp-2), env(safe-area-inset-right));
  background: var(--frame);       /* no border — the frame tone does the separating */
  min-width: 0;
}
/* flex:1 so .tb-screen has room to fill on a phone, and so app.css's
   `.header-tools { margin-left: auto }` still pins the tools to the right. */
.tb-left { flex: 1 1 auto; display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }

/* The current screen name. Phone only — it replaces the wordmark, so a phone
   gets ONE row of chrome carrying its own identity instead of a logo you are
   not using. Painted by js/app.js on every route change. */
.tb-screen {
  display: none; flex: 1; min-width: 0;
  font-weight: 800; font-size: var(--font-size-base); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── Activity rail (tablet + desktop) ─────────────────────────────────── */

.rail {
  display: none;                  /* phone: off — the bottom nav replaces it */
  flex-direction: column; align-items: center;
  gap: 2px; padding: var(--sp-2) 0;
  background: var(--frame-2);     /* recessed tone separates it, no border */
  overflow: hidden;               /* the rail never scrolls — .rail-nav does */
}
@media (min-width: 640px) { .rail { display: flex; } }

/* The nav group scrolls if CheckIt ever grows past the rail's height; the
   pinned group below (Settings, Menu) always stays on screen. Without this
   the rail clips SILENTLY — no scrollbar, no fade, the items simply are not
   there. A phone in landscape at 640+ is a short viewport and hits it. */
.rail-nav {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  overflow-y: auto; overflow-x: hidden;
  overscroll-behavior: contain;
  scrollbar-width: none;
}
.rail-nav::-webkit-scrollbar { display: none; }

/* 44px, CheckIt's glove floor (theme.css note 2) — not a 40px desk size. */
.rail-item {
  position: relative;
  width: var(--tap); height: var(--tap); flex: none;
  display: grid; place-items: center;
  border: 0; background: transparent; color: var(--ink-soft);
  border-radius: var(--r-md); cursor: pointer; text-decoration: none;
  transition: color var(--transition), background var(--transition);
}
.rail-item svg { width: 22px; height: 22px; }
.rail-item:hover { background: var(--frame); color: var(--ink); }
.rail-item.active { color: var(--accent); }
/* 2px accent bar on the left edge marks the current section. */
.rail-item.active::before {
  content: ""; position: absolute; inset: 8px auto 8px 0;
  width: 2px; border-radius: 0 2px 2px 0; background: var(--accent);
}
.rail-group {
  display: flex; flex-direction: column; gap: 2px; align-items: center; flex: none;
  padding-top: var(--sp-2); border-top: 1px solid var(--line); width: var(--tap);
}
.rail-item:focus-visible {
  outline: none; box-shadow: 0 0 0 3px var(--accent-soft);
}

/* Tooltip — condensed rail items are icon-only, so the label has to be
   reachable. This is the ONLY place a nav label is not permanently visible.

   ⚠️ position: FIXED, not absolute — and that swap IS the fix. Ported from
   LinkIt #849, where as `absolute` this tooltip never rendered once: it sits
   inside TWO clip boxes — .rail{overflow:hidden} above, and .rail-nav, whose
   overflow-x can never be `visible` because declaring `overflow-x:visible`
   beside `overflow-y:auto` computes BACK to `auto`. The tooltip's origin is
   x≈58px against a 52px clip, so it was clipped by both at every rail width.

   A fixed box's containing block is the VIEWPORT, so no ancestor's overflow
   can clip it, and .rail-nav goes on scrolling. Two things make this
   deterministic rather than lucky:
     · every inset stays `auto`, so the box keeps its STATIC position — where
       it would have been anyway — and the offsets below are measured from the
       rail item, not from the viewport.
     · `place-self: start` is LOAD-BEARING. .rail-item is display:grid with
       place-items:center, which Chrome applies to the static position too, so
       without it the tooltip lands centred INSIDE the icon and shifts with
       the length of its own label.
   ⚠️ Do not add transform / filter / backdrop-filter / contain / will-change
   to .rail, .rail-nav or .app-frame. Any of those makes that ancestor the
   containing block for fixed descendants and silently brings the clip back. */
.rail-item::after {
  content: attr(data-tip);
  position: fixed; inset: auto; place-self: start;
  margin-left: 58px;                /* the 44px item + 4px rail padding + 10px gap */
  /* 22px = the item's vertical centre. The --rail-scroll correction is not
     decoration: a fixed box keeps the static position it had BEFORE its
     scroller moved, so once .rail-nav actually scrolls every tooltip renders
     scrollTop px too low and labels the wrong icon. app.js publishes the
     offset on .rail-nav; the fallback is 0px, so the pinned items (outside
     the scroller) and a boot where that never ran are both still correct. */
  margin-top: calc(22px - var(--rail-scroll, 0px));
  transform: translateY(-50%);
  padding: 5px 9px; border-radius: var(--r-sm);
  background: var(--ink); color: var(--bg);
  font-weight: 700; font-size: var(--font-size-xsm); line-height: 1; white-space: nowrap;
  opacity: 0; pointer-events: none; transition: opacity var(--dur-2);
  z-index: var(--z-popover);
}
.rail-item:hover::after, .rail-item:focus-visible::after { opacity: 1; }

/* ════════════════════════════════════════════════════════════════════════
   EXPANDED + OVERLAY rail
   Everything above is the condensed 52px strip and is unchanged. Everything
   below applies only once <html data-rail> says the labels are visible.
   ═══════════════════════════════════════════════════════════════════════ */

html[data-rail="expanded"] .rail,
html[data-rail="overlay"]  .rail { align-items: stretch; padding: var(--sp-2); }

html[data-rail="expanded"] .rail-nav,
html[data-rail="overlay"]  .rail-nav,
html[data-rail="expanded"] .rail-group,
html[data-rail="overlay"]  .rail-group { align-items: stretch; width: 100%; }

/* icon · label. No badges in CheckIt's rail, so two columns, not three.
   Height stays --tap at every pointer type — this app is touch-first even on
   a desktop-sized tablet, so there is no @media(pointer:coarse) special case
   to get wrong. */
html[data-rail="expanded"] .rail-item,
html[data-rail="overlay"]  .rail-item {
  width: 100%; height: var(--tap);
  display: grid;
  grid-template-columns: 22px 1fr;
  align-items: center;
  justify-items: start;
  gap: 10px;
  padding: 0 var(--sp-3);
  border-radius: var(--r-md);
}

.rail-label { display: none; }
html[data-rail="expanded"] .rail-label,
html[data-rail="overlay"]  .rail-label {
  display: block; min-width: 0;
  font-weight: 700; font-size: var(--font-size-sm); line-height: 1; color: inherit;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Nothing reaches the ellipsis at 184px — it is there so a future long label
   degrades instead of breaking the rail. */

/* Active: bar + colour + fill. The fill is labelled-state-only, deliberately
   — a 2px bar beside a 44px square reads fine, but beside a 160px row it is a
   mark at one end of a mostly-empty row. Three cues also survive a
   colour-blind viewer, which is the same rule the tick row follows (app.css
   §7: three signals, never one). --accent-soft is exactly what the old
   .sidenav .tab.active used, so the labels move into the rail carrying their
   existing treatment. */
html[data-rail="expanded"] .rail-item.active,
html[data-rail="overlay"]  .rail-item.active { background: var(--accent-soft); }
html[data-rail="expanded"] .rail-item.active::before,
html[data-rail="overlay"]  .rail-item.active::before { inset: 6px auto 6px 0; }
html[data-rail="expanded"] .rail-item.active .rail-label,
html[data-rail="overlay"]  .rail-item.active .rail-label { color: var(--accent); font-weight: 800; }

/* ⚠️ Tooltips OFF whenever the label is visible, and the second reason is not
   cosmetic. This tooltip is position:fixed, and the block comment above warns
   that a transform on .rail makes .rail the containing block for fixed
   descendants, silently restoring the clip. The overlay animates with a
   transform. Killing the tooltip in the labelled states is what makes that
   transform safe — one rule, both problems. */
html[data-rail="expanded"] .rail-item::after,
html[data-rail="overlay"]  .rail-item::after { content: none; }

/* ── The tablet overlay (640–1023) ─────────────────────────────────────────
   That band is touch — there is no hover, so a tooltip was never a fix there.
   The grid track stays 52px, so the pane does NOT reflow: nothing is taken
   from the content when the drawer opens. */

html[data-rail="overlay"] .rail {
  position: fixed;
  top: calc(var(--titlebar-h) + env(safe-area-inset-top));
  bottom: var(--statusbar-h);
  left: 0;
  width: 184px;
  z-index: var(--z-drawer);
  box-shadow: var(--shadow-lg);
  transform: translateX(-100%);          /* start state */
  transition: transform var(--dur-2) var(--ease-out);
}
html.rail-in[data-rail="overlay"] .rail { transform: translateX(0); }

/* Transform ONLY — never width or a layout property.
   The scrim covers the PANE, not the chrome: full-bleed would dim the very
   toggle you press to close, and a dimmed control you can still operate reads
   as broken. */
.rail-scrim {
  display: none;
  position: fixed;
  top: calc(var(--titlebar-h) + env(safe-area-inset-top));
  bottom: var(--statusbar-h); left: 0; right: 0;
  background: var(--scrim-soft);
  z-index: var(--z-scrim);
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease-out);
}
html[data-rail="overlay"] .rail-scrim { display: block; }
html.rail-in[data-rail="overlay"] .rail-scrim { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  html[data-rail="overlay"] .rail,
  .rail-scrim { transition: none; }
}

/* ── The rail toggle ──────────────────────────────────────────────────────
   Far left of .tb-left, before the brand — standard position (VS Code, Slack,
   Linear) and directly above the column it controls. Reuses CheckIt's own
   .icon-btn so app.css paints it. */
#rail-toggle .ti-close,
#rail-toggle .ti-open { display: none; }
html[data-rail="condensed"] #rail-toggle .ti-open,
html[data-rail="expanded"]  #rail-toggle .ti-close,
html[data-rail="overlay"]   #rail-toggle .ti-close { display: block; }
/* A control with nothing to control is worse than no control. */
html[data-rail="off"] #rail-toggle { display: none; }

/* ── The pane ─────────────────────────────────────────────────────────────
   The gutter is the pane's margin, so the frame shows on all four edges and
   the pane reads as floating on the chrome. On a phone --gutter and
   --pane-radius are both 0, so this is full-bleed and costs nothing — the
   flat mobile case is the default and the floating shell is the enhancement
   (Shaun's standard, 2026-08-27). */

.pane {
  position: relative;
  flex: 1; min-width: 0;
  min-height: 0;                  /* load-bearing: without it the body won't scroll */
  margin: var(--gutter);
  display: flex; flex-direction: column;
  /* Bare var(--pane), deliberately no fallback (#1217). The pane once computed
     transparent at 1440: a stray comment-closer in shell-tokens.css's header
     dropped its whole base :root block, so --pane (and --frame, --titlebar-h,
     --tap...) did not exist. --gutter/--pane-radius still came from the
     breakpoint blocks, which is why margin and radius looked fine. A fallback
     here would hide that failure on the one surface you look at most. */
  background: var(--pane);
  border-radius: var(--pane-radius);
  box-shadow: var(--shadow-sm);
  overflow: hidden;               /* clips content into the radius */
}

/* The pane body is the ONLY scrolling region in the app — which it already
   was: it keeps id="app-scroll" AND class .app-scroll, so util.js
   appScroller(), the run view's scroll restore, the router's scroll-to-top
   and app.css's `body:has(.modal-overlay) .app-scroll{overflow:hidden}` all
   keep working untouched. .pane-body only adds the scrollbar skin. */
.pane-body {
  flex: 1; min-height: 0;
  overflow: auto;
  overscroll-behavior: contain;   /* pane scroll never chains to the frame */
  scrollbar-width: thin;
  scrollbar-color: var(--surface-3) transparent;
}
.pane-body::-webkit-scrollbar { width: 10px; height: 10px; }
.pane-body::-webkit-scrollbar-thumb {
  background: var(--surface-3); border-radius: 99px;
  border: 3px solid var(--pane);  /* inset look — keeps the floating illusion */
}
.pane-body::-webkit-scrollbar-track { background: transparent; }
.pane-body:focus { outline: none; }

/* ── Status bar (tablet + desktop) ─────────────────────────────────────── */

.statusbar {
  display: none;                  /* phone: gone */
  align-items: stretch; gap: 2px;
  padding: 0 var(--sp-2);
  background: var(--frame);
  font-size: var(--font-size-xsm); color: var(--ink-soft);
  overflow: hidden;
}
@media (min-width: 640px) { .statusbar { display: flex; } }

.sb-item {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 0 var(--sp-2);
  border: 0; background: transparent; color: inherit; font: inherit;
  border-radius: var(--r-sm); white-space: nowrap; text-decoration: none;
  min-width: 0;
}
.sb-item.is-static { cursor: default; }
a.sb-item:hover, button.sb-item:hover { background: var(--surface-2); color: var(--ink); }
.sb-spacer { flex: 1; }
.sb-ctx-text { overflow: hidden; text-overflow: ellipsis; }
.sb-num { font-family: var(--font-num); font-variant-numeric: tabular-nums; }

/* ── Bottom nav — phones only ─────────────────────────────────────────────
   This REPLACES the rail below 640. It is the old .tabbar's job and keeps its
   geometry: a real grid row of the frame, so the scroll region ends where it
   begins. Safe-area inset is on the frame's row height (see .app-frame), so
   the nav itself only pads its own content. */

.bottomnav {
  display: flex; align-items: stretch;
  background: var(--frame);
  border-top: 1px solid var(--line);
  padding-bottom: env(safe-area-inset-bottom);
}
@media (min-width: 640px) { .bottomnav { display: none; } }

.bn-item {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  border: 0; background: transparent;
  /* --ink-soft, not --ink-faint: this app is read in sunlight, and the faint
     ink on the --frame tone is a squint at this size (theme.css note 1 is the
     same argument about body text). */
  color: var(--ink-soft);
  font-weight: 700; font-size: var(--font-size-xsm); line-height: 1; text-decoration: none;
  position: relative; cursor: pointer;
}
.bn-item svg { width: 22px; height: 22px; }
.bn-item.active { color: var(--accent); }
.bn-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.bn-item:focus-visible { outline: none; box-shadow: inset 0 0 0 3px var(--accent-soft); }

/* ── Phone: the wordmark yields to the screen name ────────────────────── */

@media (max-width: 639px) {
  .titlebar .brand-word { display: none; }
  .tb-screen { display: block; }
}

/* ── Immersive run / editor — PHONE ONLY, and that limit is the retrofit ──
   app.css §2 hides the app header and tab bar inside a run (Maya §7.2.6):
   once a run is open the screen IS the list, and one of those tabs navigates
   away mid-preflight. That reasoning is about a 390px phone and it still
   holds there, so it is preserved exactly — re-expressed against .titlebar
   and .bottomnav, and by collapsing their grid rows so the space goes
   straight to the list with no viewport arithmetic.

   What CHANGES is the ceiling. app.css only restored the chrome at 1200px, so
   a 1024px iPad in a run lost every bit of navigation. From 640 up the frame
   now stays: there is no thumb-zone mistap risk with a rail at the side, and
   losing all navigation on a large screen is worse than the 52px it costs —
   which is the argument app.css already made for the side nav at 1200, just
   applied at the width where a tablet actually starts. */
@media (max-width: 639px) {
  body.in-run .app-frame,
  body.editing .app-frame { grid-template-rows: 0 1fr var(--statusbar-h) calc(var(--bottomnav-h) + env(safe-area-inset-bottom)); }
  body.in-run .titlebar,
  body.editing .titlebar { display: none; }
  /* The nav goes inside a RUN but stays inside the EDITOR — app.css §2: the
     editor is not a run, and a mistap here does not lose a tick. */
  body.in-run .app-frame { grid-template-rows: 0 1fr var(--statusbar-h) 0; }
  body.in-run .bottomnav { display: none; }
}

/* ── Re-basing app.css's offsets on the pane ──────────────────────────────
   .run-head and .editor-head both carry `padding-top: env(safe-area-inset-top)`,
   because on a phone they ARE the top bar (the header is hidden above). From
   640 up the title bar is showing and already owns that inset, so leaving it
   on would count the notch twice. Re-based here rather than edited in
   app.css, so the whole retrofit still reverts by removing two <link> tags.

   Nothing else needs re-basing: CheckIt's sticky bars already pin at `top:0`
   against #app-scroll, which was ALREADY the scroll container before this
   change. That is why this port is so much smaller than LinkIt's was — the
   fixed-frame work landed in #545. */
@media (min-width: 640px) {
  .pane-body .run-head,
  .pane-body .editor-head { padding-top: 0; }
}

/* ── Fixed overlays that used to be measured against the WINDOW ───────────
   .toast-stack, .finish-bar and .jump-pill are all position:fixed against the
   viewport. With a rail at the left and a status bar at the bottom, "bottom:0
   left:0 right:0" is no longer the pane's edge — it runs under the chrome.
   Priya measured exactly this: MED-9, the finish bar at x=0 w=1400 lying
   across a 190px side nav, and COS-2, toasts floating 68px up inside a run
   because they reserved --tabbar-height for a bar that body.in-run had
   hidden. Both are rebased on the shell's own tokens, so they track the
   breakpoint instead of hard-coding a number. */

.toast-stack {
  /* COS-2: --bottomnav-h is 0 at 640+ AND the in-run rule below drops it to 0
     on a phone, so the stack sits just above whatever is actually there. */
  bottom: calc(var(--statusbar-h) + var(--gutter) + var(--bottomnav-h) + env(safe-area-inset-bottom) + var(--sp-3));
  /* Centre on the PANE, not the window, or the rail pushes it off-centre. */
  left: calc(50% + (var(--rail-w) / 2));
}
body.in-run .toast-stack {
  bottom: calc(var(--statusbar-h) + var(--gutter) + env(safe-area-inset-bottom) + var(--sp-3));
}

/* MED-9 (the overlap half): inset the finish bar into the pane so it stops at
   the rail instead of lying across it. The "two Finish buttons at once"
   half of MED-9 is a run.js fix, not a CSS one. */
.finish-bar {
  left: calc(var(--rail-w) + var(--gutter));
  right: var(--gutter);
  bottom: calc(var(--statusbar-h) + var(--gutter));
  padding-bottom: calc(var(--sp-2) + env(safe-area-inset-bottom));
  border-radius: 0 0 var(--pane-radius) var(--pane-radius);
}
.jump-pill {
  right: calc(var(--rail-w) * 0 + max(var(--sp-4), env(safe-area-inset-right)) + var(--gutter));
  bottom: calc(var(--statusbar-h) + var(--gutter) + var(--sp-4) + env(safe-area-inset-bottom));
}
body.has-finish-bar .jump-pill {
  bottom: calc(var(--statusbar-h) + var(--gutter) + var(--button-height) + var(--sp-6) + env(safe-area-inset-bottom));
}
