/* 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;
  /* 8px rounding is the SHARED default — all five consumers want it (exercise
     cards, History sets, Calories rows, Custom-exercise rows, the
     session-details sheet). It used to live in three near-identical
     per-consumer blocks, and the one consumer that never got its own block
     (SessionDetailsSheet) therefore rendered square. */
  border-radius: 8px;
}
.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;
  /* R3: rounded 2px MORE than the slide, so the fill pulls strictly inside the
     slide's corner curve and nothing leaks through its antialiased corners.
     R5: padding-left (>= the slide's radius) painted in the Edit button's own
     colour, so a fully-open row has no canvas crescents beside the buttons.
     See docs/swipe-row-guidelines.md. */
  border-radius: 0 10px 10px 0;
  overflow: hidden;
  padding-left: 10px;
  background: var(--blue-fill);
}
.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-fill); }
.xl-cs-act-del { background: #c0392b; }
.xl-cs-slide {
  position: relative;
  background: var(--white);
  padding: 6px 12px;
  border-radius: 8px;   /* R2 — must EQUAL the container's clip radius */
  transform: translateX(0);
  transition: transform 0.18s ease;
  z-index: 1;
  touch-action: pan-y;
}
/* While a finger is on the row the transition must be off — otherwise every
   touchmove starts a fresh 0.18s ease tween, so the row trails/lags then snaps
   on release (the reported "jumping/shaking" swipe). The base rule's transition
   stays for the release snap and open/close toggles. Same idea as
   .xl-card.dragging in exercise.css. */
.xl-cs-slide.xl-cs-slide-dragging { transition: none; }
/* 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;
  /* 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: var(--shadow-l2);
}
.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; }

/* 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; }
/* 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;
  /* Rows bleed 8px each side (-mx-2) so the revealed swipe actions reach the
     card edge. overflow-y:auto forces overflow-x to clip, which cut that bleed
     off — Delete lost 8px (unequal buttons) and the rounded right corners were
     sheared flat. Overflow clips at the PADDING box, so shifting the scroller
     out 8px and padding it back keeps the bleed visible and leaves the row
     text exactly where it was. */
  margin-inline: -8px;
  padding-inline: 8px;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.xl-flog-scroll::-webkit-scrollbar { display: none; }

/* Shared portaled action pill. Fixed controls must live under document.body so
   iOS Safari does not position them against .xl-main's scrolling context. */
.sa-floating-action {
  position: fixed;
  left: max(18px, calc(50% - 320px + 18px));
  right: max(18px, calc(50% - 320px + 18px));
  bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  z-index: 95;
  pointer-events: none;
}
.sa-floating-action > * {
  pointer-events: auto;
  padding-inline: 24px;
  box-shadow: var(--shadow-l3);
}

/* 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; }

/* A single-line row with no note gets HALF the base radius (4px vs 8px) — a
   note-less row is visually shorter/tighter, so the full 8px reads oversized.
   Generic (not scoped to a consumer): any SwipeRow can opt in via
   className="no-notes" when its content has no remark/note line. Currently
   used by History and the session-details sheet.
   See docs/swipe-row-guidelines.md. */
.xl-cs-set-swipe.no-notes { border-radius: 4px; }
.xl-cs-set-swipe.no-notes > .xl-cs-slide { border-radius: 4px; }
.xl-cs-set-swipe.no-notes > .xl-cs-actions {
  border-radius: 0 6px 6px 0;
}

/* ───────── 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-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: var(--bg);
  color: var(--slate);
  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; }
/* Cardio has three fields, not nine — it uses the roomier bottom-sheet
   rhythm (.cp-sheet-form, 19px) rather than the compact 6px the strength
   form needs to fit everything on one screen. */
.xl-set-fields-cardio { display: flex; flex-direction: column; gap: 19px; }
.xl-set-fields-cardio .xl-form-row { margin-bottom: 0; }
.xl-set-fields-cardio .xl-two-col { row-gap: 19px; }
.xl-advanced-toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.xl-simple-weight-reps { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

/* ── 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;
}

/* Read-only counterpart to .xl-ref-toggle, for a field with a single
   possible value (e.g. a bilateral-only exercise's Category). */
.xl-ref-static {
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  color: var(--ink);
}

/* 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. Gesture blocking belongs to the backdrop, while the
   sheet body keeps vertical panning enabled. */
html.sheet-open,
html.sheet-open body {
  overflow: hidden;
  overscroll-behavior: none;
}

.cp-backdrop {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--panel-close-dur) var(--panel-ease);
  touch-action: none;
  /* 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: var(--shadow-l5-deep);
  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 {
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior-y: contain;
  touch-action: pan-y;
  -webkit-overflow-scrolling: touch;
  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; }

/* Stacked, full-width — deliberately NOT .xl-sheet-actions' 2-col row. */
.xl-install-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 19px; }

/* ───────── 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: var(--bg);
  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: var(--ink);
}
.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. The pill
   portals to document.body (RestTimerBar.tsx), so this can't hang off .xl-app
   any more — match from the root instead. */
body:has(.xl-app .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: var(--shadow-l3);
}


/* ---- 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 var(--scrim);
  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: var(--shadow-l4);
  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:
    var(--shadow-l4),
    0 0 0 2000px var(--scrim);
}

.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-fill);
  color: var(--on-accent);
}
