/* ShapeUp — shared UI primitives (each used by >=2 features). Split out of
 * exercise.css. Load after shell.css, before exercise.css/feature CSS.
 * Requires shell.css's :root tokens (--blue/--border/--muted/etc). */

/* ───────── back row/button (used by hero, manual picker, session details, history) ───────── */
.xl-manual-backrow { padding: 0 0 6px; }
.xl-manual-back {
  display: inline-flex; align-items: center; gap: 4px;
  background: none; border: none; padding: 3px 4px 3px 2px; cursor: pointer;
  color: var(--muted); font-size: var(--fs-base); font-weight: var(--fw-semibold); font-family: inherit;
}
.xl-manual-back svg { width: 15px; height: 15px; }

/* ───────── PR badge (Log tab + History, shared source of truth) ───────── */
.pr-badge {
  flex: 0 0 auto;
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  padding: 1px 6px; border-radius: 4px;
  white-space: nowrap;
  color: #fff;
}
.pr-badge-weight { background: #0F6E56; } /* Weight PR  — green */
.pr-badge-reps   { background: #BA7517; } /* Lower reps — amber */
.pr-badge-lower  { background: #A32D2D; } /* Lower wt   — red   */

/* ───────── swipe-to-edit/delete row (SwipeRow.tsx: Current panel + History) ───────── */
/* ── swipe-to-edit/delete on a logged set (mirrors history.js) ── */
.xl-cs-set-swipe {
  position: relative;
  overflow: hidden;
  touch-action: pan-y;       /* let vertical scroll through; we handle horizontal */
  margin-bottom: 2px;
}
.xl-cs-set-swipe:last-child { margin-bottom: 0; }
.xl-cs-set-swipe.editing { overflow: visible; }
.xl-cs-actions {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  display: flex;
  align-items: stretch;
}
.xl-cs-act {
  width: 60px; border: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  color: #fff;
}
.xl-cs-act svg { width: 18px; height: 18px; }
.xl-cs-act-edit { background: var(--blue); }
.xl-cs-act-del { background: #c0392b; }
.xl-cs-slide {
  position: relative;
  background: var(--white);
  padding: 6px 12px;
  transform: translateX(0);
  transition: transform 0.18s ease;
  z-index: 1;
  touch-action: pan-y;
}
/* Exercise cards (ProgramCard) bring their own surface + padding, so the
   slide layer is a bare transform wrapper there — otherwise the card renders
   inside a second white box.
   The card's own 6px margin-bottom moves onto this container: left on the
   card, the container would be 6px taller than the card and the full-height
   actions layer would bleed out below it. Same reason for the radius — the
   actions are square otherwise and poke past the card's rounded corners. */
.xl-cs-set-swipe.xl-card-swipe {
  margin-bottom: 6px;
  border-radius: 8px;
  /* The card's own shadow is clipped away by this container's overflow:hidden,
     so it lives here instead — an element's overflow never clips its OWN
     box-shadow, only its descendants'. Level 2, natural-shadows skill (same
     recipe as .xl-card). */
  box-shadow:
    0 1px 2px rgba(15, 23, 42, 0.04),
    0 4px 10px rgba(15, 23, 42, 0.05),
    0 12px 28px rgba(15, 23, 42, 0.07);
}
.xl-cs-set-swipe.xl-card-swipe.done { box-shadow: none; }  /* completed = flat, like .xl-card.target-met */
.xl-cs-set-swipe.xl-card-swipe > .xl-cs-slide { background: none; padding: 0; }
.xl-cs-set-swipe.xl-card-swipe > .xl-cs-slide > .xl-card { margin-bottom: 0; box-shadow: none; }
/* The actions block sits directly behind the card. Rounded 2px MORE than
   the card's 8px corners, so its fill pulls back strictly inside the card's
   corner curve — the card's antialiased corner pixels then have nothing red
   behind them to leak. (Matching 8px leaves ~25% red coverage at the corner
   pixel; squaring the CARD instead clips its border arc off — don't.) The
   left edge stays square so it butts flush against the sliding card. */
.xl-cs-set-swipe.xl-card-swipe > .xl-cs-actions {
  border-radius: 0 10px 10px 0;
  overflow: hidden;
  /* Extend 10px past the 120px reveal, in the Edit button's own colour. The
     card's right corners are rounded 8px, so when the row is open its fill
     pulls back from the actions' straight left edge and the two corner
     crescents would otherwise expose the page canvas. This strip sits under
     the card at rest (it's behind the slide layer) and only ever shows
     through those crescents. Must stay >= the card's 8px radius. */
  padding-left: 10px;
  background: var(--blue);
}

/* Calories Entry Log rows (CaloriesTab EntryLog). A bare row like the logged
   sets, but 8px-rounded, so R2/R3/R5 of docs/swipe-row-guidelines.md apply the
   same way they do to cards: actions rounded 2px more than the row, and
   extended past REVEAL in the Edit button's colour so the row's corner
   crescents never expose the card underneath.
   Slide padding is 9px 8px (not the base 6px 12px) — the rows bleed 8px past
   the card's side padding, so the text still lines up with the card heading. */
/* R2: the slide's radius must EQUAL the container's 8px clip radius. The base
   bare-row slide is square (its containers have no radius), and left square
   here the container clipped the white fill's corners away and the blue Edit
   strip behind it showed through as four thin arcs per row. */
.xl-cs-set-swipe.xl-flog-swipe > .xl-cs-slide { padding: 9px 8px; border-radius: 8px; }
/* Rows sit flush — the base 2px margin exposed the full-height actions layer
   as a blue/red hairline in every gap. Spacing comes from the slide's padding. */
.xl-cs-set-swipe.xl-flog-swipe { margin-bottom: 0; }
/* Caps the Entry Log at N rows (5 by default; a card can override via the
   --flog-rows inline style), then scrolls inside the card. Same hidden-
   scrollbar + momentum treatment as Body Stats'
   .bcx-tbl-scroll, on the Y axis instead of X. The cap is N × the row height
   (--flog-row-h), measured from the rendered row, so it stays a whole number
   of rows — a half-row peeking under the cut is itself the scroll affordance.
   overflow-x: hidden so a mid-drag row can't add a horizontal scrollbar. */
.xl-flog-scroll {
  --flog-row-h: 49px;
  max-height: calc(var(--flog-rows, 5) * var(--flog-row-h));
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.xl-flog-scroll::-webkit-scrollbar { display: none; }

/* Horizontal chip-filter row (Foods tab) — same hidden-scrollbar treatment,
   X axis instead of Y. Mirrors .xl-manual-chips/.bcx-pills. */
.xl-flog-chips {
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.xl-flog-chips::-webkit-scrollbar { display: none; }
.xl-cs-set-swipe.xl-flog-swipe > .xl-cs-actions {
  border-radius: 0 10px 10px 0;
  overflow: hidden;
  padding-left: 10px;
  background: var(--blue);
}

/* ───────── form primitives (SetFieldsBlock, LogForm, EditSetForm, CustomProgramSheet) ───────── */
.xl-form-error { font-size: var(--fs-xs); color: #c0392b; margin-top: 6px; }
.xl-form-error:empty { display: none; }

/* ───────── logging form ───────── */

/* Form chrome ported from the old app (.xl-card-form / .xl-form-group):
   white background, 16px padding, uppercase labels with a faint required mark. */
.xl-form {
  border-top: 1px solid var(--border);
  padding: 16px;
  background: var(--white);
}
.xl-form button { -webkit-tap-highlight-color: transparent; }
.xl-form-row { margin-bottom: 8px; }
.xl-label {
  display: block;
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  color: var(--muted); margin-bottom: 4px;
  line-height: 1.1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Faint "*" required marker after a label (old app .xl-required). */
.xl-required { color: var(--muted); font-size: var(--fs-2xs); margin-left: 4px; }
.xl-two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.xl-three-col { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }

.xl-form input[type="number"], .xl-form input[type="text"] {
  width: 100%;
  height: var(--control-h); box-sizing: border-box;
  font-family: var(--font); font-size: var(--fs-md);
  padding: 0 14px;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--white); color: var(--ink);
}
.xl-form input:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 3px rgba(37,99,235,.1); }

/* Clear (×) button inside the Notes field. Bare icon — no ring, border or fill.
   The attribute selector is load-bearing: `.xl-form input[type="text"]` above
   sets the `padding` shorthand at specificity (0,2,1), so a plain
   `.xl-note-wrap input` rule (0,1,1) lost and long notes ran under the icon. */
.xl-note-wrap { position: relative; }
.xl-note-wrap input[type="text"] { padding-right: 38px; }
.xl-note-clear {
  position: absolute; top: 50%; right: 12px; transform: translateY(-50%);
  display: flex; align-items: center; justify-content: center;
  padding: 0; border: none; background: none;
  color: var(--muted); line-height: 1; cursor: pointer;
}
.xl-note-clear svg { width: 14px; height: 14px; }
.xl-note-clear:hover { color: var(--slate); }

/* RIR / RPE dropdowns — match the text/number inputs, with a custom chevron. */
.xl-form select.xl-select {
  width: 100%;
  height: var(--control-h); box-sizing: border-box;
  font-family: var(--font); font-size: var(--fs-md);
  padding: 0 14px;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--white); color: var(--ink);
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center; background-size: 16px;
  padding-right: 34px;
}
.xl-form select.xl-select:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 3px rgba(37,99,235,.1); }

/* Field-level error highlight (weight/reps/RPE/RIR left empty on submit).
   The red tweens in rather than snapping; the shake itself is WAAPI-driven
   from useShake.ts, and the message is a toast — no inline error text. */
.xl-form input, .xl-form select { transition: border-color 150ms ease-out; }
.xl-form input.xl-invalid, .xl-form select.xl-invalid { border-color: #c0392b; }
.xl-form input.xl-invalid:focus, .xl-form select.xl-invalid:focus { border-color: #c0392b; box-shadow: 0 0 0 3px rgba(192,57,43,.1); }

.xl-form input:disabled,
.xl-form select:disabled {
  background: #f8fafc;
  color: #64748b;
  cursor: not-allowed;
  opacity: 1;
}

/* Hide native number spinners (no scroll-to-change on the field). */
.xl-form input[type="number"] { -moz-appearance: textfield; }
.xl-form input[type="number"]::-webkit-outer-spin-button,
.xl-form input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0;
}

/* RPE description pill (ported from the old app). Colors are set inline per
   level from RPE_CONFIG; this just handles layout. Hidden until a value is set. */
.xl-rpe-pill {
  display: none;
  padding: 4px 12px;
  margin-top: 6px;
  border-radius: 14px;
  font-size: var(--fs-xs); font-weight: var(--fw-semibold);
  font-family: var(--font);
}

.xl-equip-chips { display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 0; }
/* Base pill recipe (radius/padding/font/selected-color) lives in shell.css's
   shared .sa-chip. Only the required-but-unselected prompt state is
   equipment-chip-specific. */
.xl-equip-chip.required-missing { border-style: dashed; border-color: var(--error); }
.xl-equip-chip.selected { border-style: solid; }
/* Compact the log/edit field block. */
.xl-set-fields .xl-form-row { margin-bottom: 6px; }
.xl-set-fields .xl-equip-chips { padding: 2px 0; }
.xl-set-fields .xl-label { margin-bottom: 3px; }

/* ── reference row (Latest / Max hint) ── */
.xl-ref-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-bottom: 6px; padding: 2px;
}
.xl-ref-left { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.xl-ref-label {
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  color: var(--muted);
}
.xl-ref-date { font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: var(--slate); line-height: 1; }
.xl-ref-date.empty { color: var(--muted); font-style: italic; }
.xl-ref-toggle {
  flex: 0 0 auto;
  display: inline-flex; gap: 2px;
  background: var(--white); border: 1px solid var(--border);
  border-radius: 14px; padding: 2px;
}

/* Native radios, no boxed border (matches the old app). */
.xl-radio-box { display: flex; gap: 16px; padding: 4px 0; }
.xl-radio {
  display: flex; align-items: center; gap: 5px;
  font-size: var(--fs-lg); font-weight: var(--fw-regular); color: var(--ink);
  cursor: pointer;
}
.xl-radio input {
  margin: 0;
  accent-color: var(--blue);
  cursor: pointer;
}

.xl-form-actions {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
  margin-top: 16px;
}
/* ───────── bottom-sheet shell (BottomSheet.tsx). Only the shell — .cp-card/
   .cp-field/.cp-input etc. form styles stay with the custom-program feature. ───────── */
/* While any BottomSheet is open, freeze the document scroll (BottomSheet.tsx
   toggles .sheet-open on <html>). The document — not .xl-main — is what iOS
   scrolls behind the sheet, so lock html/body here; overscroll-behavior kills
   the iOS rubber-band. */
html.sheet-open,
html.sheet-open body {
  overflow: hidden;
  overscroll-behavior: none;
  touch-action: none;
}

.cp-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(18,26,38,0.5);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--panel-close-dur) var(--panel-ease);
  /* Above everything, including the header/tab bar (z-index:2) and the FAB/
     rest-timer bar (9997-9998) — the sheet must fully block the whole app. */
  z-index: 20000;
}
.cp-backdrop.open {
  opacity: 1;
  pointer-events: auto;
  transition: opacity var(--panel-open-dur) var(--panel-ease);
}
/* Nested sheet (BottomSheet's `nested` prop) — a sheet opened from inside an
   already-open .cp-sheet (e.g. DatePicker's mobile calendar, opened from
   Body Stats' Log-entry sheet) needs to stack strictly above the base tier. */
.cp-backdrop-nested { z-index: 20010; }

.cp-sheet {
  position: fixed;
  /* Docked: touches the left/right screen edges and the very bottom. The
     safe-area inset is handled INSIDE the sheet (.cp-sheet-body padding), not
     as a bottom offset. */
  bottom: 0;
  left: 0;
  right: 0;
  /* left/right 0 + auto margins centers the 612px desktop cap exactly — no
     fractional half-viewport offset, so no hairline seams at the edges. */
  margin-inline: auto;
  /* Closed: lifted down by --panel-travel, faded out and blurred. The blur is
     what lets the travel be 48px instead of the sheet's full height — it
     bridges the two states so the eye reads one reveal, not a slide. */
  transform: translateY(var(--panel-travel));
  opacity: 0;
  filter: blur(var(--panel-blur));
  /* Closed sheets now sit OVER the page (they used to park off-screen), so
     they must not swallow taps, and must leave the a11y/tab order. */
  pointer-events: none;
  visibility: hidden;
  width: 100%;
  max-width: 612px;
  /* Free to grow over the header/tab bar — it's meant to sit on top of
     everything when open. */
  max-height: 90vh;
  max-height: 90dvh;
  background: var(--white);
  border-radius: 18px 18px 0 0;
  box-shadow: 0 12px 40px rgba(15, 23, 42, 0.22);
  transition:
    transform var(--panel-close-dur) var(--panel-ease),
    opacity var(--panel-close-dur) var(--panel-ease),
    filter var(--panel-close-dur) var(--panel-ease),
    visibility 0s linear var(--panel-close-dur);
  z-index: 20001;
  display: flex;
  flex-direction: column;
}
.cp-sheet-nested { z-index: 20011; }
.cp-sheet.open {
  transform: translateY(0);
  opacity: 1;
  filter: blur(0);
  pointer-events: auto;
  visibility: visible;
  transition:
    transform var(--panel-open-dur) var(--panel-ease),
    opacity var(--panel-open-dur) var(--panel-ease),
    filter var(--panel-open-dur) var(--panel-ease),
    visibility 0s;
}

/* Reduced motion: keep the fade (it explains that something appeared), drop
   the movement and the blur. */
@media (prefers-reduced-motion: reduce) {
  .cp-sheet,
  .cp-sheet.open {
    transform: translateY(0);
    filter: none;
  }
}
.cp-sheet-grip { display: none; }
/* Top padding matches the sides (16px) so the title is optically inset the same
   amount from every edge. It used to be 6px, which read as cramped against the
   top — every sheet then re-fixed it locally (five separate `padding-top: 16px`
   overrides had accumulated). Fixed here once; new sheets inherit it and need
   no override. See docs/bottom-sheet-guidelines.md. */
.cp-sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 16px 12px;
  flex-shrink: 0;
}
.cp-sheet-head h3 { font-size: var(--fs-xl); font-weight: var(--fw-bold); color: var(--ink); }
.cp-sheet-close {
  background: none;
  border: none;
  font-size: var(--fs-3xl);
  color: var(--slate);
  cursor: pointer;
  line-height: 1;
  padding: 0 4px;
  font-family: var(--font);
}
/* Bottom padding carries the safe-area inset for every sheet in the app — the
   sheet itself is docked to bottom:0, so the CONTENT is what clears the home
   indicator. New sheets must not re-add their own env() padding. */
.cp-sheet-body { overflow-y: auto; padding: 14px 16px calc(18px + env(safe-area-inset-bottom, 0px)); flex: 1; }
.cp-sheet-body::-webkit-scrollbar { width: 0; }

/* Standard sheet form body: one 19px rhythm between rows and above the
   action buttons. Cells inside grids drop their own margins — the flex
   gap / grid row-gap owns ALL vertical spacing. */
.cp-sheet-form { display: flex; flex-direction: column; gap: 19px; }
.cp-sheet-form .cp-field,
.cp-sheet-form .cp-field-tight { margin-bottom: 0; }
.cp-sheet-form .cp-ex-grid2 { row-gap: 19px; }
.cp-sheet-form .xl-sheet-actions { margin-top: 0; }

/* ───────── full-tab shell (ManualPicker + CustomProgramSheet takeovers) + empty card (EmptyCard.tsx) ───────── */
/* "No active session" empty state — docs/shapeup-new-design-spec.md State 1 */
.xl-nosession {
  background: var(--tab-track);
  border-radius: 18px;
  padding: 32px 20px 24px;
  text-align: center;
}
.xl-nosession-icon {
  width: 58px; height: 58px; border-radius: 16px;
  background: #E1E5EC;
  display: flex; align-items: center; justify-content: center;
  margin: 0 auto 16px;
}
.xl-nosession-icon img {
  width: 27px; height: 27px;
}
.xl-nosession-title {
  font-size: var(--fs-2xl); font-weight: var(--fw-extrabold); color: #3A404C;
}
.xl-nosession-sub {
  font-size: var(--fs-md); color: var(--muted); margin: 7px auto 0;
  line-height: 1.5; max-width: 250px;
}
.xl-nosession-actions {
  display: flex; flex-direction: row; justify-content: center; gap: 9px; margin-top: 22px;
}
/* "Input manual  or  create custom program" — two underlined text links under
   the primary button (session-states State 1). */
.xl-nosession-links {
  margin-top: 14px; font-size: var(--fs-md); font-weight: var(--fw-regular); color: var(--muted);
}
.xl-nosession-link {
  background: none; border: none; padding: 0; font: inherit; color: var(--blue);
  text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
}

/* ───────── full-tab shell (manual picker) ─────────
   A flex column that fills .xl-main so its inner region scrolls (not the whole
   page) and a solid footer band sits below it. Used by ManualPicker.tsx — a
   full-tab takeover of the Log tab (it needs a footer pinned below the
   scrolling list). CustomProgramSheet.tsx used to share this shell too, but
   its scroll behavior didn't match the other tabs (Record/Progress/History
   all scroll as one region with .xl-main, tab bar floating above via the
   overlay math) — it's now a plain panel like those, no bounded shell. */

/* When a full-tab takeover is active, make .xl-main a non-scrolling flex column
   (the inner scroller owns scrolling) and drop its padding so the shell spans the
   full height. Horizontal gutter is re-added inside the shell's scroller.
   Scoped with :has() to the ACTIVE panel actually containing a full-tab — NOT
   a JS-toggled .manual-on class on .xl-app. Panels stay mounted across tab
   switches, so a class on the shell leaked these rules onto History/Record/
   Progress (killing their scroll) whenever the picker was left open. */
.xl-app:has(.xl-panel.active .xl-fulltab) .xl-main {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 0;
  /* margin-top is deliberately NOT reset — the .has-tabbar overlay pull stays,
     so the fulltab scroller extends under the floating pill exactly like every
     other tab. The compensating top padding lives on .xl-fulltab-scroll. */
}
/* The active panel must pass the flex height down to the shell. */
.xl-app:has(.xl-panel.active .xl-fulltab) .xl-panel.active {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
/* Hide the rest-timer pill while a full-tab takeover owns the tab. */
.xl-app:has(.xl-panel.active .xl-fulltab) .xl-rest-pill { display: none; }

.xl-fulltab {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
/* The scrolling region — EVERYTHING scrolls here, footer included (see below).
   18px side gutter (restored, since .xl-main's padding was zeroed above). Top
   padding reproduces the .has-tabbar overlay math's own padding-top
   (shell.css) — .xl-main keeps the negative margin pull but its padding is
   zeroed in full-tab mode, so this is what makes content scroll UNDER the
   floating pill with the same 12px resting gap every other tab has.
   Flex column so the footer's `margin-top: auto` can push it to the bottom
   when the list is short (e.g. a search filtered down to one card). */
.xl-fulltab-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  padding: calc(var(--tabbar-h) + 6px + 12px) 18px 0;
}
/* Floating action button, mirroring the tab bar's treatment at the other end
   of the tab: content-width, no background band, list scrolls UNDERNEATH it.
   It is the LAST CHILD OF THE SCROLLER and sticks to its bottom edge — NOT a
   sibling overlaying it via a negative margin, which is how it used to work.
   That version was a full-width, ~34px-tall element outside the scroll
   container sitting exactly where a thumb rests: a drag starting on it had no
   scrollable ancestor (.xl-main is overflow:hidden in full-tab mode), so it
   couldn't scroll the list and the gesture chained to the document instead —
   "the whole page moves, the cards don't". Inside the scroller, a drag on the
   button scrolls the list like any other content. Its own height doubles as
   the scroller's bottom clearance, so there's no padding to keep in sync.
   Not position:fixed — .xl-main is a touch-scroll container on iOS and fixed
   children get clipped to it (gotcha #9). */
.xl-fulltab-footer {
  position: sticky;
  bottom: 0;
  z-index: 2;                       /* over the cards; under the tab bar (3) */
  flex-shrink: 0;
  margin-top: auto;
  padding: 12px 0 calc(14px + env(safe-area-inset-bottom, 0px));
  pointer-events: none;             /* let scroll gestures pass beside the pill */
}
/* Level 3 (floating control) — natural-shadows skill. The button now sits over
   scrolling content, so it needs its own elevation; it had none before because
   the opaque band separated it. */
.xl-fulltab-footer > * {
  pointer-events: auto;
  box-shadow: 0 6px 20px rgba(15, 23, 42, 0.18);
}


/* ---- Feature tour (components/Tour.tsx) -----------------------------------
   Spotlight coach marks. The dim is a 9999px spread shadow around the cutout
   box (no SVG mask — masks near the scroller fail on iOS, see
   docs/tabbar-scroll-guidelines.md §6). Sits at the sheet tier so it covers
   the header + tab bar; only the tooltip takes pointer events, so the dim can
   be tapped through to nothing rather than swallowing the whole app. */
.tour-root {
  position: fixed;
  inset: 0;
  z-index: 20020;
  pointer-events: none;
  /* Clips the cutout's spread shadow to the viewport. Without it the 9999px
     spread paints a box far larger than the screen, and iOS/Chrome cap how
     much of an oversized shadow they rasterize — which showed as the dim
     visibly weakening toward the bottom of the page. */
  overflow: hidden;
}

.tour-cutout {
  position: absolute;
  border-radius: 10px;
  /* Spread stays well under the rasterization cap now that the parent clips. */
  box-shadow: 0 0 0 2000px rgba(15, 23, 42, 0.6);
  pointer-events: auto;
}

.tour-tip {
  position: absolute;
  left: 16px;
  right: 16px;
  margin-inline: auto;
  max-width: 380px;
  padding: 16px;
  background: var(--white);
  border-radius: 14px;
  /* Level 4 popover (natural-shadows skill §3). */
  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
  pointer-events: auto;
}

/* Instruction-only step: no element to spotlight, so the card centers and
   carries the dim itself (normally the cutout's 9999px spread provides it). */
.tour-tip-centered {
  top: 50%;
  transform: translateY(-50%);
  box-shadow:
    0 8px 24px rgba(15, 23, 42, 0.12),
    0 0 0 2000px rgba(15, 23, 42, 0.6);
}

.tour-tip-steps {
  margin: 12px 0 0;
  padding-left: 20px;
  font-size: var(--fs-md);
  color: var(--ink);
  line-height: 1.7;
}

.tour-tip-title {
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  color: var(--ink);
}

.tour-tip-body {
  margin-top: 6px;
  font-size: var(--fs-md);
  color: var(--slate);
  line-height: 1.5;
}

.tour-tip-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 14px;
}

.tour-skip,
.tour-next {
  font-family: inherit;
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  border-radius: 10px;
  padding: 9px 16px;
  border: 0;
  cursor: pointer;
}

.tour-skip {
  background: none;
  color: var(--muted);
  padding-inline: 4px;
  margin-right: auto;
}

.tour-next {
  background: var(--blue);
  color: var(--white);
}

