/* ============================================================
   GYST UI — Web Component Library
   Direct analog of GystComponents.swift (iOS).

   Source of truth: ios/Gyst/Views/Components/GystComponents.swift.
   Token names referenced in the brief (`--surface`, `--surface2`,
   `--background`, `--border-subtle`) don't exist in our actual theme;
   they're aliased here to the real tokens (--bg-2, --bg-3, --bg,
   --border) so callers can use the cleaner names without invasive
   theme-wide renames.

   Loaded after profile.css. Additive only — no overrides.

   See /components for live examples of every class listed below.
   ============================================================ */

@import url("/brand-wordmark.css?v=20261002granite1");

/* =============================================================
   GYST-UI COMPONENT REGISTRY
   Before adding a new component: scan this list. If something
   similar exists, extend it. Do NOT create a new component for
   a pattern already listed here.
   Last updated: 2026-08-24 (audit catch-up — see the "2026-08-24
   REGISTRY CATCH-UP" section near the end of this block; entries
   tagged [ambient.css] / [modal.css] are registered here but defined
   in that file)
   ============================================================= */

/*
  BRAND
  .gyst-wordmark ............ The GYST wordmark image (artwork, not type)
  .gyst-studio-bar .......... Skyline glass with granite navigation artwork [ambient.css]
  .gyst-auth-wordmark ....... Granite authentication wordmark; motion in brand-wordmark.css

  LAYOUT & PANELS
  .gyst-panel ............... Card container with bg2 background, border, 12px radius
  .gyst-panel-grid-2col ..... 2-column grid layout variant of gyst-panel
  .gyst-section-label ....... All-caps section header above a panel (11px, dimmed)
  .gyst-section-desc ........ Muted description text below a section label
  .gyst-row ................. Base row inside a panel (44px min-height, 16px padding)
  .gyst-row-label ........... Leading label text inside a row
  .gyst-row-value ........... Trailing value text inside a row (dimmed)
  .gyst-row-chevron ......... Trailing chevron icon (rotates when open)
  .gyst-row-icon ............ Leading icon slot inside a row
  .gyst-status-dot .......... Semantic health dot; tint via --gyst-status-dot-tint
  .gyst-divider ............. 0.5px horizontal separator between rows
  .gyst-row-divider ......... RowDivider with the shared row inset
  .gyst-boot-recovery ....... Web boot failure placement for a GystUI panel + Reload action
  .gyst-icon-tile ........... Square icon container with bg2 background

  ROW TYPES
  .gyst-field-row ........... Row containing a borderless text input (TextFieldRow)
  .gyst-text-field-row ...... Standalone text input row with icon + label
  .gyst-text-editor-row ..... Multiline textarea row with icon + label (TextEditorRow)
  .gyst-text-editor-row-header  Header line of a TextEditorRow (icon + label)
  .gyst-menu-wrap ........... Wrapper around a menu-row + its expansion
  .gyst-menu-row ............ Row with trailing chevron that expands content inline (MenuRow)
  .gyst-daygrid ............. 7-column day-of-month grid inside a menu expansion (DayOfMonthGridRow)
  .gyst-daygrid-cell ........ One day cell (1-31); .is-last = full-width "Last day"; .selected = accent tint
  .gyst-toggle-row .......... Row with trailing iOS-style toggle switch (ToggleRow)
  .gyst-toggle-row-rich ..... Toggle-row variant with title + description below the title
  .gyst-toggle-row-info ..... Title slot inside a rich toggle row
  .gyst-toggle-row-desc ..... Description slot inside a rich toggle row
  .gyst-toggle .............. iOS-style switch (track) inside a ToggleRow
  .gyst-toggle-thumb ........ The sliding thumb inside .gyst-toggle
  .gyst-toggle.is-disabled .. Dimmed, non-interactive switch (keeps its slot)
  .gyst-setting-row ......... Read-only row with icon + label + optional trailing value (SettingRow); .open turns its chevron down (inline expand)
  .gyst-copy-row ............ A value to take elsewhere as the row title + trailing Copy button (CopyValueRow; GystUI.copyRow)
  .gyst-copy-row-body ....... Value + optional caption stack
  .gyst-copy-row-value ...... The value (wraps, never truncated; selectable)
  .gyst-copy-row-caption .... Optional dimmed caption under the value
  .gyst-copy-row-btn ........ Trailing Copy text button; .is-copied = "Copied" in success tone for 1.8 s
  .gyst-step-list ........... Numbered procedure <ol> (StepList; GystUI.stepList)
  .gyst-step ................ One step: accent ordinal (.gyst-step-num) + text (.gyst-step-text)
  .gyst-select-row .......... Row hosting a styled native select
  .gyst-select-row-info ..... Label/icon slot of a select row
  .gyst-select-row-sub ...... Subtitle slot of a select row
  .gyst-select-row-select ... The actual <select> styled to match GystUI
  .gyst-danger-row .......... Red-text row with title + subtitle "cannot be undone" (DangerRow)
  .gyst-danger-row-body ..... Title + subtitle stack inside a danger row
  .gyst-danger-row-title .... Title text inside a danger row
  .gyst-danger-row-subtitle . Subtitle text inside a danger row
  .gyst-danger-row-muted .... Muted variant — dims to text-dim (e.g. Disregard)
  .gyst-confirm-row ......... Inline confirm row that appears after DangerRow expand
  .gyst-choice-panel ........ Inline N-choice panel + Cancel (iOS InlineChoicePanel; GystUI.choicePanel). .is-destructive = danger tone; .is-ambient-frost = frosted on the atmosphere (choicePanel({ frosted: true }))
  .gyst-choice-panel-message  Message line at the top of a choice panel
  .gyst-choice-panel-row .... One choice (button); .is-cancel = the built-in Cancel row
  .gyst-choice-panel-sub .... A choice's dim second line (choices[].subtitle; iOS Choice.subtitle) inside .gyst-choice-panel-text

  SHEET HERO HEADER (iOS GystUI.SheetHeroHeader)
  .gyst-sheet-hero .......... Identity header for blueprint-editing sheets (tinted emoji tile + title + badge + explainer)
  .gyst-sheet-hero-top ...... Tile + title/badge row
  .gyst-sheet-hero-tile ..... 48px tinted emoji tile (tint via --list-template-color)
  .gyst-sheet-hero-title .... Bold 22px title
  .gyst-sheet-hero-badge .... Uppercase tracked badge chip in the subject tint
  .gyst-sheet-hero-subtitle . Dimmed explainer line

  MENU EXPANSION
  .gyst-menu-expansion ...... Animated expand container below a MenuRow
  .gyst-menu-options-list ... Scrollable list of option rows inside a MenuRow expansion
  .gyst-menu-option ......... Single option row inside a menu expansion (legacy)
  .gyst-menu-option-row ..... Option row with icon + label + checkmark (current pattern)
  .gyst-menu-search ......... Type-ahead filter field wrap inside a searchable menu expansion
  .gyst-menu-search-input ... The filter input itself (menuRow opts.searchable)
  .gyst-menu-option-list .... Scroll-capped option container in a searchable expansion
  .gyst-menu-no-matches ..... Empty state when the type-ahead filter matches nothing
  .gyst-menu-optgroup-label . Group label inside an option list
  .gyst-menu-time-picker-mount  Mount point for inline time picker inside a menu expansion

  PICKERS
  .gyst-time-picker ......... Drum-roll inline time picker (InlineTimePickerRow)
  .gyst-time-picker-sep ..... Separator (":") between hour and minute columns
  .gyst-time-col ............ Single column (hour/minute/ampm) inside time picker
  .gyst-time-col-pill ....... Selected value pill inside a time column
  .gyst-time-col-track ...... Scrollable track of time items
  .gyst-time-item ........... Single item in a time drum column
  .gyst-inline-date-picker .. Inline calendar date picker (InlineDatePickerRow)
  .gyst-inline-date-picker-nav         Month nav row at top of inline date picker
  .gyst-inline-date-picker-nav-btn     ‹/› buttons for month navigation
  .gyst-inline-date-picker-nav-label   "May 2026" centered label
  .gyst-inline-date-picker-grid        7-column day grid
  .gyst-inline-date-picker-dow         "Sun Mon …" header row
  .gyst-inline-date-picker-day         Single day cell in the grid
  .gyst-inline-date-picker-quick       Quick chips row (Today / Tomorrow / etc)
  .gyst-inline-date-picker-quick-chip  Single chip in the quick row
  .gyst-date-picker ......... Standalone date picker (used in profile birthday row)
  .gyst-date-picker-* ....... Sub-elements of the standalone date picker
  .gyst-date-year-drum ...... Year drum used inside .gyst-date-picker

  SEGMENTED CONTROL
  .gyst-segmented ........... Multi-option selector bar (SegmentedControl)
  .gyst-segmented-option .... Single option inside segmented control
  .gyst-segmented.is-compact  Compact height variant

  CHIPS & PILLS
  .gyst-day-chip ............ Rounded-rect weekday chip (S M T W T F S) — fills its
                              grid column, 34px tall, chipRadius corners. Matches iOS.
                              [scoped under #addEventModalBody / #taskModalContent]
  .gyst-preset-chip ......... Rounded-rect preset button (Weekdays/Weekend/Daily) —
                              fills its grid column, 34px tall, 6px corners. Matches iOS.
                              [scoped under #addEventModalBody / #taskModalContent]
  .gyst-priority-cell ....... Rectangular priority selector cell (Normal/High/Urgent)
                              [scoped under #taskModalContent]
  .gyst-people-chip ......... Person pill chip — outlined, filled accent when selected
  .gyst-pill-chip ........... Generic pill chip with optional icon
  .gyst-metadata-chip ....... Small metadata label chip (bg2, 6px radius)
  .gyst-meta-strip .......... Short facts separated by hairline dividers
                              ("Monthly | Total Checking | due Sep 10"). Wraps
                              BETWEEN segments only, never inside one, and drops
                              the divider that would begin a wrapped line.
                              Twin of GystUI.MetaStrip (iOS).
  .gyst-meta-strip-seg ...... One atomic fact inside a .gyst-meta-strip
  .gyst-cat-swatch .......... Small colored square swatch for category row
  .gyst-card-pill ........... Status pill badge on a person card
  .gyst-card-pill-success ... Green-success variant of card pill
  .gyst-card-color-dot ...... Small color dot used inside a person card

  PEOPLE SECTION
  .gyst-people-row .......... Container row for people chips
  .gyst-people-chip-group ... Flex wrap group of people chips
  .gyst-people-chip-dot ..... Color dot inside a person chip
  .gyst-people-chip-name .... Name label inside a person chip
  .gyst-people-empty-message  "No one assigned" muted text (legacy id-based)
  .gyst-people-empty ........ "No one assigned" muted text (new, scoped under modals)
  .gyst-people-remove ....... × remove button inside a chip
  .gyst-people-available .... Expandable list of available people to assign
  .gyst-people-available-item Single person row in the available list

  BUTTONS
  .gyst-btn-primary ......... Full-width filled primary button (accent color)
  .gyst-btn-secondary ....... Inline filled secondary button (bg2 fill, text-dim) — Cancel partner
  .gyst-btn-destructive ..... Full-width filled danger button
  .gyst-sticky-footer ....... Sticky bottom footer bar (bg1 + top border)
  .gyst-sticky-footer-btn-cancel  Cancel outline button in footer
  .gyst-sticky-footer-btn-save    Save filled button in footer (disabled when empty)
  .gyst-accent-action-btn ... Accent-tinted action button (profile add actions)
  .gyst-accent-action-btn-icon  Leading icon slot inside accent action button
  .gyst-card-btn ............ Small button inside a person card
  .gyst-card-btn-accent ..... Accent variant of card button
  .gyst-card-btn-danger ..... Danger variant of card button
  .gyst-brief-generate-btn .. Daily brief generate-CTA button (briefing widget)

  HABIT LOG ENTRY
  .gyst-stepper-card ........ Centered prompt + minus / value / plus stepper for count + timer entry
  .gyst-stepper-card-prompt . "How many steps?" / "Minutes logged" prompt above the stepper row
  .gyst-stepper-card-row .... HStack containing the minus + value + plus controls
  .gyst-stepper-card-value .. 48px tabular-nums numeric value
  .gyst-stepper-btn ......... Circular 36px stepper button — `.is-minus` / `.is-plus` modifier
  .gyst-stamp-card .......... Centered status stamp (success/abstain) for binary + negative habits
  .gyst-log-entry-header .... Habit name + icon row at top of the log-entry sheet
  .gyst-log-entry-footnote .. "This habit also syncs with Apple Health." caption text

  HYDRATION CALCULATOR
  .gyst-hydration-result ......... Centered stack inside the RECOMMENDED DAILY INTAKE panel
  .gyst-hydration-result-oz ...... Large accent-secondary oz value (32px / weight-800)
  .gyst-hydration-result-sub ..... Muted subtitle line beneath the oz value
  .gyst-hydration-result-sub-dim . Dimmer variant for the "N × 8 oz glasses" line
  .gyst-hydration-weight-input ... Borderless number input used inside the Weight row

  HABIT / ROUTINE TEMPLATE PICKER
  .gyst-template-picker-body ..... Scroll region between header + sticky search bar
  .gyst-template-row ............. Single template row inside a category panel
  .gyst-template-tile ............ 36×36 emoji icon tile (accent-tinted bg)
  .gyst-template-tile.is-workout . Stronger accent tint for the Workout Routine row
  .gyst-template-row-text ........ Title + description + meta-chip stack inside a template row
  .gyst-template-row-title ....... 15px bold title text inside a template row
  .gyst-template-row-desc ........ 13px dim description text inside a template row (clamped to 2 lines)
  .gyst-template-row-meta ........ Inline flex row hosting Type badge + target meta text
  .gyst-template-row-meta-text ... "64 oz" / "10 minutes" / "6 steps" meta text beside the type badge
  .gyst-template-add-btn ......... Trailing 24px accent-filled circular ⊕ button
  .gyst-template-type-badge ...... Uppercase pill ("Binary" / "Count" / "Timer" / "Negative") beneath the description
  .gyst-template-empty ........... "No matching templates" centered empty-state panel content
  .gyst-template-empty-icon ...... Magnifying-glass icon inside the empty-state panel
  .gyst-template-empty-label ..... Caption text inside the empty-state panel
  .gyst-template-search-bar ...... Sticky bottom bar hosting the pill search input
  .gyst-template-search-pill ..... Pill-shaped container (magnifying glass + input)
  .gyst-template-search-input .... Borderless input inside the search pill
  .gyst-detail-sheet-header-cancel  Left-side Cancel pill button (mirrors Done pill on the right)

  LIST GALLERY (NewListSheetView template picker)
  .gyst-list-template-section-label  Small-caps "PICK A TEMPLATE" / "YOUR LISTS" header
  .gyst-list-template-grid ........  2-column grid hosting the template cards
  .gyst-list-template-card ........  Grid card: header row (emoji + edit slot) + left-aligned name/desc; surface-panel bg
  .gyst-list-template-card.is-featured  Full-width Master Grocery card variant (left-aligned, larger)
  .gyst-list-template-card-top ....  Grid-card header row: emoji left, in-flow edit-starters control right
  .gyst-list-template-card-emoji ..  Emoji glyph in the header row / left of featured card
  .gyst-list-template-card-stack .. (featured) Title + subtitle text stack
  .gyst-list-template-card-name ...  Card title
  .gyst-list-template-card-desc ...  Card description text (centered in grid, left in featured)
  .gyst-list-template-card-trailing  (featured) chevron-right or star glyph on the right
  .gyst-list-existing-rail .......  Vertical stack of ExistingListRow entries beneath the grid
  .gyst-list-existing-row ........  Single existing-list row: emoji + name + meta + chevron
  .gyst-list-existing-row-icon ...
  .gyst-list-existing-row-name ...
  .gyst-list-existing-row-meta ...

  LIST CATEGORIES (grouped list detail — iOS GystUI.ListCategoryHeader)
  .gyst-list-cat-header ..... Collapsible, check-all section header for list categories (caret/name/checkall/count/menu)
  .gyst-list-cat-header__caret     Leading disclosure caret (rotates -90° when collapsed)
  .gyst-list-cat-header__name ...  Uppercase tracked category name
  .gyst-list-cat-header__spacer .  Flex spacer pushing trailing controls right
  .gyst-list-cat-header__checkall  Tri-state (none/some/all) accent check-all box
  .gyst-list-cat-header__count ..  "3/8" tabular count beside the check-all
  .gyst-list-cat-header__menu ...  Trailing ⋯ overflow-menu button
  .gyst-list-cat-section ........  Wrapper around one category (header + hairline + items)
  .gyst-list-cat-hairline .......  Accent progress hairline under the header (> span = fill)
  .gyst-list-cat-items.collapsed   Hides the category's items when collapsed
  .list-detail-add-cat-row ......  Add-bar category chooser row (hidden carrier #listAddItemCategoryId)
  .list-detail-add-cat-chooser .   Chooser button (label + caret) opening the inline category menu
  .list-detail-add-cat-menu ....   Inline category option list (No category + each custom category + "+ New category" action)
  .list-detail-add-cat-new-row .   Inline "+ New category" name-entry row, revealed from the menu's sentinel option (no native prompt)
  .list-cat-menu-pop ...........   Per-category overflow menu (Rename/Move up/Move down/Delete, inline)

  HABIT DETAIL
  .gyst-detail-sheet-header . Sticky top header (centered title + Done pill)
  .gyst-detail-sheet-header-title  Centered title text
  .gyst-detail-sheet-header-done   Top-right Done pill button (cream/accent)
  .gyst-tier-hero ........... Centered avatar + tier label + habit name block
  .gyst-tier-hero-avatar .... Circular tier-tinted avatar (88px)
  .gyst-tier-hero-avatar-glyph  Centered emoji/glyph inside the avatar
  .gyst-tier-hero-text ...... Tier name + habit name stack below the avatar
  .gyst-tier-hero-tier ...... "Tier N · Name" label (tier-color)
  .gyst-tier-hero-name ...... Habit name text under the tier label
  .gyst-stat-grid ........... 2-col grid of StatCards (no mobile collapse — iOS keeps 2-col)
  .gyst-stat-card ........... Single stat tile (icon+label header, big value+unit body)
  .gyst-stat-card-head ...... Header row of a stat card (icon + label)
  .gyst-stat-card-body ...... Body row of a stat card (value + unit)
  .gyst-stat-card-value ..... 28px bold value text inside a stat card
  .gyst-stat-card-unit ...... Trailing unit text inside a stat card body
  .gyst-milestone-card ...... "Next milestone" panel (emoji + label / count + bar)
  .gyst-milestone-card-head . Top row of a milestone card (emoji + label + count)
  .gyst-milestone-card-bar .. 8px progress-bar track inside a milestone card
  .gyst-milestone-card-fill . Gradient fill inside the milestone-card bar
  .gyst-milestone-ladder .... Expandable all-tiers progression inside the milestone card (iOS: GystUI.MilestoneLadder)
  .gyst-milestone-rung ...... One ladder row (.earned/.current/.locked) — dot + emoji + label + threshold + trailing slot

  EVENT DETAIL
  .gyst-event-folder-tab .... Color band tab at top of event detail modal
  .gyst-event-header ........ Event title + category header block
  .gyst-event-header-title .. Large event title text
  .gyst-event-header-cat .... Category row below title (icon + label + subcategory)
  .gyst-event-header-cat-icon  Category icon slot
  .gyst-event-header-cat-sep . " · " separator between category and subcategory
  .gyst-event-cancelled-banner Banner shown for cancelled events
  .gyst-event-cancelled-banner-head  "X cancelled" headline row
  .gyst-event-cancelled-banner-note  Cancelled note body text
  .gyst-event-detail-body ... Scroll container for all detail rows
  .gyst-event-detail-rule ... Horizontal rule separating detail rows from actions
  .gyst-event-actions ....... Actions panel container
  .gyst-event-actions-tiles . 3-column action tile grid
  .gyst-action-tile ......... Single tile in the actions panel
  .gyst-action-tile-grid .... Grid wrapper for action tiles (legacy alias)
  .gyst-action-tile-grid--two  Two-up row (a tile is hidden, e.g. no Share on an Apple Health workout)
  .gyst-tile-expansion-card . Inline expansion card under an action tile
  .gyst-expansion-row ....... Inline expand row inside the actions panel
  .gyst-reminder-active-row . Existing reminder row (bell + label + X) at top of Remind tile expansion
  .gyst-reminder-subrow ..... Trigger row + collapsible inline expansion (Custom time)
  .gyst-inline-custom-time .. Inline count + unit + Add picker (Custom time sub-row)
  .gyst-reminder-drum ....... Two-column scroll-snap drum (count + unit) for Beforehand
  .gyst-reminder-drum-column  Single drum column — scroll-snap track of values
  .gyst-reminder-drum-row ... One row in a drum column (faded by default, active = capsule)
  .gyst-reminder-drum-add ... Accent "Add reminder" confirm row below the drum
  .gyst-reminder-drum-add-icon Leading plus icon slot inside the drum-add row
  .gyst-reminder-drum-time .. "at 9:00 AM" nudge fire-time row below the drum (expands GystUI.timePicker)
  .gyst-detail-row .......... Single detail row (date, time, location, notes, etc.)
  .gyst-detail-rows ......... Container for all detail rows
  .gyst-linked-task-row ..... Linked task row inside event detail
  .gyst-cancel-note-wrap .... Cancel event note input wrapper
  .gyst-cancel-note-label ... Label inside the cancel-note wrapper
  .gyst-cancel-note-input ... Textarea inside the cancel-note wrapper
  .gyst-action-strip ........ Horizontal action strip (event-detail address row)
  .gyst-action-strip-item ... Single circular action button in the strip
  .gyst-action-strip-circle . The circular bg2 background of an action strip item
  .gyst-action-strip-label .. Label beneath an action strip item

  SHEETS (full-height overlays)
  .gyst-sheet-overlay ....... Full-screen overlay backdrop
  .gyst-sheet ............... Sheet container (bottom-up card)
  .gyst-sheet-header ........ Sheet header with title + close button
  .gyst-sheet-title ......... Centered title text inside the sheet header
  .gyst-sheet-close ......... Close ✕ button in sheet header
  .gyst-sheet-cancel ........ Cancel text-link in sheet header
  .gyst-sheet-body .......... Scrollable sheet content area
  .gyst-sheet-footer ........ Sheet footer with action buttons
  .gyst-sheet-empty ......... Empty-state slot inside a sheet body
  .gyst-timezone-sheet ...... Timezone picker sheet (searchable list)
  .gyst-sheet-search ........ Search input inside a sheet

  TOP-SLIDE SHEET (anchored below top nav — Add Chooser)
  .gyst-top-sheet-overlay ... Full-screen dim layer + open flag (.is-open)
  .gyst-top-sheet ........... Sheet card that slides down from below the top nav
  .gyst-top-sheet-header .... Centered sheet title + right-aligned Cancel
  .gyst-top-sheet-title ..... Sheet title inside the header (15px/600, title case — not an eyebrow)
  .gyst-top-sheet-cancel .... Right-aligned text Cancel button inside the header
  .gyst-top-sheet-body ...... Scrollable content area inside the sheet
  .gyst-top-sheet-grabber ... 36×4 drag indicator at the bottom edge (visual only)

  FILTER SHEET (full-sheet modal — Calendar Filter)
  .gyst-filter-sheet-overlay ...... Full-screen dim layer + .is-open flag
  .gyst-filter-sheet .............. Sheet card (mobile: slide-up bottom; desktop: centered)
  .gyst-filter-sheet-grabber ...... 36×4 grab handle at the top on mobile
  .gyst-filter-sheet-header ....... Sticky 2-row top bar (action edges + title; primary buttons below)
  .gyst-filter-sheet-header-row1 .. Top row: Clear All (leading) · title (center) · ✕ (trailing)
  .gyst-filter-sheet-header-row2 .. Bottom row: Filter + Search pill buttons (equal width)
  .gyst-filter-sheet-title ........ Centered 16px title text
  .gyst-filter-sheet-clear ........ Leading danger-tinted "Clear All" text action (hidden when no criteria)
  .gyst-filter-sheet-close ........ Trailing circular ✕ icon button (replaces former Done pill)
  .gyst-filter-sheet-filter-btn ... Filter pill — accent fill when data-active-action="filter" on row2, outlined otherwise. Tap dismisses sheet (criteria live-apply)
  .gyst-filter-sheet-search-btn ... Search pill — accent fill when data-active-action="search" on row2, outlined otherwise. Tap runs criteria + text search, sheet stays open
  .gyst-filter-sheet-body ......... Scrollable body — column of section groups
  .gyst-filter-section-label ...... 15px bold section heading inside the filter sheet
  .gyst-filter-section-header ..... Header row with label + trailing "All" clear button
  .gyst-filter-section-clear ...... Trailing "All" text-link in a section header
  .gyst-filter-mode ............... 2-segment mode toggle (label + subtitle per segment)
  .gyst-filter-mode-btn ........... Single mode toggle button
  .gyst-filter-mode-btn-label ..... 13px label inside a mode button
  .gyst-filter-mode-btn-sub ....... 10px subtitle line inside a mode button
  .gyst-filter-chips .............. Flex-wrap container for category chips
  .gyst-filter-chip ............... Single category chip — per-category color tint via --gfc-*
  .gyst-filter-panel .............. Grouped panel container (Health / Kids / Friends rows)
  .gyst-filter-row ................ Single row inside .gyst-filter-panel (avatar + text + check)
  .gyst-filter-row-avatar ......... 34×34 circular avatar slot
  .gyst-filter-row-text ........... Title + optional subtitle stack
  .gyst-filter-row-title .......... 14px row title
  .gyst-filter-row-subtitle ....... 12px dim subtitle below title
  .gyst-filter-row-check .......... Trailing 20×20 check icon (filled when active)
  .gyst-filter-clear-all .......... Danger-tinted "Clear All Filters" button

  LOCATION REMINDER SHEET (centered modal on desktop, bottom sheet on mobile)
  .gyst-location-sheet-overlay   Full-screen dim layer + .is-open flag
  .gyst-location-sheet ......... Sheet card (mobile: slide-up bottom; desktop: centered)
  .gyst-location-sheet-grabber . 36×4 grab handle at the top on mobile
  .gyst-location-sheet-header .. Centered small-caps title + absolute right Close X
  .gyst-location-sheet-title ... Centered 13px tracked uppercase title
  .gyst-location-sheet-close ... Circular Close X in the header (accent-secondary tint)
  .gyst-location-sheet-body .... Scrollable body — column of section groups
  .gyst-location-section-label . Small-caps section heading
  .gyst-location-trigger-toggle  2-segment toggle (When I arrive / When I leave)
  .gyst-location-trigger-btn ... Single segment in the trigger toggle (`.is-active`)
  .gyst-location-place-panel ... Place section panel (Location row + saved list + Use current)
  .gyst-location-details-panel . Details section panel (Radius row)
  .gyst-location-row ........... Tappable Location row (icon + label + trailing value)
  .gyst-location-row-icon ...... Leading pin icon slot
  .gyst-location-row-label ..... Label text inside the Location row
  .gyst-location-row-value ..... Trailing value (placeholder "Search for an address" / selected name)
  .gyst-location-saved-list .... Inline expansion list under the Location row
  .gyst-location-saved-place ... Single saved-place row (icon + label + multi-line address)
  .gyst-location-saved-place-icon  Leading icon slot inside a saved-place row
  .gyst-location-saved-place-text  Label + address stack inside a saved-place row
  .gyst-location-saved-place-label   Label line (15px / weight-600)
  .gyst-location-saved-place-address Address line (13px dim, multi-line)
  .gyst-location-status-slot ... "Looking up…" / "Couldn't find…" inline status under Location
  .gyst-location-status-label .. Status text (.is-error / .is-ok variants)
  .gyst-location-current-row ... "Use current location" row at the bottom of the panel
  .gyst-location-current-icon .. Leading navigation icon slot
  .gyst-location-radius-row .... Radius row (icon + label + value + stepper)
  .gyst-location-radius-icon ... Leading crosshair icon slot
  .gyst-location-radius-value .. "200 m" trailing value text (tabular-nums)
  .gyst-location-radius-stepper  Pill container with minus / divider / plus buttons
  .gyst-location-radius-stepper-btn      Single stepper button (− or +)
  .gyst-location-radius-stepper-divider  0.5px divider between stepper buttons
  .gyst-location-footnote ...... "Geofences require always-on location permission."
  .gyst-location-sheet-footer .. Sticky Cancel + Add bar (border-top hairline)
  .gyst-location-sheet-cancel .. Cancel button (bg2 fill, fixed width)
  .gyst-location-sheet-add ..... Add button (accent fill, fills remaining space, disabled by default)

  FILTER & SEARCH RESULTS (sheet result rows + tally)
  .fns-search-month-header ...... Subtle uppercase month label that groups result rows by start month (text-dimmer, tracked)
  .fns-search-tally ............. Two-line summary above the result panel (count + total hours)
  .fns-search-tally-primary ..... Line 1: "N events · Xh Ym total"
  .fns-search-tally-leave ....... Line 2 (conditional): "Xh Ym on leave" — only when a leave-type pill is active
  .fns-search-partial ........... Conditional second subtitle line on a row when leave window < shift duration
  .fns-search-leave-badge ....... Compact uppercase pill rendering an event's leave_type (PTO / SICK / LWOP / FMLA / BEREAVE / JURY)
                                  Per-type tints via .fns-search-leave-{pto|sick|lwop|fmla|bereavement|jury-duty}

  BOOK DETAIL SHEET (web parity 1.6 — clone of BookDetailSheet.swift)
  .gyst-book-detail-body ....... Vertical stack of Title/Author/Notes/Link/Rating/Status/Danger sections
  .gyst-book-open-link ......... "Open link" sibling row that appears beneath the Link input when valid http(s) — REUSED by Item Edit (1.5)
  .gyst-book-rating-row ........ 5-star horizontal row inside the rating panel (tap same star to clear)
  .gyst-book-rating-star ....... Single ★/☆ button — `.is-active` paints gold
  .gyst-book-status-row ........ "Mark as Read" / "Move back to Reading" row inside the status panel
  .gyst-book-status-date ....... Trailing "Read MMM D YYYY" caption inside the status row when checked
  .gyst-book-confirm ........... THE shared inline confirm (web twin of iOS GystUI.ConfirmActionPanel; replaces
                                  native confirm()). Flush strip by default; .is-card = the rounded danger well iOS
                                  draws. .money-unlink-confirm / .habit-slip-confirm / .list-leave-confirm are thin
                                  aliases of .is-card (their sheets keep only spacing) — 2026-09-30
  .gyst-book-confirm-message ... Confirm-panel headline copy ("Delete X? This cannot be undone.") — REUSED by Item Edit (1.5)
  .gyst-book-confirm-actions ... Cancel + Delete button row inside the confirm panel — REUSED by Item Edit (1.5)
  .gyst-book-confirm-delete .... Filled danger Delete button inside the confirm panel — REUSED by Item Edit (1.5)

  ITEM EDIT SHEET (web parity 1.5 — clone of ItemEditSheet.swift)
  .gyst-item-edit-body ......... Vertical stack of Title/Notes/Link/Danger sections for non-book card lists
                                  (movies, gifts, meals, bucket, restaurants, travel, date-night). All other
                                  rows reuse the existing .gyst-* primitives — no new sibling chrome.

  CONFIRMATION SHEET (centered modal on desktop, bottom sheet on mobile)
  .gyst-confirm-sheet-overlay  Full-screen dim layer + .is-open flag
  .gyst-confirm-sheet ........ Sheet card containing title + message + actions
  .gyst-confirm-sheet-grabber  36×4 grab handle shown at the top on mobile
  .gyst-confirm-sheet-title .. Title text (22px, weight 700)
  .gyst-confirm-sheet-message  Dim sub-title beneath the title
  .gyst-confirm-sheet-actions  Vertical button stack
  .gyst-confirm-sheet-btn .... Base button inside the actions stack
  .gyst-confirm-sheet-btn-primary    Filled accent variant (primary choice)
  .gyst-confirm-sheet-btn-secondary  Outlined neutral variant
  .gyst-confirm-sheet-btn-cancel ... Text-only Cancel at the bottom
  .gyst-confirm-sheet-btn-icon ..... Leading icon slot inside a button
  .gyst-sheet-scroll-list ... Scrollable list inside a sheet
  .gyst-sheet-list-row ...... Single row inside a sheet list
  .gyst-sheet-list-label .... Label text inside a sheet list row
  .gyst-sheet-list-check .... Trailing checkmark on a selected sheet list row

  PROFILE / NAVIGATION
  .gyst-nav-group ........... Profile nav section group
  .gyst-nav-group-title ..... Nav group header label
  .gyst-nav-row ............. Profile nav row (tappable section link)
  .gyst-nav-row-text ........ Text stack inside a nav row
  .gyst-nav-row-title ....... Title text inside a nav row
  .gyst-nav-row-subtitle .... Subtitle text inside a nav row
  .gyst-sub-row ............. Nested sub-category row (child of a section, behind a guide rail)
  .gyst-person-card ......... Person card in family/friends section
  .gyst-person-card-head .... Card header row (avatar + name + subtitle)
  .gyst-person-card-body .... Expanded body of a person card
  .gyst-person-card-actions . Footer action row inside a person card
  .gyst-person-avatar ....... Circular avatar with color background
  .gyst-person-info ......... Name + subtitle stack inside a person card
  .gyst-person-name ......... Name text inside a person card
  .gyst-person-subtitle ..... Subtitle text inside a person card
  .gyst-person-status ....... Status row inside a person card
  .gyst-person-status-icon .. Status icon (e.g. global share, kid sync)
  .gyst-person-chev ......... Trailing expand chevron on a person card
  .gyst-color-swatch ........ Color swatch circle (theme/category color pickers)
  .gyst-collapsible ......... Native <details> styled as collapsible section
  .gyst-saved-places-add .... "Add saved place" button in profile

  CONNECTOR CARDS (integrations)
  .gyst-connector-card ...... Connector card (Google / Outlook integrations)
  .gyst-connector-head ...... Header row inside a connector card
  .gyst-connector-info ...... Info stack inside a connector card
  .gyst-connector-name ...... Name text inside a connector card
  .gyst-connector-sub ....... Subtitle text inside a connector card
  .gyst-connector-chip ...... Status chip inside a connector card
  .gyst-connector-chev ...... Trailing chevron of a connector card
  .gyst-connector-body ...... Expanded body of a connector card
  .gyst-connector-actions ... Action row inside connector card body

  SHARED LISTS (iOS GystUI.MemberBubble / MemberBubbleStack, 2026-06-12)
  .gyst-member-bubble ....... Colored circle w/ white bold initial; --bubble-color
                              (viewer-relative member hex; default accent = the
                              viewer), --bubble-size (default 20px)
  .gyst-member-stack ........ Overlapping row of bubbles (owner first), bg ring
  .gyst-member-overflow ..... "+N" overflow bubble (bg-3 fill, dim text)

  MISC
  .gyst-birthday-input-wrap . Birthday field wrapper (MM/DD/YYYY inputs)
  .gyst-birthday-input ...... Single MM / DD / YYYY input
  .gyst-text-input .......... Generic styled text input
  .gyst-num-input ........... Generic styled number input
  .gyst-textarea ............ Generic styled textarea
  .gyst-voice-name-slot ..... Voice-actor name display (briefing settings)

  EVENT CATEGORY PICKER (Surface 3.1 — iOS EventCategoryPickerSheet)
  .gyst-event-cat-sheet-overlay  Full-screen dim layer + .is-open flag
  .gyst-event-cat-sheet ........ Sheet card (mobile: slide-up bottom; desktop: centered)
  .gyst-event-cat-sheet-grabber  36×4 grab handle at the top on mobile
  .gyst-event-cat-sheet-body ... Scrollable body (headline + 2-col grid)
  .gyst-event-cat-headline ..... "NEW EVENT" big-heavy display label
  .gyst-event-cat-subtitle ..... "Select a category" small-medium subtitle
  .gyst-event-cat-grid ......... 2-column grid of category cards
  .gyst-event-cat-card ......... Single category card — top-leading flex column, 14px radius
  .gyst-event-cat-card-circle .. 44px tinted icon circle (--gec-color inline)
  .gyst-event-cat-card-icon .... Lucide icon inside the circle
  .gyst-event-cat-card-label ... Card title (15px / weight-600)
  .gyst-event-cat-card-desc .... Card subtitle (11px text-dim, 2-line clamp)

  ICONS (Lucide sprite)
  .gyst-status-icon ............ Inline 11px Lucide SVG with semantic color
                                 (used by SearchResultRow + timeline tiles for
                                 recurring/cancelled/kid/family/etc. badges)

  LISTS
  .gyst-disposition-panel ...... Inline "what happened?" panel under a Gift
                                 Ideas (for me) item row on check-off. Composes
                                 section label + option rows + text field +
                                 inline date picker + toggle + Cancel/Done.
  .gyst-disposition-detail ..... Per-outcome detail mount (giver/date/thank-you)
  .gyst-disposition-footer ..... Cancel + Done button row inside the panel
  .gift-history-section ........ Received-history block under a Gift Ideas (for
                                 me) list (reuses .books-completed-section frame)
  .gift-history-row ............ One resolved gift: title + outcome/date sub-label
                                 + Edit / Undo icon buttons (.gift-history-btn)

  SLEEP HISTORY [defined in modal.css, registered here]
  .gyst-sleep-history-bar ...... Duration bar track in a Sleep History row (flex:1, 10px, surface-panel bg)
  .gyst-sleep-history-bar__fill  Accent fill inside the bar (width% set inline; min-width 10px)
  .gyst-sleep-history-header ... Header row (back button + centered title) inside #modalContent
  .gyst-sleep-history-back ..... Back affordance button (accent colour, returns to detail)
  .gyst-sleep-history-title .... "Sleep History" title label inside the header
  .gyst-sleep-history-body ..... Scrollable body wrapper (extends .sleep-detail-body)
  .gyst-sleep-month-section .... One calendar-month block (label + summary card + night rows)
  .gyst-sleep-night-row ........ Single night row (date | bar | duration | score)
  .gyst-sleep-night-date ....... Date label col (52px, text-dim)
  .gyst-sleep-night-dur ........ Duration col (64px, right-aligned, text bold)
  .gyst-sleep-night-score ...... Score badge col (reuses .sleep-hero-score size)
  .gyst-sleep-month-summary .... Summary card inside a month section (sleep-panel)
  .gyst-sleep-summary-head ..... Two-stat row at top of summary card (avg duration + avg score)
  .gyst-sleep-summary-stat ..... One stat block (val + label stacked)
  .gyst-sleep-summary-stat--right Right-aligned stat variant
  .gyst-sleep-summary-val ...... Large stat value (24px bold)
  .gyst-sleep-summary-lbl ...... Caps micro-label under stat value (9px)
  .gyst-sleep-summary-lines .... Container for consistency/vitals/range/coverage text lines
  .gyst-sleep-summary-line ..... One text line inside the summary (12px, text-dim)
  .gyst-sleep-history-empty .... Empty/error/loading state container (centred, 40px pad)
  .gyst-sleep-history-empty-title  Title inside empty state (16px bold)
  .gyst-sleep-history-empty-sub   Sub-text inside empty state (13px)
  .gyst-sleep-history-entry .... Sleep History entry button row at bottom of detail sheet
  .gyst-sleep-entry-chev ....... Chevron inside the entry row (›, text-dimmer)
  .gyst-sleep-night-expanded ... Inline expanded panel that appears below a night row (flex col, 12px pad)
  .gyst-sleep-exp-line ......... One text line inside the expanded panel (12px, text-dim)
  .gyst-sleep-stages-mini ...... Mini 8px stage proportional bar inside expanded panel
  .gyst-sleep-stages-mini__seg . One colour segment in the mini bar (width% set inline)
  .gyst-sleep-stages-legend .... Flex row of dot+label stage legend items (10px, text-dim)
  .gyst-sleep-detail-link ...... "View full night detail ›" drill-in button (accent, 13px bold)

  WORK SCHEDULE MULTI-SHIFT EDITOR
  .ws-shift-row ............ One shift row: start input – to – end input – overnight chip – warn – remove btn
  .ws-shift-overnight ...... "ends Xa tomorrow" chip (accent tint, hidden when not overnight)
  .ws-shift-remove ......... × remove-shift button (hidden when only 1 shift)
  .ws-shift-warn ........... ⚠ inline muted warning (overlap or ≥24h), always saves
  .ws-add-shift ............ "+ Add shift" button (hidden at 3 shifts)
  .ws-carryover ............ Read-only "↳ from PrevDay  12:00a–Xa" carryover line (muted)
  .ws-times-col ............ Column 3 of the day-row grid: carryover + shift-list + add-btn
  .ws-shift-list ........... Vertical stack of .ws-shift-row entries
  .ws-to-label ............. "to" span between start and end time inputs inside a shift row

  IN-TIMELINE SPORTS CARD (calendar.css — mirrors iOS SportsGameView)
  .sports-card ............. In-timeline sports matchup card overlay; extends .weather-chart-overlay
  .sports-card-row ......... Flex row: my-crest · game-time · opp-crest
  .sports-card-center ...... Centered game time / score text (15px bold)
  .sports-card-meta ........ Home/away + venue micro-label (9px, text-dimmer)
  .sports-card-meta--live .. Live-game meta: flex row with live-dot + "LIVE · Q<n> <clock>" text
  .sports-card-meta--final . Final-game meta: plain "FINAL" in text-dimmer
  .sports-card-live-dot .... Small filled accent-color dot before LIVE status (pulsing, like insight-market-dot)
  .sports-card-empty ....... "No game" fallback when event lookup fails

  AMBIENT STYLE PICKER (profile.js buildAmbientStyleRow — under the Ambient depth toggle)
  .gyst-ambient-style-row ....... Horizontal scroll row of live preview cards, shown only when ambient depth is on
  .gyst-ambient-card ............ 92-wide <button> holding a live mini preview stage + style name; accent ring when .selected
  .gyst-ambient-card-stage ...... 92×118 overflow-clipped stage hosting a scaled CSS-recipe instance or a preview canvas
  .gyst-ambient-card-name ....... Style display name label below the stage
  .gyst-ambient-preview ......... Scoping class that forces the inner style wrapper visible regardless of global data-ambient-style
  .gyst-ambient-preview-instance  390×560 recipe instance scaled to fill the stage (liquid/aurora/breathe)
  .gyst-ambient-preview-canvas .. Per-card <canvas> for the drift/ink previews (.is-ink adds a soft blur)

  BACKGROUND PICKER (2026-07-27 — four category accordions, one open at a time; replaces the
  AMBIENT STYLE PICKER above and the single-row 2026-07-21 layout. Lives in ambient.css, not
  this file — the exception below is intentional, since the registry indexes both.)
  .gyst-bg-category ............. One collapsible category row (Abstract/Nature/Landscape/Sky); .is-open expands it [ambient.css]
  .gyst-bg-category-head ........ Header button: title + style count + chevron; toggles the category open/closed [ambient.css]
  .gyst-bg-category-body ........ Expanded body: holds the strip + (if selection lives here) the mixer pane [ambient.css]
  .gyst-bg-strip ................ Horizontal-scroll row of .gyst-bg-tile inside an open category's body [ambient.css]
  .gyst-bg-tile ................. Large 9:16 background-preview tile: cached graded still, name, selection ring + check
  .gyst-bg-tile-frame .......... 9:16 image frame (background-image set to the still); accent border when .is-selected
  .gyst-bg-tile-check .......... Selection checkmark overlay (shown when .is-selected)
  .gyst-bg-tile-name ........... Style name label below the tile
  .gyst-bg-tile-dice ............ Dice tile at a strip's head: selecting it makes the background random-from-this-category, hourly [ambient.css]
  .gyst-bg-tile-dice-frame ..... Flat themed 9:16 frame for the dice tile (no artwork)
  .gyst-bg-tile-dice-glyph ..... The die glyph inside the dice tile frame
  .gyst-bg-tile-dice-sub ....... "hourly" subtitle under the dice tile's name
  .gyst-bg-mixer ................ Mixer pane under the SELECTED background's strip: Animate toggle + grade chips [ambient.css]
  .gyst-bg-mixer-motion ......... Fixed-width Animate toggle + label slot inside the mixer; .is-disabled dims to 0.5 [ambient.css]
  .gyst-bg-mixer-fav ............ Second toggle row (Favorite) stacked in the mixer's left slot [ambient.css]
  .gyst-bg-mixer-chips .......... Horizontal-scroll strip of .gyst-bg-grade-chip inside the mixer pane [ambient.css]
  .gyst-bg-grade-chip .......... One theme grade chip (mini still + name); accent ring + check when .is-selected

  EMPTY STATE (clone of iOS EmptyStateView — build via window.buildEmptyState())
  .gyst-empty-state ............. Centered icon + title + subtitle (+ optional CTA) wrapper for empty lists/tabs/panels; pass {tint} to set --gyst-empty-tint (identity color for icon/action, falls back to accent-secondary)
  .gyst-empty-state-icon ........ Lucide icon slot, accent-secondary tinted (44px when {tint} is passed, 18px default otherwise — see buildEmptyState in popover.js)
  .gyst-empty-state-text ........ Title + subtitle stack
  .gyst-empty-state-title ....... 18px semibold title
  .gyst-empty-state-subtitle .... 14px dim supporting line
  .gyst-empty-state-action ...... Accent pill CTA button (tinted by --gyst-empty-tint when set); :disabled renders an inert/coming-soon CTA as a 10px rect chip (not the pill), untinted regardless of --gyst-empty-tint

  ═══ 2026-08-24 REGISTRY CATCH-UP ══════════════════════════════
  Families that shipped since 2026-06-27 without registry entries.
  `-*` = the family's sub-element classes (icon/label/body/etc.)
  follow the parent's naming — grep the family prefix for the full set.

  .gyst-api-key-vault / .gyst-api-key-* .. API-key vault panel: key rows (icon,
                                  chip, edit/remove affordances), inline edit
                                  input + save/cancel (profile AI section)
  .gyst-up-next / .gyst-up-next-*  Up Next bar + expandable panel: event rows
                                  (time, title, chip, countdown, crests)
  .gyst-milestone-toggle / -chevron / .gyst-milestone-card-* / .gyst-milestone-rung-*
                                  Milestone ladder sub-elements (rung dot/emoji/
                                  check/count/threshold/minibar) under the
                                  registered .gyst-milestone-card / -ladder
  .gyst-toast / .gyst-toast-* .... App toast (host, icon, message, dismiss)
  .gyst-undo-toast / .gyst-undo-toast-*  Undo toast variant (title/text/undo/
                                  dismiss). NOTE: overlaps .gyst-toast —
                                  consolidation deferred (audit 2026-08-24)
  .gyst-list-grocery-choice / -*   "Start from master?" grocery choice panel
                                  (message + stacked choice buttons)
  .gyst-list-gallery-body ........ List template gallery sheet body
  .gyst-detail-row-* ............. Sub-elements of the registered detail rows
                                  (icon, body, text, sub, chip, chevron)
  .gyst-detail-notes-toggle ...... Notes expand/collapse affordance in detail sheet
  .gyst-sheet-header-title / -btn / -btn-start / -btn-end / -btn-primary
                                  Sheet header slots (Cancel/Done buttons, title)
  .gyst-expansion-row-* .......... Sub-elements of expansion rows (icon, label,
                                  color-dot, trailing)
  .gyst-sub-row-* / .gyst-subrow-group / .gyst-subrow-list
                                  Nested sub-row stack inside panels (body,
                                  title, subtitle, text)
  .gyst-row-label-stack / -sublabel / -subtitle / .gyst-row-tappable
                                  Row label stacking + tappable-row variants
  .gyst-form-panel ............... Form section panel (FormGroupPanel analog)
  .gyst-popover / .gyst-popover-*  Anchored popover (header + rows)
  .gyst-skeleton ................. Loading shimmer placeholder block
  .gyst-status-pill / -row ....... Status pill + host row. NOTE: overlaps
                                  .gyst-card-pill — consolidation deferred
                                  (audit 2026-08-24)
  .gyst-pill ..................... Generic pill (is-active accent state).
                                  NOTE: overlaps .gyst-pill-chip — deferred
  .gyst-count-badge .............. Small absolute count badge pinned to a corner
  .gyst-date-chip / .gyst-date-range-chips  Date chip + range chip row
  .gyst-date-year-drum-wrap / -pill / .gyst-date-year-item  Year drum internals
  .gyst-search-field ............. Standalone search input field
  .gyst-search-result-row ........ Result row under a search field
  .gyst-fns-action-bar / -buttons / .gyst-fns-filter-btn / .gyst-fns-search-btn
                                  Filter-and-search action bar + its buttons
  .gyst-ie-row / -icon / -body ... Inline-expand info rows (item edit sheet)
  .gyst-inline-custom-add / -count / -unit  Inline custom-value entry controls
  .gyst-linked-tasks-list / .gyst-linked-task-*  Linked-task rows in event detail
                                  (title, status, chevron)
  .gyst-log-entry-body / .gyst-log-entry-header-icon / -name
                                  Habit log-entry sheet internals
  .gyst-habit-detail-body / .gyst-habit-edit-body  Habit sheet scroll bodies
  .gyst-menu-option-icon / -label / -check  Menu option row internals
  .gyst-meta-strip-inner / -text . Meta strip internals (see .gyst-meta-strip)
  .gyst-reminder-inline-expansion / .gyst-reminder-active-row-* /
  .gyst-reminder-subrow-trigger / .gyst-reminder-expansion-active-sep
                                  Reminder picker expansion internals
  .gyst-action-tile-icon / -label / -chevron  Action tile internals
  .gyst-add-btn / .gyst-add-chooser-row  Add button + add-chooser row
  .gyst-notif-enable-btn ......... Notification-permission enable CTA
  .gyst-notice-row / __icon / __body / __title / __message / __action
                                  Flush "something's in the way + fix it" row
                                  (iOS GystUI.NoticeRow). .is-attention = danger
                                  wash (default), .is-info = neutral. First use:
                                  the notifications-are-off notice (2026-09-29)
  .gyst-notify-host.is-divided ... Host of a notify slot that sits under another
                                  row: a row divider above the notice while it
                                  shows (iOS .noticeLeadingDivider()) — 2026-09-30
  .gyst-gate / -header / -icon / -title / -subtitle / -message / -actions / -btn /
  -otp-input / -links / -textlink
                                  Shared verification panel (iOS ElevationGatePanel):
                                  Money step-up, app unlock, Security fresh-proof,
                                  delete / withdraw checks. GystUI.gatePanel() /
                                  GystUI.gateHeader(). Accent: --accent, .is-danger,
                                  or the host's --gyst-gate-accent (Money). Promoted
                                  from financial.css .money-gate-* (2026-09-30)
  .gyst-tap-floor ................ 44×44px HIT area for a visually smaller
                                  control (text button, 26–32px icon button)
                                  via an invisible centred ::after — the look
                                  and layout don't move (iOS .gystTapFloor()).
                                  Not for a control that already uses ::after.
                                  First use: Money (2026-09-29 item 26)
  .gyst-event-header-bar / -text . Event detail header internals
  .gyst-event-cat-card-text ...... Category card text stack
  .gyst-confirm-row-icon ......... Icon slot inside the registered confirm row
  .gyst-priority-cells ........... Grid wrapper for .gyst-priority-cell
  .gyst-stamp-card-icon .......... Icon inside the registered stamp card
  .gyst-tier-hero-medallion ...... Medallion slot inside the registered tier hero
  .gyst-toggle-row-body .......... Body slot of the registered toggle row
  .gyst-list-existing-row-text / -chevron  Existing-list row internals
  .gyst-bg-grade-chip-thumb / -name / -check / .gyst-bg-grade-auto-badge
                                  Grade chip internals [ambient.css]
*/

:root {
  /* Token aliases (web spec → real theme tokens).
   *
   * iOS parity: GystUI components paint on theme.surfacePanel /
   * surfaceRaised — the role tokens — not the raw bg layers. Web aliases
   * now point at the role tokens too, with the base palette as fallback
   * for themes that haven't defined the extension. Themes that DO define
   * distinct surface tokens (Neon Eclipse, future themes) automatically
   * get their intended elevated-panel hue through every gyst-panel /
   * row / sheet / picker on the web. */
  --gyst-surface:        var(--surface-panel, var(--bg-2));
  --gyst-surface-2:      var(--surface-raised, var(--bg-3));
  --gyst-background:     var(--bg);

  /* Bare alias names promised by this file's header (2026-08-24 audit).
   * These names appear at live call sites (var(--surface-2, …),
   * var(--card), var(--background, …), var(--surface2), var(--surface-card))
   * but were never defined, so those declarations resolved to their
   * fallbacks — or, for the --surface-2/--card chain, to NOTHING
   * (transparent panels). Defining them here makes every such call site
   * resolve to the real role tokens. Prefer the real tokens in new code;
   * these exist so the documented names stop being phantoms. */
  --background:          var(--bg);
  --surface:             var(--surface-panel, var(--bg-2));
  --surface2:            var(--surface-raised, var(--bg-3));
  --surface-2:           var(--surface-raised, var(--bg-3));
  --card:                var(--surface-panel, var(--bg-2));
  --surface-card:        var(--bg-1); /* matches what its call sites' fallback chains resolved to */
  --border-subtle:       var(--border-dim, var(--border));

  /* Geometry — mirror iOS GystUI.Tokens where it makes sense */
  --gyst-panel-radius:   12px;   /* GystUI.Tokens.panelRadius */
  --gyst-row-radius:     8px;    /* nested controls inside panels */
  --gyst-nested-radius:  10px;   /* GystUI.Tokens.nestedRadius */
  --gyst-chip-radius:    8px;    /* GystUI.Tokens.chipRadius */
  --gyst-card-radius:    12px;   /* referenced with a 12px fallback; now real */
  --gyst-row-min-height: 44px;   /* referenced with a 44px fallback; now real */

  --gyst-row-min-h:      48px;   /* iOS standard touch target */
  --gyst-row-px:         14px;   /* GystUI.Tokens.rowPaddingH */
  --gyst-tap-floor:      44px;   /* fixed hit area, independent of density */
  --gyst-row-py:         9px;    /* GystUI.Tokens.rowPaddingV */
  --gyst-row-gap:        12px;   /* GystUI.Tokens.rowItemSpacing */
  --gyst-chip-h:         32px;   /* GystUI.Tokens.chipHeight (tiers: 40 / 48) */
  --gyst-row-subtitle-gap: 2px;  /* GystUI.Tokens.rowSubtitleGap (tiers: 2.5 / 3) */
  /* Row glyph: the same --fs16 box .gyst-row-icon has always used — the web
     reading of iOS rowIconSize (a 15pt SF Symbol fills about a 16px Lucide
     box). Rides the type ladder, so it follows the density tier. */
  --gyst-row-icon-size:  var(--fs16, 16px);
  /* Danger wash + border — GystUI.Tokens.dangerBgOpacity (0.08) and
     dangerBorderOpacity (0.28). One pair for the DangerRow, the NoticeRow's
     attention tone and the ConfirmActionPanel card (2026-09-30). */
  --gyst-danger-wash:    8%;
  --gyst-danger-border:  28%;

  /* Top nav bar height — mirrors .topnav rule in style.css (56px).
     Hoisted here so the Add Chooser top-slide sheet anchors precisely
     beneath the nav without hardcoding the value at every consumer. */
  --top-nav-height:      56px;

  /* Apple-system stack so labels render at SF Pro's optical size on
     macOS / iOS browsers and fall back gracefully elsewhere. */
  --gyst-font-stack:     -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', sans-serif;

  /* Opacities — match iOS panelBorderOpacity / dividerOpacity. The base
   * resolves to --chrome-border (the role token iOS uses for panel
   * outlines) when the theme provides one, falling back to --border. */
  --gyst-panel-border:   color-mix(in srgb, var(--chrome-border, var(--border)) 55%, transparent);
  --gyst-row-divider:    color-mix(in srgb, var(--chrome-border, var(--border)) 35%, transparent);

  /* Time-picker drum geometry. Visible items default to 7 (selected +
     3 above + 3 below); column height + mask gradient reference these
     vars so a single override flows through the whole component. */
  --gyst-drum-item-height: 36px;
  --gyst-drum-visible:     7;
  --gyst-drum-fade:        2;       /* items faded by mask at each edge */
}

/* ──────────────────────────────────────────────────────────
   DENSITY TIERS — type ladder + row geometry
   iOS analog: the tier multipliers in TimelineMetrics.swift

   `html[data-tl-tier]` (0 / 1 / 2) is stamped on <html> by
   timeline-v2/timeline-metrics.js from the viewport. Tier 0 is the phone
   baseline; 1 and 2 are the roomier tablet / desktop densities.

   Every `font-size` declaration in gyst-ui / modal / tasks / lists /
   habits / briefing / ai / app-destinations is written as
   `var(--fsN, Npx)` by scripts/tier-varize.mjs. The fallback equals the
   literal it replaced, so if this ladder were deleted the app would still
   render exactly as it did before the conversion — and tier 0 is
   byte-identical by construction, because :root sets each --fsN to its own
   base value.

   The timeline CANVAS is deliberately NOT on this ladder. calendar.css
   carries its own `html[data-tl-tier="N"] .track-*` rules, which are more
   specific than a bare class and therefore win; putting .track-* on --fsN
   as well would scale it twice. Never add calendar.css to the script's
   SCOPE. calendar.css's NON-canvas surfaces (day-tile headers, span lane,
   insight band, month grid/agenda, chart chrome) DO consume these vars —
   hand-written there (2026-08-17 uniform-scale audit), never scripted.

   Regenerate with: node scripts/tier-varize.mjs --apply
   ────────────────────────────────────────────────────────── */

/* Density-tier type ladder (generated by scripts/tier-varize.mjs).
   Fallbacks equal the literals, so tier 0 is byte-identical. Sizes are
   collected from every sheet that consumes the ladder (SCOPE +
   HAND_TIERED), so a hand-written var() always resolves. The timeline
   CANVAS (.track-* in calendar.css) is deliberately NOT on the ladder —
   it carries its own, more specific html[data-tl-tier] rules. */
:root {
  --fs8: 8px;
  --fs9: 9px;
  --fs9_5: 9.5px;
  --fs10: 10px;
  --fs10_5: 10.5px;
  --fs11: 11px;
  --fs11_5: 11.5px;
  --fs12: 12px;
  --fs12_5: 12.5px;
  --fs13: 13px;
  --fs13_5: 13.5px;
  --fs14: 14px;
  --fs15: 15px;
  --fs16: 16px;
  --fs17: 17px;
  --fs18: 18px;
  --fs19: 19px;
  --fs20: 20px;
  --fs22: 22px;
  --fs24: 24px;
  --fs26: 26px;
  --fs28: 28px;
  --fs30: 30px;
  --fs32: 32px;
  --fs34: 34px;
  --fs36: 36px;
  --fs44: 44px;
  --fs48: 48px;
  --fs56: 56px;
}
html[data-tl-tier="1"] {
  --fs8: 10px;
  --fs9: 11px;
  --fs9_5: 12px;
  --fs10: 13px;
  --fs10_5: 13px;
  --fs11: 14px;
  --fs11_5: 14px;
  --fs12: 15px;
  --fs12_5: 16px;
  --fs13: 16px;
  --fs13_5: 17px;
  --fs14: 18px;
  --fs15: 19px;
  --fs16: 20px;
  --fs17: 21px;
  --fs18: 23px;
  --fs19: 24px;
  --fs20: 25px;
  --fs22: 28px;
  --fs24: 30px;
  --fs26: 33px;
  --fs28: 35px;
  --fs30: 38px;
  --fs32: 40px;
  --fs34: 43px;
  --fs36: 45px;
  --fs44: 55px;
  --fs48: 60px;
  --fs56: 70px;
}
html[data-tl-tier="2"] {
  --fs8: 12px;
  --fs9: 14px;
  --fs9_5: 14px;
  --fs10: 15px;
  --fs10_5: 16px;
  --fs11: 17px;
  --fs11_5: 17px;
  --fs12: 18px;
  --fs12_5: 19px;
  --fs13: 20px;
  --fs13_5: 20px;
  --fs14: 21px;
  --fs15: 23px;
  --fs16: 24px;
  --fs17: 26px;
  --fs18: 27px;
  --fs19: 29px;
  --fs20: 30px;
  --fs22: 33px;
  --fs24: 36px;
  --fs26: 39px;
  --fs28: 42px;
  --fs30: 45px;
  --fs32: 48px;
  --fs34: 51px;
  --fs36: 54px;
  --fs44: 66px;
  --fs48: 72px;
  --fs56: 84px;
}

/* Row geometry per tier — the touch target and its internal padding grow
   with the type so rows stay proportional instead of turning into thin
   bands of big text. Base values live in the :root block above. */
html[data-tl-tier="1"] { --gyst-row-min-h: 60px; --gyst-row-px: 18px; --gyst-row-py: 11px; --gyst-row-gap: 15px; --gyst-chip-h: 40px; --gyst-row-subtitle-gap: 2.5px; }
html[data-tl-tier="2"] { --gyst-row-min-h: 72px; --gyst-row-px: 21px; --gyst-row-py: 14px; --gyst-row-gap: 18px; --gyst-chip-h: 48px; --gyst-row-subtitle-gap: 3px; }

/* Modal/sheet widths do NOT scale with the density tier (Jeff 2026-08-17:
   modals are designed for their base widths — at 1.5× they read as filling
   the window). Each modal renders at --modal-w (or its sheet rule's base),
   capped at --content-w; the CONTENT inside still scales via the --fs
   ladder, mirroring iOS's system-sized iPad sheets with tier-scaled tokens.
   The former --modal-scale multiplier was removed from every consuming rule
   (modal.css .modal, .chooser-modal, and the sheet rules). */

/* ──────────────────────────────────────────────────────────
   Wordmark — the brand mark
   iOS analog: GystUI.Wordmark

   Approved pale granite with a soft-charcoal wave. Keep its original
   rendered colors, texture and bevel lighting in every theme.

   Sized by height only. The 320:108 ratio belongs to the artwork, so
   width stays auto; setting both would stretch it.
   ────────────────────────────────────────────────────────── */
.gyst-wordmark {
  height: 18px;
  width: auto;
  display: block;
  flex: 0 0 auto;
  user-select: none;
  -webkit-user-drag: none;
}

/* Both modes use the same approved granite pixels and their native srcset. */
[data-mode="light"] .gyst-wordmark {
  content: normal;
}

/* ──────────────────────────────────────────────────────────
   FormGroupPanel — grouped surface that hosts rows
   ────────────────────────────────────────────────────────── */
.gyst-panel {
  background: var(--gyst-surface);
  border: 1px solid var(--gyst-panel-border);
  border-radius: var(--gyst-panel-radius);
  overflow: hidden;
  font-family: var(--gyst-font-stack);
}

.gyst-boot-recovery {
  position: fixed;
  z-index: 100001;
  inset: auto 16px 16px;
  max-width: min(400px, var(--content-w));
  margin-inline: auto;
  padding: var(--gyst-row-px);
  color: var(--text);
  font-size: var(--fs14);
}
.gyst-boot-recovery p { margin: 0 0 var(--gyst-row-px); }
.gyst-boot-recovery button { min-height: 44px; }

/* Stacking siblings: 12pt gap between panels (iOS uses 14; web feels
   tighter at 12 with the way profile expansions already pad outward). */
.gyst-panel + .gyst-panel,
.gyst-panel + .gyst-section-label,
.gyst-section-label + .gyst-panel {
  margin-top: 12px;
}

/* 2-column grid variant — used by sections with many small rows
   (e.g. weather insights toggles) so the list takes roughly half
   the vertical space. Toggle rows lack inter-row dividers, so the
   grid reads cleanly with no leftover hairlines. Mobile collapses
   back to a single column at the responsive 768px breakpoint. */
.gyst-panel.gyst-panel-grid-2col {
  display: grid;
  grid-template-columns: 1fr 1fr;
}
@media (max-width: 768px) {
  .gyst-panel.gyst-panel-grid-2col {
    grid-template-columns: 1fr;
  }
}

/* ──────────────────────────────────────────────────────────
   Base row (SettingRow anatomy)
   ────────────────────────────────────────────────────────── */
.gyst-row {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--gyst-row-gap);
  min-height: var(--gyst-row-min-h);
  padding: var(--gyst-row-py) var(--gyst-row-px);
  font-family: var(--font);
  color: var(--text);
  box-sizing: border-box;
}

/* Hairline divider between siblings inside a panel. Padding-left
   matches rowPaddingH so the divider visually starts under the label
   column, mirroring the iOS RowDivider component. */
.gyst-panel > .gyst-row + .gyst-row::before,
.gyst-panel > .gyst-menu-row + .gyst-row::before,
.gyst-panel > .gyst-row + .gyst-menu-row::before,
.gyst-panel > .gyst-menu-row + .gyst-menu-row::before,
.gyst-panel > .gyst-field-row + .gyst-row::before,
.gyst-panel > .gyst-row + .gyst-field-row::before,
.gyst-panel > .gyst-toggle-row + .gyst-row::before,
.gyst-panel > .gyst-row + .gyst-toggle-row::before,
.gyst-panel > .gyst-setting-row + .gyst-setting-row::before,
.gyst-panel > .gyst-field-row + .gyst-field-row::before {
  content: "";
  display: block;
  position: absolute;
  left: var(--gyst-row-px);
  right: 0;
  top: 0;
  height: 1px;
  background: var(--gyst-row-divider);
}

.gyst-row-divider {
  border: 0;
  flex: none;
  height: 1px;
  margin: 0 0 0 var(--gyst-row-px);
  background: var(--gyst-row-divider);
}

/* Anchor for the ::before divider sibling rule above. */
.gyst-row,
.gyst-menu-row,
.gyst-field-row,
.gyst-toggle-row,
.gyst-setting-row,
.gyst-select-row {
  position: relative;
}

/* Universal row divider for addEventModal — covers any direct child of
   .gyst-panel that follows another direct child. The existing sibling rules
   above only handle .gyst-row/.gyst-menu-row direct siblings; in
   addEventModal many rows are wrapped in .gyst-menu-wrap containers and so
   aren't caught by those rules. Scoped to #addEventModal to avoid
   unintended side-effects on profile or other modals. */
#addEventModal .gyst-panel > * + * {
  position: relative;
}
#addEventModal .gyst-panel > * + *::before {
  content: '';
  position: absolute;
  top: 0;
  left: var(--gyst-row-px);
  right: 0;
  height: 0.5px;
  background: var(--border);
  pointer-events: none;
}

/* Collapse empty reminder list so no phantom space / divider appears above "Add Reminder". */
#eventRemindersList:empty { display: none; }
/* When the list is empty (display:none) the next sibling would still get a ::before divider
   from the universal rule above — suppress it so "Add Reminder" has no stray hairline above it. */
#addEventModal .gyst-panel > #eventRemindersList:empty + *::before { display: none; }

/* Row label / value typography — sized to match iOS body text. */
.gyst-row-label {
  color: var(--text);
  font-size: var(--fs15, 15px);
  font-weight: 400;
  letter-spacing: -0.1px;
  flex: 0 0 auto;
}
.gyst-row-label.dimmed {
  color: var(--text-dimmer);
  font-size: var(--fs13, 13px);
}
.gyst-row-value {
  color: var(--text-dim);
  font-size: var(--fs15, 15px);
  font-weight: 400;
  text-align: right;
  margin-left: auto;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Right-side chevron — rotates when its row is open. */
.gyst-row-chevron {
  color: var(--text-dimmer);
  font-size: var(--fs12, 12px);
  font-weight: 600;
  flex-shrink: 0;
  transition: transform 0.18s ease;
  display: inline-block;
}
.gyst-menu-row.open .gyst-row-chevron,
.gyst-row.open .gyst-row-chevron,
.gyst-setting-row.open .gyst-row-chevron {
  transform: rotate(90deg);
}

/* Leading icon slot (~SF Symbol size). 20×20 box, dimmer color so
   neutral glyphs (▦, ⏱) read as quiet labels; emoji glyphs ignore
   `color` and render in their own palette. Legacy variant retained
   for any callers that still use raw .gyst-row-icon spans. */
.gyst-row-icon {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-dimmer);
  font-size: var(--gyst-row-icon-size);
  line-height: 1;
}

/* Accent-tinted 32×32 chip wrapping the leading icon glyph — matches
   iOS GystUI.IconTile. Background is a low-alpha accent mix so the
   tile sits distinct from row bg in both light + dark themes; child
   <i data-lucide> picks up `currentColor` for the SVG stroke. */
.gyst-icon-tile {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
  font-size: var(--fs16, 16px);
  line-height: 1;
}

/* ──────────────────────────────────────────────────────────
   TextFieldRow
   ────────────────────────────────────────────────────────── */
.gyst-field-row {
  display: flex;
  align-items: center;
  gap: var(--gyst-row-gap);
  min-height: var(--gyst-row-min-h);
  padding: var(--gyst-row-py) var(--gyst-row-px);
  box-sizing: border-box;
}
.gyst-field-row .gyst-row-label {
  flex: 0 0 auto;
}
.gyst-field-row input,
.gyst-field-row textarea {
  flex: 1 1 auto;
  min-width: 0;
  text-align: right;
  background: transparent;
  color: var(--text);
  font-size: var(--fs14, 14px);
  font-family: inherit;
  border: 0;
  padding: 0;
  outline: none;
}
.gyst-field-row input::placeholder,
.gyst-field-row textarea::placeholder {
  color: var(--text-dimmer);
}
.gyst-field-row textarea {
  text-align: left;
  min-height: 64px;
  resize: vertical;
  line-height: 1.4;
  padding-top: 8px;
  padding-bottom: 8px;
  align-self: flex-start;
}

/* ──────────────────────────────────────────────────────────
   MenuRow — header + inline expansion
   ────────────────────────────────────────────────────────── */
.gyst-menu-row {
  display: flex;
  align-items: center;
  gap: var(--gyst-row-gap);
  min-height: var(--gyst-row-min-h);
  padding: var(--gyst-row-py) var(--gyst-row-px);
  cursor: pointer;
  user-select: none;
  box-sizing: border-box;
  transition: background-color 0.12s ease;
}
.gyst-menu-row:hover {
  background: var(--gyst-surface-2);
}
.gyst-menu-row.open {
  background: color-mix(in srgb, var(--gyst-surface-2) 70%, transparent);
}
.gyst-menu-row .gyst-row-value {
  /* Selected option label — accent when expanded so the user sees
     which option is currently selected even before scanning the list. */
  color: var(--text);
}
.gyst-menu-row.open .gyst-row-value {
  color: var(--accent);
}

/* Time-variant menuRow value: muted while collapsed, accent when the
   row is open — same on/off cue the option-list variant uses, just
   with a tabular-nums treatment so the digits don't wobble. */
.gyst-menu-wrap[data-variant="time"] .gyst-menu-row .gyst-row-value {
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.gyst-menu-wrap[data-variant="time"] .gyst-menu-row.open .gyst-row-value {
  color: var(--accent);
}

.gyst-menu-expansion {
  display: none;
  background: var(--gyst-surface-2);
  padding: 0 var(--gyst-row-px) 8px;
  position: relative;
}
.gyst-menu-expansion::before {
  /* Hairline divider matching the iOS expanded-row divider. */
  content: "";
  display: block;
  position: absolute;
  left: var(--gyst-row-px);
  right: 0;
  top: 0;
  height: 1px;
  background: var(--gyst-row-divider);
}
.gyst-menu-row.open + .gyst-menu-expansion {
  display: block;
}
/* The open/close animation itself runs in gyst-ui.js via element.animate()
   (height + opacity, 300ms) — the same approach background-picker.js uses,
   and for the same reason: a `display` toggle has nothing to transition
   from, and animating height explicitly is what lets ensureExpansionVisible
   measure the FINAL rect (it runs on finish, not alongside). iOS parity:
   GystUI.Tokens.expandSpring. Reduced motion skips straight to open. */

/* Calendar-style day-of-month grid (GystUI.dayGridRow). iOS twin:
   GystUI.DayOfMonthGridRow — tint + stroke selected state, never a solid
   accent fill, so it reads on every theme without contrast math. */
.gyst-daygrid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 6px;
  padding: 10px 0 4px;
}
.gyst-daygrid-cell {
  appearance: none;
  padding: 0;
  /* em, not px: rides the cell's --fs12 so the box scales with
     the tier ladder (density pitfall #4 — scale the box with the type). */
  min-height: 2.7em;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font: inherit;
  font-size: var(--fs12, 12px);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.gyst-daygrid-cell.is-last { grid-column: 1 / -1; }
.gyst-daygrid-cell.selected {
  background: color-mix(in srgb, var(--accent-active, var(--accent)) 22%, transparent);
  border-color: var(--accent-active, var(--accent));
  border-width: 1.5px;
  color: var(--accent-active, var(--accent));
  font-weight: 600;
}

.gyst-menu-option {
  display: flex;
  align-items: center;
  gap: var(--gyst-row-gap);
  /* iOS parity (GystUI.MenuRow.optionRow): option rows reserve the parent
     row's leading icon column, so an option's LABEL lines up under the parent
     row's label rather than under its icon. Web had no reserve, so the list
     read flush-left and unrelated to the row that opened it (Jeff 2026-08-17:
     "profile drop downs don't show up indented like they do on iOS").
     Tokens: rowIconFrameWidth (.gyst-row-icon, 20px) + rowItemSpacing. */
  padding-left: calc(var(--gyst-row-icon-w, 20px) + var(--gyst-row-gap));
  min-height: var(--gyst-option-min-h, 38px);   /* iOS optionRowMinHeight */
  padding-top: var(--gyst-option-py, 9px);      /* iOS optionRowPaddingV */
  padding-bottom: var(--gyst-option-py, 9px);
  padding-right: 0;
  cursor: pointer;
  color: var(--text);
  font-size: var(--fs14, 14px);
  font-weight: 400;
  border-top: 1px solid color-mix(in srgb, var(--border) 18%, transparent);
}
.gyst-menu-option:first-child {
  border-top: 0;
}
.gyst-menu-option.selected {
  font-weight: 600;
}
.gyst-menu-option.selected::after {
  content: "✓";
  margin-left: auto;
  color: var(--accent-active, var(--accent));
  font-weight: 700;
  font-size: var(--fs13, 13px);
}

/* ── Searchable (type-ahead) menu expansion ──────────────────────────
   menuRow(opts.searchable = N): filter input above the option list;
   option list caps its height and scrolls internally so hundreds of
   options never stretch the host form. iOS parity:
   GystUI.MenuRow.searchThreshold. */
.gyst-menu-search {
  padding: 8px 0 2px;
}
.gyst-menu-search-input {
  width: 100%;
  box-sizing: border-box;
  padding: 8px 10px;
  font-size: var(--fs14, 14px);
  color: var(--text);
  background: color-mix(in srgb, var(--bg-3) 60%, transparent);
  border: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  border-radius: 8px;
  outline: none;
}
.gyst-menu-search-input:focus-visible {
  border-color: var(--accent-active, var(--accent));
}
.gyst-menu-expansion.searchable .gyst-menu-option-list {
  max-height: 304px; /* 8 × 38px option rows, matches iOS menuSearchMaxVisibleRows */
  overflow-y: auto;
}
.gyst-menu-option.filtered-out {
  display: none;
}
/* display:none'd options break :first-child border suppression — the list
   container carries no top border, so suppress via the container edge. */
.gyst-menu-option-list .gyst-menu-option.selected {
  font-weight: 600;
}
.gyst-menu-no-matches {
  padding: 10px 0;
  font-size: var(--fs14, 14px);
  color: var(--text-dimmer);
  text-align: center;
}

/* Optgroup heading rendered between options when a <select> uses
   <optgroup> (AI model rows: OpenAI / Anthropic / etc.). Quiet
   small-caps label, no border-top so it doesn't read as a regular
   option row. */
.gyst-menu-optgroup-label {
  font-family: var(--gyst-font-stack);
  font-size: var(--fs10, 10px);
  font-weight: 700;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-dimmer);
  padding: 10px 0 4px;
}
.gyst-menu-optgroup-label:first-child {
  padding-top: 4px;
}
/* The first regular option after a group label shouldn't carry the
   inter-option border — the label itself already separates the
   groups visually. */
.gyst-menu-optgroup-label + .gyst-menu-option {
  border-top: 0;
}

/* Divider between adjacent gyst-menu-wrap siblings inside a panel
   so a column of menu-rows reads with the same hairline rhythm as
   .gyst-row siblings. Anchored to the wrap (which is the panel's
   direct child); no relative-positioning needed since the divider
   is a top border on the wrap itself. */
.gyst-panel > .gyst-menu-wrap + .gyst-menu-wrap > .gyst-menu-row {
  border-top: 1px solid var(--gyst-row-divider);
}
.gyst-panel > .gyst-menu-wrap + .gyst-menu-wrap > .gyst-menu-row.open {
  /* When the row above is open and the expansion sits between two
     siblings, the next wrap's border-top would double up against
     the expansion's own ::before hairline. Suppress it. */
  border-top-color: transparent;
}

/* ──────────────────────────────────────────────────────────
   CopyValueRow — a value to take elsewhere + a Copy button
   (iOS GystUI.CopyValueRow, 2026-09-29). A plain row with no
   nested well, so it adds no glass layer under Ambient Depth.
   Box sizes are em so they grow with the tier's type.
   ────────────────────────────────────────────────────────── */
.gyst-copy-row {
  display: flex;
  align-items: center;
  gap: var(--gyst-row-gap);
  min-height: var(--gyst-row-min-h);
  padding: var(--gyst-row-py) var(--gyst-row-px);
  box-sizing: border-box;
}
.gyst-copy-row-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15em;
}
/* The value is what the user takes elsewhere, so it is never cut off: a
   long value (an address, no spaces) breaks onto a second line instead of
   ellipsising its end away — for the MCP address, "/mcp" is the part
   that matters. */
.gyst-copy-row-value {
  color: var(--text);
  font-size: var(--fs15, 15px);
  font-weight: 600;
  line-height: 1.3;
  overflow-wrap: anywhere;
  -webkit-user-select: all;
  user-select: all;
}
.gyst-copy-row-caption {
  color: var(--text-dim);
  font-size: var(--fs12, 12px);
  line-height: 1.35;
  /* Two lines at most, like iOS CopyValueRow's caption lineLimit(2). */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}
.gyst-copy-row-btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  min-width: 44px;   /* tap floor — ergonomics, not density */
  min-height: 44px;
  padding: 0 0.3em;
  font: inherit;
  font-size: var(--fs13, 13px);
  font-weight: 600;
  color: var(--accent);
  background: none;
  border: 0;
  cursor: pointer;
}
.gyst-copy-row-btn .gyst-copy-row-icon {
  /* GystUI.icon(…, { size: 'css' }) writes no inline size, so this sizes
     the glyph: 1.15em of the tiered label, so it follows the density tier. */
  width: 1.15em;
  height: 1.15em;
}
.gyst-copy-row-btn .is-check,
.gyst-copy-row-btn.is-copied .is-copy { display: none; }
.gyst-copy-row-btn.is-copied .is-check { display: inline-block; }
.gyst-copy-row-btn.is-copied { color: var(--success); }
.gyst-copy-row-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--gyst-chip-radius);   /* a text button's ring = the chip radius token */
}

/* ──────────────────────────────────────────────────────────
   StepList — a short numbered procedure (iOS GystUI.StepList,
   2026-09-29). The ordinals are real (the steps are a
   sequence); a wrapped step hangs under its own text.
   ────────────────────────────────────────────────────────── */
.gyst-step-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.6em;
  font-size: var(--fs13, 13px);
}
.gyst-step {
  display: flex;
  align-items: baseline;
  gap: 0.9em;
  line-height: 1.45;
  color: var(--text);
}
.gyst-step-num {
  flex: 0 0 1.7em;
  text-align: center;
  color: var(--accent);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.gyst-step-text { flex: 1 1 auto; min-width: 0; }

/* ──────────────────────────────────────────────────────────
   SegmentedControl
   ────────────────────────────────────────────────────────── */
.gyst-segmented {
  display: flex;
  flex-direction: row;
  gap: 2px;
  padding: 3px;
  background: var(--gyst-surface-2);
  border-radius: var(--gyst-nested-radius);
}
.gyst-segmented-option {
  flex: 1 1 0;
  text-align: center;
  padding: 6px 10px;
  border-radius: var(--gyst-chip-radius);
  font-size: var(--fs13, 13px);
  font-weight: 600;
  color: var(--text-dim);
  background: transparent;
  border: 0;
  cursor: pointer;
  font-family: inherit;
  transition: background-color 0.18s ease, color 0.18s ease;
}
.gyst-segmented-option:hover:not(.active) {
  color: var(--text);
}
.gyst-segmented-option.active {
  background: var(--accent-active, var(--accent));
  color: var(--bg);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
}

/* Hide native radio input inside segmented options — the radio dot
   was visibly rendering inside the pill, breaking the aesthetic. The
   input is kept in the DOM for form serialization (radio.checked still
   works for save scope); only its visual representation is hidden. */
.gyst-segmented-option > input[type="radio"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 0;
  height: 0;
}

/* Compact variant — used inside form panels for inline scope choosers etc. */
.gyst-segmented.is-compact {
  padding: 2px;
  border-radius: 8px;
}
.gyst-segmented.is-compact .gyst-segmented-option {
  padding: 5px 10px;
  font-size: var(--fs12, 12px);
  font-weight: 500;
  border-radius: 6px;
}
.gyst-segmented.is-compact .gyst-segmented-option.active {
  background: var(--bg-1);
  border: 0.5px solid var(--border);
  color: var(--text);
  box-shadow: none;
}

/* Category color swatch — small filled square shown in the Category row icon slot */
.gyst-cat-swatch {
  display: inline-block;
  width: 14px;
  height: 14px;
  border-radius: 4px;
  background: var(--border);
  border: 0.5px solid color-mix(in srgb, var(--text) 10%, transparent);
}

/* ──────────────────────────────────────────────────────────
   ToggleRow
   ────────────────────────────────────────────────────────── */
.gyst-toggle-row {
  display: flex;
  align-items: center;
  gap: var(--gyst-row-gap);
  min-height: var(--gyst-row-min-h);
  padding: var(--gyst-row-py) var(--gyst-row-px);
  box-sizing: border-box;
}
.gyst-toggle-row .gyst-row-label {
  flex: 1 1 auto;
}

/* Multi-line title+subtitle stack used by the _toggleRow opts-object
   variant in app.js. Without this, the subtitle span renders inline
   with the label and looks like "Honor systemTrust the user's word". */
.gyst-toggle-row-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.gyst-toggle-row-body .gyst-row-label {
  flex: 0 0 auto;
}
.gyst-row-subtitle {
  font-size: var(--fs12, 12px);
  color: var(--text-dim);
  line-height: 1.3;
}

/* Small filled dot reporting the health of the thing it sits beside — a
   synced account, a running job, a live connection.

   Sized in `em`, so it tracks whatever text it is bonded to and therefore
   rides the density ladder for free (its neighbours are already on var(--fsN);
   a px diameter would pin the dot at compact while they grow).

   Presentational only: the caller supplies the colour through
   --gyst-status-dot-tint. "Healthy" is deliberately NOT var(--success) —
   that token is teal, purple or amber across GYST's themes and stops
   meaning "good" at this size. Never the sole carrier of a state; always
   pair it with text that says the same thing.

   MIRROR: GystUI.StatusDot in ios/Gyst/Views/Components/GystComponents.swift. */
.gyst-status-dot {
  flex: 0 0 auto;
  display: inline-block;
  width: 0.55em;
  height: 0.55em;
  margin-right: 0.45em;
  border-radius: 50%;
  background: var(--gyst-status-dot-tint, var(--text-dim));
  /* Lifted off the baseline so the dot centres on the x-height rather than
     reading as an oversized full stop. */
  vertical-align: 0.1em;
}

/* Custom toggle in the GystUI.ThemedToggle shape — rectangular track
   (44 × 24) with 5pt-equivalent corner radii and a 18px thumb. */
.gyst-toggle {
  -webkit-appearance: none;
  appearance: none;
  flex: 0 0 auto;
  width: 44px;
  height: 24px;
  background: var(--bg-3);
  border: 1px solid color-mix(in srgb, var(--border) 65%, transparent);
  border-radius: 5px;
  position: relative;
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease;
}
.gyst-toggle::before {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  background: var(--text-dimmer);
  border-radius: 3px;
  transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), background 0.2s ease;
}
.gyst-toggle:checked {
  background: var(--accent-active, var(--accent));
  border-color: transparent;
}
.gyst-toggle:checked::before {
  transform: translateX(20px);
  background: var(--bg);
}

/* ──────────────────────────────────────────────────────────
   SettingRow — read-only with optional chevron
   ────────────────────────────────────────────────────────── */
.gyst-setting-row {
  display: flex;
  align-items: center;
  gap: var(--gyst-row-gap);
  min-height: var(--gyst-row-min-h);
  padding: var(--gyst-row-py) var(--gyst-row-px);
  box-sizing: border-box;
}
.gyst-setting-row.is-tappable {
  cursor: pointer;
  transition: background-color 0.12s ease;
}
.gyst-setting-row.is-tappable:hover {
  background: var(--gyst-surface-2);
}
/* A row is frequently a <button> — every Money ledger row (accounts,
   transactions, recurring streams) is one, because the whole row is the tap
   target, exactly as GystUI.SettingRow behaves on iOS. Without these resets
   the user agent paints `buttonface` grey, shrinks the box to its content and
   swaps in 13.33px Arial: that is literally how the Money section shipped
   (2026-08-17 audit — grey, ragged-width rows in a system font). Everywhere
   else the class sits on a <div>, which is why it went unnoticed. Scoped to
   the element selector so div rows are untouched. */
button.gyst-setting-row {
  width: 100%;
  background: none;
  border: 0;
  margin: 0;
  padding: var(--gyst-row-py) var(--gyst-row-px);
  color: inherit;
  font: inherit;
  text-align: left;
  appearance: none;
  -webkit-appearance: none;
}

/* ──────────────────────────────────────────────────────────
   Section label (above panels)
   ────────────────────────────────────────────────────────── */
.gyst-section-label {
  display: block;
  font-size: var(--fs11, 11px);
  font-weight: 600;
  letter-spacing: 1px;            /* GystUI.Tokens.sectionLabelTracking: 1.0 */
  text-transform: uppercase;
  color: var(--text-dimmer);
  padding: 20px var(--gyst-row-px) 6px;
  font-family: var(--gyst-font-stack);
}

/* ──────────────────────────────────────────────────────────
   Buttons
   ────────────────────────────────────────────────────────── */
.gyst-btn-primary,
.gyst-btn-destructive {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 12px 14px;
  font-size: var(--fs14, 14px);
  font-weight: 600;
  font-family: inherit;
  border: 0;
  border-radius: var(--gyst-nested-radius);
  cursor: pointer;
  transition: filter 0.15s ease, opacity 0.15s ease, background-color 0.15s ease;
  letter-spacing: 0.2px;
  box-sizing: border-box;
}
.gyst-btn-primary {
  background: var(--accent);
  color: var(--bg);
}
.gyst-btn-primary:hover {
  filter: brightness(1.05);
}
.gyst-btn-primary:active {
  filter: brightness(0.94);
}
/* Disabled = control-surface fill + dim label at full opacity — a whole-button
   opacity fade collapses against same-hue backgrounds (visual audit 2026-07-08,
   finding 1.2; paired with iOS GystUI.PrimaryActionButton). */
.gyst-btn-primary:disabled {
  background: var(--bg-3);
  color: var(--text-dimmer);
  cursor: not-allowed;
}

.gyst-btn-destructive {
  background: color-mix(in srgb, var(--danger) 8%, transparent);
  color: var(--danger);
}
.gyst-btn-destructive:hover {
  background: color-mix(in srgb, var(--danger) 14%, transparent);
}
.gyst-btn-destructive:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Secondary action button — used as the "Cancel" partner to gyst-btn-primary
   in modal footers (e.g. the Location Reminder sheet). bg2 fill + text-dim
   color, no border, no fill on hover (just brightens). */
.gyst-btn-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 14px;
  font-size: var(--fs14, 14px);
  font-weight: 600;
  font-family: inherit;
  border: 0;
  border-radius: var(--gyst-nested-radius);
  cursor: pointer;
  transition: filter 0.15s ease, background-color 0.15s ease;
  letter-spacing: 0.2px;
  box-sizing: border-box;
  background: var(--bg-2);
  color: var(--text-dim);
}
.gyst-btn-secondary:hover { background: var(--bg-3); color: var(--text); }
.gyst-btn-secondary:disabled { opacity: 0.5; cursor: not-allowed; }

/* ──────────────────────────────────────────────────────────
   Divider — standalone hr (e.g. between panels outside a card)
   ────────────────────────────────────────────────────────── */
.gyst-divider {
  border: 0;
  height: 1px;
  background: color-mix(in srgb, var(--border) 15%, transparent);
  margin: 0;
}

/* ──────────────────────────────────────────────────────────
   Drum-roll time picker — three scrolling columns (hour /
   minute / am-pm) with a fixed center pill and a soft mask
   fade above and below. Native overflow scroll handles touch
   momentum + wheel; the JS layer adds mouse drag + inertia +
   keyboard arrows on top, and snaps to itemHeight on rest.
   ────────────────────────────────────────────────────────── */
.gyst-time-picker {
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
  gap: 8px;
  padding: 8px 8px 4px;
  user-select: none;
  -webkit-user-select: none;
  font-family: var(--gyst-font-stack);
}

/* Separator span retained on the API but rendered hidden — the
   selected item itself reads well enough as an `h:mm` chunk and the
   colon was crowding the columns. Keep the span so existing JS
   doesn't break. */
.gyst-time-picker-sep {
  display: none;
}

.gyst-time-col {
  position: relative;
  width: 64px;
  /* Visible-item count × item height — single source of truth for the
     drum geometry (vars at :root). 7 × 36 = 252 in the default config. */
  height: calc(var(--gyst-drum-item-height) * var(--gyst-drum-visible));
  overflow-y: auto;
  overflow-x: hidden;
  scroll-snap-type: y mandatory;
  scrollbar-width: none;          /* Firefox */
  -ms-overflow-style: none;       /* legacy Edge */
  outline: none;
  cursor: grab;
  /* Drum-roll fade: opaque from the top of item 2 through the bottom
     of item 4 (the center 3 items), linearly fading to transparent
     across the outer 2 items on each edge. The fade % is driven by
     the same vars as the column height so a config change keeps the
     gradient aligned to item boundaries.
        edge band  = --gyst-drum-fade × item-height = 72px
        column h   = --gyst-drum-visible × item-height = 252px
        fade %     = 72 / 252 ≈ 28.5% */
  -webkit-mask-image: linear-gradient(
    to bottom,
    transparent 0%,
    rgba(0, 0, 0, 1) calc(100% * var(--gyst-drum-fade) / var(--gyst-drum-visible)),
    rgba(0, 0, 0, 1) calc(100% - 100% * var(--gyst-drum-fade) / var(--gyst-drum-visible)),
    transparent 100%
  );
          mask-image: linear-gradient(
    to bottom,
    transparent 0%,
    rgba(0, 0, 0, 1) calc(100% * var(--gyst-drum-fade) / var(--gyst-drum-visible)),
    rgba(0, 0, 0, 1) calc(100% - 100% * var(--gyst-drum-fade) / var(--gyst-drum-visible)),
    transparent 100%
  );
}

/* Wider column for AM / PM since the labels are longer. */
.gyst-time-col[data-type="ampm"] {
  width: 72px;
}

.gyst-time-col::-webkit-scrollbar {
  display: none;
}

.gyst-time-col:focus-visible {
  box-shadow: 0 0 0 2px var(--accent);
  border-radius: 10px;
}

/* Slightly brighter pill on the keyboard-focused column so type-to-set
   and arrow nav have a visible target without darkening the unfocused
   columns. The accent ring on the column itself does the heavy lifting. */
.gyst-time-col:focus-visible .gyst-time-col-pill {
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
}

.gyst-time-col.is-dragging {
  cursor: grabbing;
}

/* Center pill — fixed at viewport center, sits behind items. */
.gyst-time-col-pill {
  position: absolute;
  top: 50%;
  left: 2px;
  right: 2px;
  height: 38px;
  transform: translateY(-50%);
  pointer-events: none;
  background: var(--gyst-surface-2);
  border: 1px solid color-mix(in srgb, var(--border) 20%, transparent);
  border-radius: 10px;
  z-index: 0;
}

.gyst-time-col-track {
  position: relative;
  z-index: 1;
}

/* Items: uniform 19px / 400 with no tracking; the dimming-by-distance
   effect comes from explicit opacity bands set by JS on every scroll
   frame. Far items (4+) sit at the default 0 opacity so they fade
   completely past the visible 7-row band. */
.gyst-time-item {
  height: var(--gyst-drum-item-height);
  display: flex;
  align-items: center;
  justify-content: center;
  scroll-snap-align: center;
  font-size: var(--fs19, 19px);
  font-weight: 400;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  opacity: 0;
  transition: opacity 0.12s ease, font-weight 0.12s ease;
  cursor: pointer;
  letter-spacing: 0;
}
.gyst-time-item.is-near-3 { opacity: 0.12; }
.gyst-time-item.is-near-2 { opacity: 0.30; }
.gyst-time-item.is-near-1 { opacity: 0.55; }
.gyst-time-item.is-selected {
  opacity: 1;
  font-weight: 500;
}

/* ──────────────────────────────────────────────────────────
   Collapsible panel — <details>/<summary> styled to match
   .gyst-panel. Used for sections that compose a long list of
   GystUI rows under a tappable summary (e.g. Category Colors).
   ────────────────────────────────────────────────────────── */
.gyst-collapsible > summary {
  list-style: none;        /* drop default disclosure triangle */
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: var(--gyst-row-gap);
  min-height: var(--gyst-row-min-h);
  padding: var(--gyst-row-py) var(--gyst-row-px);
  position: relative;
}
.gyst-collapsible > summary::-webkit-details-marker {
  display: none;
}
.gyst-collapsible > summary .gyst-row-chevron {
  transition: transform 0.18s ease;
}
.gyst-collapsible[open] > summary .gyst-row-chevron {
  transform: rotate(90deg);
}

/* ──────────────────────────────────────────────────────────
   Color swatch — small clickable square at the trailing edge
   of a row. Filled with the row's current color via inline
   style; the wrapping styles handle border + radius + hover.
   ────────────────────────────────────────────────────────── */
.gyst-color-swatch {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  padding: 0;
  margin-left: auto;
  border: 1px solid color-mix(in srgb, var(--input-border, var(--border)) 50%, transparent);
  border-radius: 6px;
  cursor: pointer;
  background: transparent;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.gyst-color-swatch:hover {
  transform: scale(1.05);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 35%, transparent);
}
.gyst-color-swatch:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--accent);
}

/* ──────────────────────────────────────────────────────────
   Birthday row — built inline in profile.js but uses these
   classes for visual consistency with the rest of GystUI.
   ────────────────────────────────────────────────────────── */
.gyst-birthday-input-wrap {
  padding: 8px var(--gyst-row-px) 4px;
}
.gyst-birthday-input {
  width: 100%;
  padding: 8px 10px;
  background: var(--gyst-surface-2);
  border: 1px solid color-mix(in srgb, var(--border) 35%, transparent);
  border-radius: 8px;
  color: var(--text);
  font-size: var(--fs15, 15px);
  font-family: var(--gyst-font-stack);
  color-scheme: dark light;
}

/* Date variant — value defaults to dim, accent on .open (mirrors
   the time variant). */
.gyst-menu-wrap[data-variant="date"] .gyst-menu-row .gyst-row-value {
  color: var(--text-dim);
}
.gyst-menu-wrap[data-variant="date"] .gyst-menu-row.open .gyst-row-value {
  color: var(--accent);
}

/* ──────────────────────────────────────────────────────────
   Modal sheet — used by openTimezoneSheet / openPlaceSheet.
   Centered card on a scrim; small header (small caps title +
   close X), scrollable body, optional footer with action btns.
   ────────────────────────────────────────────────────────── */
.gyst-sheet-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2000;
  padding: 20px;
  font-family: var(--gyst-font-stack);
}
.gyst-sheet {
  background: var(--bg-2);
  border: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  border-radius: 16px;
  width: 100%;
  max-width: min(420px, var(--content-w));
  max-height: calc(100vh - 80px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.45);
}
.gyst-sheet-header {
  display: flex;
  align-items: center;
  padding: 12px 14px 10px;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 25%, transparent);
}
.gyst-sheet-title {
  flex: 1 1 auto;
  padding: 0;          /* override .gyst-section-label padding */
}
.gyst-sheet-close {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  background: transparent;
  border: 0;
  color: var(--text-dim);
  font-size: var(--fs16, 16px);
  cursor: pointer;
  border-radius: 50%;
  line-height: 1;
}
.gyst-sheet-close:hover {
  background: var(--gyst-surface-2);
  color: var(--text);
}
.gyst-sheet-body {
  flex: 1 1 auto;
  overflow: auto;
  padding: 12px 14px 6px;
}
.gyst-sheet-footer {
  display: flex;
  gap: 8px;
  padding: 10px 14px 14px;
  border-top: 1px solid color-mix(in srgb, var(--border) 25%, transparent);
}
.gyst-sheet-footer .gyst-btn-primary,
.gyst-sheet-footer .gyst-btn-destructive {
  flex: 1 1 auto;
}
.gyst-sheet-cancel {
  flex: 1 1 auto;
  padding: 12px 14px;
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  border-radius: var(--gyst-nested-radius);
  color: var(--text-dim);
  font-family: inherit;
  font-size: var(--fs14, 14px);
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.gyst-sheet-cancel:hover {
  background: var(--gyst-surface-2);
  color: var(--text);
}

/* Timezone sheet inner content */
.gyst-timezone-sheet {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 320px;
}
.gyst-sheet-search {
  width: 100%;
  padding: 10px 12px;
  background: var(--gyst-surface-2);
  border: 1px solid color-mix(in srgb, var(--border) 35%, transparent);
  border-radius: 10px;
  color: var(--text);
  font-size: var(--fs14, 14px);
  font-family: inherit;
  outline: none;
}
.gyst-sheet-search::placeholder { color: var(--text-dimmer); }
.gyst-sheet-search:focus {
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
}
.gyst-sheet-scroll-list {
  display: flex;
  flex-direction: column;
  max-height: 360px;
  overflow-y: auto;
  background: var(--gyst-surface-2);
  border: 1px solid color-mix(in srgb, var(--border) 25%, transparent);
  border-radius: 10px;
}
.gyst-sheet-list-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: transparent;
  border: 0;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 12%, transparent);
  color: var(--text);
  font-size: var(--fs14, 14px);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.gyst-sheet-list-row:last-child { border-bottom: 0; }
.gyst-sheet-list-row:hover { background: color-mix(in srgb, var(--accent) 8%, transparent); }
.gyst-sheet-list-label { flex: 1 1 auto; }
.gyst-sheet-list-check {
  flex: 0 0 auto;
  color: var(--accent);
  font-weight: 700;
  visibility: hidden;
}
.gyst-sheet-list-row.is-selected .gyst-sheet-list-check { visibility: visible; }
.gyst-sheet-list-row.is-selected .gyst-sheet-list-label {
  color: var(--accent-active, var(--accent));
  font-weight: 600;
}
.gyst-sheet-empty {
  padding: 20px 12px;
  text-align: center;
  color: var(--text-dimmer);
  font-size: var(--fs13, 13px);
}

/* Add Saved Place button wrapper — sits below the collapsible
   panel inside the Personal Info section body. */
.gyst-saved-places-add {
  margin-top: 10px;
}

/* ──────────────────────────────────────────────────────────
   Profile nav (grouped tile rows)
   Three groups (Personal / Intelligence / Connections) replace the
   old uppercase section-label headers. Each group is a .gyst-panel
   of .gyst-nav-row tiles that toggle their corresponding section
   body. THEME + CALENDAR PREFERENCES live outside the nav (hero
   card driven).
   ────────────────────────────────────────────────────────── */
.gyst-nav-group {
  margin-bottom: 18px;
}
.gyst-nav-group:last-child {
  margin-bottom: 22px;
}
/* Bold uppercase accent label + hairline rule filling the remaining width, so
   each category reads as a clear, scannable section break (mirrors iOS
   GystUI.MenuSectionHeader). */
.gyst-nav-group-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--gyst-font-stack);
  font-size: var(--fs13, 13px);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--accent-secondary, var(--accent));
  padding: 24px 4px 10px;
  margin: 0;
}
.gyst-nav-group-title::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--border);
  opacity: 0.5;
}

/* Tile row — composes like .gyst-setting-row but with a two-line
   text block (title + subtitle) sitting between the icon tile and
   chevron. Chevron rotates on .open to mirror the matching section
   state, set by syncNavTileStates() in profile.js. */
.gyst-nav-row {
  display: flex;
  align-items: center;
  gap: var(--gyst-row-gap);
  min-height: var(--gyst-row-min-h);
  padding: var(--gyst-row-py) var(--gyst-row-px);
  cursor: pointer;
  user-select: none;
  transition: background 0.15s ease;
}
/* External-link rows (Privacy Policy / Terms of Use, Task 5) render this
 * class on an <a> instead of a <div> so target=_blank/rel=noopener are
 * native — strip the browser's default link underline/color so it reads
 * identically to the section-toggle rows around it. */
a.gyst-nav-row {
  text-decoration: none;
  color: inherit;
}
/* Hairline between consecutive nav rows. renderProfileNav() interleaves
   .profile-section bodies between rows (so each row's expansion can
   live inline beneath it), which breaks adjacent-sibling selectors —
   `:not(:first-child)` works regardless of what sits between rows. */
.gyst-panel > .gyst-nav-row:not(:first-child) {
  border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
}
.gyst-nav-row:hover {
  background: color-mix(in srgb, var(--accent) 6%, transparent);
}
.gyst-nav-row-text {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.gyst-nav-row-title {
  font-family: var(--gyst-font-stack);
  font-size: var(--fs15, 15px);
  font-weight: 600;
  color: var(--text);
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gyst-nav-row-subtitle {
  font-family: var(--gyst-font-stack);
  font-size: var(--fs12, 12px);
  font-weight: 400;
  color: var(--text-dim);
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gyst-nav-row .gyst-row-chevron {
  transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Active section: accent-tinted fill + a 3pt accent left-edge bar (inset
   box-shadow avoids the layout shift a border-left would cause), so the open
   section is unmistakable (mirrors the iOS active-card treatment). */
.gyst-nav-row.open {
  background: color-mix(in srgb, var(--accent) 7%, transparent);
  box-shadow: inset 3px 0 0 var(--accent);
}
.gyst-nav-row.open:hover {
  background: color-mix(in srgb, var(--accent) 9%, transparent);
}
.gyst-nav-row.open .gyst-row-chevron {
  transform: rotate(90deg);
  color: var(--accent);
}
.gyst-nav-row.open .gyst-nav-row-title {
  color: var(--accent);
}
.gyst-nav-row.open .gyst-icon-tile {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 18%, transparent);
}

/* Nested sub-category rows (e.g. Weather Insights / Sports Badges under
   Insights & Badges). Indented behind a guide rail so they read as children
   of the section above (mirrors iOS subRow). */
.gyst-subrow-group { display:flex; gap:12px; padding-left:2px; }
.gyst-subrow-group::before { content:""; width:3px; flex:0 0 3px; border-radius:2px;
  background: color-mix(in srgb, var(--border) 55%, transparent); align-self:stretch; }
.gyst-subrow-list { flex:1 1 auto; min-width:0; display:flex; flex-direction:column; }
.gyst-sub-row { display:flex; align-items:center; gap:10px; padding:8px 4px; cursor:pointer; user-select:none; }
.gyst-sub-row + .gyst-sub-row, .gyst-sub-row-body + .gyst-sub-row { border-top:1px solid color-mix(in srgb, var(--border) 60%, transparent); }
.gyst-sub-row .gyst-icon-tile { width:28px; height:28px; flex:0 0 28px; }
.gyst-sub-row-text { flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:1px; }
.gyst-sub-row-title { font-family:var(--gyst-font-stack); font-size: var(--fs14, 14px); font-weight:600; color:var(--text); }
.gyst-sub-row-subtitle { font-family:var(--gyst-font-stack); font-size: var(--fs12, 12px); font-weight:400; color:var(--text-dim); }
.gyst-sub-row .gyst-row-chevron { transition: transform .25s cubic-bezier(.4,0,.2,1); }
.gyst-sub-row.open .gyst-row-chevron { transform: rotate(90deg); color:var(--accent); }
.gyst-sub-row.open .gyst-sub-row-title { color:var(--accent); }
.gyst-sub-row.open .gyst-icon-tile { color:var(--accent); background: color-mix(in srgb, var(--accent) 18%, transparent); }
.gyst-sub-row-body { padding: 6px 0 10px; }
.gyst-sub-row-body[hidden] { display:none; }

/* ──────────────────────────────────────────────────────────
   ThemedToggle (div-based, .on/.off classes)
   The legacy `<input type="checkbox">.gyst-toggle` rules above use
   :checked + ::before, so they don't conflict with the .on/.off
   selectors we add here for the GystUI.themedToggle helper.
   ────────────────────────────────────────────────────────── */
.gyst-toggle.off {
  background: var(--bg-3);
  border: 1px solid color-mix(in srgb, var(--border) 65%, transparent);
}
.gyst-toggle.on {
  background: var(--accent);
  border: 1px solid transparent;
}
.gyst-toggle-thumb {
  width: 18px;
  height: 18px;
  border-radius: 3px;
  position: absolute;
  top: 2px;
  transition: left 0.2s cubic-bezier(0.34, 1.56, 0.64, 1),
              background 0.2s ease;
  pointer-events: none;
}
.gyst-toggle.off .gyst-toggle-thumb {
  left: 2px;
  background: var(--text-dimmer);
}
.gyst-toggle.on .gyst-toggle-thumb {
  left: 22px;
  background: var(--bg);
}
.gyst-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Disabled switch — dimmed, non-interactive, still occupies its slot so the
   surrounding layout never reflows. iOS parity: ThemedToggle(enabled:). */
.gyst-toggle.is-disabled { opacity: 0.4; cursor: default; pointer-events: none; }

/* ──────────────────────────────────────────────────────────
   Inline date picker (GystUI.inlineDatePicker)
   Compact calendar widget — year strip on top, month nav, 7×N day
   grid. Used by the birthday row (and any future date-pick surface)
   in place of <input type="date">.
   ────────────────────────────────────────────────────────── */
.gyst-date-picker {
  padding: 8px 12px 12px;
  font-family: var(--gyst-font-stack);
}

.gyst-date-picker-month-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 4px 8px;
}
.gyst-date-picker-month-label {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: transparent;
  border: 0;
  padding: 4px 8px;
  border-radius: 6px;
  font-family: inherit;
  font-size: var(--fs16, 16px);
  font-weight: 700;
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s ease;
}
.gyst-date-picker-month-label:hover {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.gyst-date-picker-month-chev {
  font-size: var(--fs12, 12px);
  color: var(--text-dim);
  margin-left: 4px;
  line-height: 1;
}

/* Body wrapper — fixes the picker height so swapping between the day
   grid and the month/year split panel doesn't make the parent row
   jump. Day grid height (≈6 weeks × 38px + DOW header) ≈ 256px. */
.gyst-date-picker-body {
  position: relative;
  min-height: 256px;
}
.gyst-date-picker-nav-btn {
  background: none;
  border: none;
  color: var(--accent);
  font-family: inherit;
  font-size: var(--fs20, 20px);
  line-height: 1;
  cursor: pointer;
  padding: 4px 10px;
  border-radius: 6px;
  transition: background 0.15s ease, opacity 0.15s ease;
}
.gyst-date-picker-nav-btn:hover {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.gyst-date-picker-nav-btn:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}

.gyst-date-picker-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
  justify-items: center;
}
.gyst-date-picker-dow {
  font-size: var(--fs11, 11px);
  font-weight: 600;
  color: var(--text-dimmer);
  text-align: center;
  padding: 4px 0;
  letter-spacing: 0.3px;
}
.gyst-date-picker-day {
  width: 36px;
  height: 36px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs14, 14px);
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
  color: var(--text);
  user-select: none;
}
.gyst-date-picker-day:hover {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.gyst-date-picker-day.other-month {
  opacity: 0.3;
}
.gyst-date-picker-day.today {
  text-decoration: underline;
  color: var(--text-dim);
}
.gyst-date-picker-day.selected {
  background: var(--accent-active, var(--accent));
  color: var(--accent-active-text, #fff);
  font-weight: 700;
}
.gyst-date-picker-day.selected.today {
  text-decoration: none;
}
.gyst-date-picker-day.disabled {
  opacity: 0.2;
  pointer-events: none;
  cursor: default;
}

/* ── Month / Year split panel (replaces day grid when month label
   is tapped). Two columns of equal 220px height: a year drum (left)
   and a 4×3 month abbreviation grid (right). The vertical hairline
   is provided by the drum wrap's right border. ─────────────────── */
.gyst-date-picker-split {
  display: flex;
  flex-direction: row;
  min-height: 256px;
  align-items: flex-start;
  gap: 8px;
}

/* Year drum — matches the time-picker aesthetic. The wrap holds the
   centred selection pill (z-index 1) + top/bottom gradient fades
   (pseudo, z-index 2), and the drum itself scrolls under them
   (z-index 3). Drum height (220) and item height (36) are mirrored
   in the JS DRUM_H / DRUM_ITEM_H constants. */
.gyst-date-year-drum-wrap {
  flex: 0 0 40%;
  position: relative;
  height: 220px;
  overflow: hidden;
  border-right: 1px solid color-mix(in srgb, var(--border) 20%, transparent);
}
.gyst-date-year-drum-wrap::before,
.gyst-date-year-drum-wrap::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  height: 72px;
  pointer-events: none;
  z-index: 2;
}
.gyst-date-year-drum-wrap::before {
  top: 0;
  background: linear-gradient(to bottom,
                              var(--bg-2),
                              color-mix(in srgb, var(--bg-2) 0%, transparent));
}
.gyst-date-year-drum-wrap::after {
  bottom: 0;
  background: linear-gradient(to top,
                              var(--bg-2),
                              color-mix(in srgb, var(--bg-2) 0%, transparent));
}

/* Centred selection pill — static visual indicator of the snap zone.
   The .selected item floats over this; the pill itself sits behind
   the drum (z-index 1, drum is 3). 36px tall = item height,
   top: calc(50% - 18px) keeps it dead centre. */
.gyst-date-year-drum-pill {
  position: absolute;
  top: calc(50% - 18px);
  left: 4px;
  right: 4px;
  height: 36px;
  border-radius: 8px;
  background: var(--bg-3);
  border: 1px solid color-mix(in srgb, var(--border) 20%, transparent);
  z-index: 1;
  pointer-events: none;
}

/* Drum scroll container. scroll-snap-type: y mandatory makes the
   browser snap to whichever item is closest on touch/wheel release.
   ::before/::after add the half-drum padding so the first and last
   year can centre in the pill. */
.gyst-date-year-drum {
  position: relative;
  z-index: 3;
  height: 220px;
  overflow-y: scroll;
  overflow-x: hidden;
  scroll-snap-type: y mandatory;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.gyst-date-year-drum::-webkit-scrollbar { display: none; }
.gyst-date-year-drum::before,
.gyst-date-year-drum::after {
  content: '';
  display: block;
  height: 92px; /* (drumH 220 - itemH 36) / 2 */
}
.gyst-date-year-drum:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.gyst-date-year-item {
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  scroll-snap-align: center;
  scroll-snap-stop: always;
  font-family: var(--gyst-font-stack);
  font-size: var(--fs15, 15px);
  font-weight: 400;
  color: var(--text-dim);
  cursor: pointer;
  border-radius: 8px;
  transition: background 0.1s ease, color 0.1s ease, font-weight 0.1s ease;
  flex-shrink: 0;
  user-select: none;
}
.gyst-date-year-item:hover {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.gyst-date-year-item.selected {
  color: var(--accent-active, var(--accent));
  font-weight: 700;
}

.gyst-date-picker-month-grid {
  flex: 1 1 60%;
  height: 220px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(4, 1fr);
  gap: 6px;
  padding: 0 0 0 8px;
}
.gyst-date-picker-month-cell {
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: transparent;
  border-radius: 8px;
  padding: 10px 6px;
  font-family: inherit;
  font-size: var(--fs14, 14px);
  font-weight: 500;
  color: var(--text);
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.gyst-date-picker-month-cell:hover {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.gyst-date-picker-month-cell.selected {
  background: var(--accent-active, var(--accent));
  color: var(--accent-active-text, #fff);
  font-weight: 700;
}
.gyst-date-picker-month-cell.disabled {
  opacity: 0.25;
  pointer-events: none;
  cursor: default;
}

/* ──────────────────────────────────────────────────────────
   Person cards (family + friends)
   Each person — kid, family adult, pending family request, or
   friend — renders as a self-contained rounded card with avatar,
   name, subtitle, optional status icons, and a chevron. Tapping
   the card head expands an inline body of action buttons.
   One-card-at-a-time accordion is enforced by the JS that creates
   the cards.
   ────────────────────────────────────────────────────────── */
.gyst-person-card {
  background: var(--bg-2);
  border: 1px solid color-mix(in srgb, var(--border) 20%, transparent);
  border-radius: 14px;
  margin-bottom: 10px;
  overflow: hidden;
  transition: border-color 0.15s ease;
}
.gyst-person-card:hover {
  border-color: color-mix(in srgb, var(--border) 35%, transparent);
}
.gyst-person-card-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  cursor: pointer;
  user-select: none;
}
.gyst-person-avatar {
  flex: 0 0 auto;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--gyst-font-stack);
  font-size: var(--fs20, 20px);
  font-weight: 700;
  color: #fff; /* deliberate: fill is a per-person DATA color, not a theme token */
  letter-spacing: -0.5px;
}
.gyst-person-info {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.gyst-person-name {
  font-family: var(--gyst-font-stack);
  font-size: var(--fs15, 15px);
  font-weight: 700;
  color: var(--text);
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.gyst-person-subtitle {
  font-family: var(--gyst-font-stack);
  font-size: var(--fs12, 12px);
  font-weight: 500;
  color: var(--text-dim);
  line-height: 1.3;
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.gyst-person-status {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 6px;
  margin-right: 4px;
}
.gyst-person-status-icon {
  font-size: var(--fs14, 14px);
  color: var(--accent);
  opacity: 0.6;
  line-height: 1;
}
.gyst-person-chev {
  flex: 0 0 auto;
  font-size: var(--fs18, 18px);
  color: var(--text-dimmer);
  line-height: 1;
  transition: transform 0.18s ease, color 0.18s ease;
}
.gyst-person-card.open .gyst-person-chev {
  transform: rotate(90deg);
  color: var(--accent);
}

/* Inline expansion body. Default closed (max-height 0); open state
   reveals the action row beneath the head. */
.gyst-person-card-body {
  max-height: 0;
  overflow: hidden;
  padding: 0 16px;
  transition: max-height 0.25s cubic-bezier(0.4, 0, 0.2, 1),
              padding 0.25s ease;
}
.gyst-person-card.open .gyst-person-card-body {
  /* Tall enough for inline panels that open INSIDE an open card (the family
     request's kind choice, Accept / Decline / Remove confirms) at every
     density tier — 280px clipped them (2026-09-30). */
  max-height: 1600px;
  padding: 4px 16px 14px;
  border-top: 1px solid color-mix(in srgb, var(--border) 18%, transparent);
}
.gyst-person-card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}

/* Action buttons + pills used inside the expansion body. Compact —
   designed to fit several on a line without overpowering the card
   visual itself. */
.gyst-card-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--bg-3);
  border: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  border-radius: 8px;
  padding: 6px 10px;
  font-family: var(--gyst-font-stack);
  font-size: var(--fs12, 12px);
  font-weight: 500;
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.gyst-card-btn:hover {
  background: color-mix(in srgb, var(--accent) 8%, var(--bg-3));
  border-color: color-mix(in srgb, var(--accent) 30%, transparent);
}
.gyst-card-btn-accent {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-color: color-mix(in srgb, var(--accent) 30%, transparent);
  color: var(--accent);
}
.gyst-card-btn-danger {
  color: var(--danger, #e68a8a);
  border-color: color-mix(in srgb, var(--danger, #e68a8a) 30%, transparent);
}
.gyst-card-btn-danger:hover {
  background: color-mix(in srgb, var(--danger, #e68a8a) 10%, var(--bg-3));
  border-color: color-mix(in srgb, var(--danger, #e68a8a) 50%, transparent);
}
.gyst-card-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--fs11, 11px);
  font-weight: 600;
  color: var(--text-dim);
  background: var(--bg-3);
  border-radius: 999px;
  padding: 3px 9px;
}
.gyst-card-pill-success {
  color: var(--success, #4caf50);
  background: color-mix(in srgb, var(--success, #4caf50) 12%, transparent);
}
.gyst-card-color-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  display: inline-block;
}

/* ──────────────────────────────────────────────────────────
   Accent action button (full-width, soft tinted)
   Used by "Add Family Member", "Generate Invite Link", and
   "Add a Friend" — matches iOS GystUI.AccentActionButton(.tinted).
   ────────────────────────────────────────────────────────── */
.gyst-accent-action-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  margin-top: 10px;
  padding: 14px 16px;
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
  border-radius: 14px;
  font-family: var(--gyst-font-stack);
  font-size: var(--fs15, 15px);
  font-weight: 600;
  color: var(--accent);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease,
              transform 0.05s ease;
}
.gyst-accent-action-btn:hover {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-color: color-mix(in srgb, var(--accent) 38%, transparent);
}
.gyst-accent-action-btn:active {
  transform: scale(0.99);
}
.gyst-accent-action-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.gyst-accent-action-btn-icon {
  font-size: var(--fs16, 16px);
  line-height: 1;
}

/* ──────────────────────────────────────────────────────────
   AI Features + Daily Brief shared chassis
   ────────────────────────────────────────────────────────── */

/* Intro paragraph above the MODELS section. Used by both renderers
   for the "choose a model / add API keys above" copy. */
.gyst-section-desc {
  font-family: var(--gyst-font-stack);
  font-size: var(--fs12, 12px);
  color: var(--text-dim);
  margin: 0 4px 12px;
  line-height: 1.45;
}
.gyst-section-desc a {
  color: var(--accent);
  text-decoration: none;
}
.gyst-section-desc a:hover {
  text-decoration: underline;
}

/* Row that hosts a native <select> on the right edge — used for the
   four MODELS rows + the Voice provider row. The select itself wears
   `.gyst-select-row-select` so its appearance ditches the OS chrome
   in favor of a chip that matches the row rhythm. */
.gyst-select-row {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: var(--gyst-row-gap);
  min-height: var(--gyst-row-min-h);
  padding: var(--gyst-row-py) var(--gyst-row-px);
  box-sizing: border-box;
}
.gyst-panel > .gyst-select-row + .gyst-select-row::before,
.gyst-panel > .gyst-select-row + .gyst-row::before,
.gyst-panel > .gyst-row + .gyst-select-row::before {
  content: "";
  display: block;
  position: absolute;
  left: var(--gyst-row-px);
  right: 0;
  top: 0;
  height: 1px;
  background: color-mix(in srgb, var(--border) 60%, transparent);
}
.gyst-select-row-info {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.gyst-select-row-info .gyst-row-label {
  font-size: var(--fs14, 14px);
  font-weight: 600;
  color: var(--text);
  line-height: 1.25;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Subtitle is too cramped on the AI-model rows (icon + label +
   select chip + ▾ all on one line); hide it so the select stays
   right-aligned. The subtitle metadata is still in the DOM so
   screen readers can announce the row's purpose. */
.gyst-select-row-sub {
  display: none;
}

/* Legacy: native <select> styled as an inline chip. No longer
   produced by buildSelectRow (AI model selectors moved to the
   buildSelectMenuRow / .gyst-menu-wrap pattern), but kept for any
   caller that still wears the class. */
.gyst-select-row-select {
  flex: 0 0 auto;
  flex-shrink: 0;
  min-width: 100px;
  max-width: 160px;
  background: var(--bg-3);
  border: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  border-radius: 8px;
  padding: 6px 28px 6px 10px;
  font-family: var(--gyst-font-stack);
  font-size: var(--fs12, 12px);
  color: var(--text);
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 12px 12px;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}
.gyst-select-row-select:hover {
  border-color: color-mix(in srgb, var(--accent) 35%, transparent);
}
.gyst-select-row-select:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--accent) 50%, transparent);
}

/* Voice Name row — a custom widget container (#ttsVoiceContainer)
   sits where the select would, so it shouldn't get chip styling but
   should align right with the same min/max width. */
.gyst-voice-name-slot {
  flex: 0 0 auto;
  max-width: 220px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
}

/* Toggle row variant with icon tile + optional description below
   the label. Builds on .gyst-toggle-row (which only had icon-less
   row anatomy). */
.gyst-toggle-row.gyst-toggle-row-rich {
  align-items: flex-start;
  padding: var(--gyst-row-py) var(--gyst-row-px);
  gap: var(--gyst-row-gap);
}
.gyst-toggle-row-rich .gyst-icon-tile {
  margin-top: 2px;
}
.gyst-toggle-row-info {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.gyst-toggle-row-info .gyst-row-label {
  font-size: var(--fs14, 14px);
  font-weight: 600;
  color: var(--text);
  line-height: 1.25;
}
.gyst-toggle-row-desc {
  font-family: var(--gyst-font-stack);
  font-size: var(--fs11, 11px);
  font-weight: 400;
  color: var(--text-dim);
  line-height: 1.4;
  margin-top: 4px;
  white-space: normal;
}
.gyst-toggle-row-rich .gyst-toggle {
  margin-top: 4px;
}

/* Textarea for Custom Instructions — full-width with the same chip
   surface ladder used by Saved Places address rows. */
.gyst-textarea {
  display: block;
  width: 100%;
  min-height: 160px;
  background: var(--bg-3);
  border: 1px solid color-mix(in srgb, var(--border) 30%, transparent);
  border-radius: 12px;
  padding: 12px 14px;
  font-family: var(--gyst-font-stack);
  font-size: var(--fs14, 14px);
  color: var(--text);
  resize: vertical;
  line-height: 1.5;
  box-sizing: border-box;
  margin-top: 4px;
  margin-bottom: 12px;
}
.gyst-textarea:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--accent) 50%, transparent);
}
.gyst-textarea::placeholder {
  color: var(--text-dimmer);
  font-size: var(--fs13, 13px);
}

/* Generate Now — accent-action-btn variant with a slightly louder
   icon glyph (the lightning bolt is a high-contrast call-to-action). */
.gyst-brief-generate-btn {
  margin-top: 4px;
}

/* ──────────────────────────────────────────────────────────
   Section-content alignment overrides
   The profile section body, panels, section labels, and intro
   text all share the same left/right baseline so AI Features +
   Insights & Badges + Daily Brief read as one continuous column.
   These rules sit after profile.css in <head> so they win on
   equal-specificity for `.profile-section-body`.
   ────────────────────────────────────────────────────────── */
.profile-section.collapsible .profile-section-body {
  padding: 4px 0 12px;
}

/* Panels span the full width of the section body — section_body
   provides 0 horizontal padding now; everything inside (panel
   surfaces, labels, intro) lives flush to that edge so the iOS
   parity is consistent without a double-inset look. */
.gyst-panel {
  margin: 0 0 8px;
}

/* Section label — already has 4px horizontal padding, but make
   the rule explicit + reset top so spacing stacks predictably
   when sections sit back-to-back. */
.gyst-section-label {
  padding-left: 4px;
  padding-right: 4px;
}

/* Intro paragraph (used above MODELS in AI Features) — sits at
   the same left edge as section labels so the whole AI Features
   column reads as one indent. */
.gyst-section-desc {
  padding: 0 4px;
  margin: 0 0 12px;
}

/* ──────────────────────────────────────────────────────────
   Connector cards (Gmail / Apple Health)
   Tap-to-expand row with a status chip on the right edge.
   Connected providers reveal an action body underneath; for
   non-connected providers the chip itself is the tap target
   (Connect → triggers OAuth start).
   ────────────────────────────────────────────────────────── */
.gyst-connector-card {
  overflow: hidden;
}
.gyst-panel > .gyst-connector-card + .gyst-connector-card::before {
  content: "";
  display: block;
  height: 1px;
  background: color-mix(in srgb, var(--border) 40%, transparent);
  margin: 0 var(--gyst-row-px);
}
.gyst-connector-head {
  display: flex;
  align-items: center;
  gap: var(--gyst-row-gap);
  min-height: var(--gyst-row-min-h);
  padding: var(--gyst-row-py) var(--gyst-row-px);
  user-select: none;
}
.gyst-connector-head.is-tappable {
  cursor: pointer;
}
.gyst-connector-info {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.gyst-connector-name {
  font-family: var(--gyst-font-stack);
  font-size: var(--fs14, 14px);
  font-weight: 600;
  color: var(--text);
  line-height: 1.25;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gyst-connector-sub {
  font-family: var(--gyst-font-stack);
  font-size: var(--fs11, 11px);
  font-weight: 400;
  color: var(--text-dim);
  line-height: 1.3;
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gyst-connector-chip {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: 999px;
  font-family: var(--gyst-font-stack);
  font-size: var(--fs11, 11px);
  font-weight: 600;
  border: 1px solid transparent;
  white-space: nowrap;
}
.gyst-connector-chip.is-on {
  color: var(--accent-active, var(--accent));
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-color: color-mix(in srgb, var(--accent) 28%, transparent);
}
.gyst-connector-chip.is-tap {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  border-color: color-mix(in srgb, var(--accent) 24%, transparent);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.gyst-connector-chip.is-tap:hover {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-color: color-mix(in srgb, var(--accent) 36%, transparent);
}
.gyst-connector-chip.is-off {
  color: var(--text-dimmer);
  background: var(--bg-3);
  border-color: color-mix(in srgb, var(--border) 40%, transparent);
}
.gyst-connector-chev {
  flex: 0 0 auto;
  font-size: var(--fs18, 18px);
  color: var(--text-dimmer);
  line-height: 1;
  transition: transform 0.18s ease, color 0.18s ease;
}
.gyst-connector-card.open .gyst-connector-chev {
  transform: rotate(90deg);
  color: var(--accent);
}
.gyst-connector-card.is-disabled {
  opacity: 0.7;
}

.gyst-connector-body {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.25s cubic-bezier(0.4, 0, 0.2, 1),
              padding 0.25s ease;
  padding: 0 var(--gyst-row-px);
}
.gyst-connector-card.open .gyst-connector-body {
  max-height: 320px;
  padding: 4px var(--gyst-row-px) 12px;
  border-top: 1px solid color-mix(in srgb, var(--border) 18%, transparent);
}
.gyst-connector-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding-top: 8px;
}

/* Inline horizontal action strip — web parity with the iOS
   GystUI.InlineActionStrip used on EventDetailSheet's address row.
   Lays icon-circle + label items evenly across its width; each
   item is its own button so keyboard + a11y navigation work. The
   circle picks up the accent palette in light + dark themes. */
.gyst-action-strip {
  display: flex;
  width: 100%;
  padding: 12px 0 4px;
  gap: 8px;
}
.gyst-action-strip[hidden] {
  display: none;
}
.gyst-action-strip-item {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-family: var(--gyst-font-stack);
  color: inherit;
  transition: transform 0.15s ease;
}
.gyst-action-strip-item:hover {
  transform: translateY(-1px);
}
.gyst-action-strip-item:active {
  transform: translateY(0);
}
.gyst-action-strip-circle {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--bg-2);
  color: var(--accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.15s ease;
}
.gyst-action-strip-item:hover .gyst-action-strip-circle {
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-2));
}
.gyst-action-strip-circle i[data-lucide],
.gyst-action-strip-circle svg {
  width: 18px;
  height: 18px;
  color: currentColor;
  flex-shrink: 0;
}
.gyst-action-strip-label {
  font-size: var(--fs11, 11px);
  font-weight: 600;
  color: var(--text-dim);
  letter-spacing: 0.2px;
  text-align: center;
  line-height: 1.2;
}
.gyst-action-strip-item.is-destructive .gyst-action-strip-circle {
  color: var(--danger);
}
.gyst-action-strip-item.is-destructive .gyst-action-strip-label {
  color: var(--danger);
}

/* ──────────────────────────────────────────────────────────
   ActionTileButton — compact icon+label tile.
   Used in the EventDetailSheet 2×3 action grid.
   ────────────────────────────────────────────────────────── */
.gyst-action-tile-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
/* Two tiles share the row evenly, like iOS's HStack of ActionTileButtons
   (maxWidth: .infinity) when one tile is hidden. */
.gyst-action-tile-grid--two {
  grid-template-columns: repeat(2, 1fr);
}

.gyst-action-tile {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 40px; /* was height — tier 2/3 type must not clip */
  padding: 0 10px;
  background: var(--bg-2);
  border: 0.5px solid var(--gyst-panel-border);
  border-radius: var(--gyst-panel-radius);
  font-family: var(--gyst-font-stack);
  font-size: var(--fs13, 13px);
  font-weight: 500;
  color: var(--text);
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
}
.gyst-action-tile:hover { background: var(--bg-3); }
.gyst-action-tile:active { transform: scale(0.98); }
.gyst-action-tile:disabled { opacity: 0.6; cursor: default; }

.gyst-action-tile .gyst-action-tile-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.gyst-action-tile .gyst-action-tile-icon i[data-lucide],
.gyst-action-tile .gyst-action-tile-icon svg {
  width: 16px;
  height: 16px;
  color: currentColor;
}

.gyst-action-tile .gyst-action-tile-label {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Trailing chevron marker for expandable tiles. Inline at the end of
   the label, baseline-aligned with the icon + text — matches iOS. */
.gyst-action-tile .gyst-action-tile-chevron {
  flex-shrink: 0;
  font-size: var(--fs11, 11px);
  line-height: 1;
  color: var(--text-dimmer);
  transition: transform 0.18s ease;
}
.gyst-action-tile.expanded .gyst-action-tile-chevron {
  transform: rotate(180deg);
}

/* Destructive variant — Delete tile. */
.gyst-action-tile.is-destructive {
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  border-color: color-mix(in srgb, var(--danger) 30%, transparent);
}
.gyst-action-tile.is-destructive:hover {
  background: color-mix(in srgb, var(--danger) 18%, transparent);
}

/* ──────────────────────────────────────────────────────────
   Tile expansion card — surface that hosts inline expansion
   content between row 1 and row 2 (or below row 2) of the
   ActionTileButton grid.
   ────────────────────────────────────────────────────────── */
.gyst-tile-expansion-card {
  background: var(--bg-2);
  border-radius: var(--gyst-panel-radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* ──────────────────────────────────────────────────────────
   Confirm row — destructive-tinted "Confirm Delete / Cancel"
   row used inside Delete and Cancel expansions.
   ────────────────────────────────────────────────────────── */
.gyst-confirm-row {
  display: flex;
  align-items: center;
  gap: var(--gyst-row-gap);
  width: 100%;
  min-height: 44px;
  padding: 12px var(--gyst-row-px);
  background: transparent;
  border: 0;
  font-family: inherit;
  font-size: var(--fs14, 14px);
  font-weight: 600;
  color: var(--danger);
  cursor: pointer;
  text-align: left;
}
.gyst-confirm-row:hover { background: color-mix(in srgb, var(--danger) 8%, transparent); }
.gyst-confirm-row:disabled { opacity: 0.6; cursor: default; }
.gyst-confirm-row .gyst-confirm-row-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  flex-shrink: 0;
}
.gyst-confirm-row .gyst-confirm-row-icon i[data-lucide],
.gyst-confirm-row .gyst-confirm-row-icon svg {
  width: 14px;
  height: 14px;
  color: var(--danger);
}

/* Hairline divider between confirm rows inside an expansion card. */
.gyst-tile-expansion-card > .gyst-confirm-row + .gyst-confirm-row {
  border-top: 0.5px solid var(--gyst-row-divider);
}

/* ──────────────────────────────────────────────────────────
   Detail row — read-only icon + text row used in the data
   section of the detail modal.
   ────────────────────────────────────────────────────────── */
.gyst-detail-rows {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.gyst-detail-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  font-family: var(--gyst-font-stack);
}
.gyst-detail-row > .gyst-detail-row-icon {
  flex-shrink: 0;
  width: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding-top: 1px;
  color: var(--accent);
}
.gyst-detail-row > .gyst-detail-row-icon i[data-lucide],
.gyst-detail-row > .gyst-detail-row-icon svg {
  width: 18px;
  height: 18px;
  color: currentColor;
}
.gyst-detail-row .gyst-detail-row-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.gyst-detail-row .gyst-detail-row-text {
  font-size: var(--fs15, 15px);
  line-height: 1.4;
  color: var(--text);
  word-wrap: break-word;
}
.gyst-detail-row .gyst-detail-row-sub {
  font-size: var(--fs12, 12px);
  color: var(--text-dim);
}

/* Inline duration chip used in the time-range row. */
.gyst-detail-row .gyst-detail-row-chip {
  display: inline-flex;
  align-items: center;
  margin-left: 8px;
  padding: 2px 6px;
  background: var(--bg-2);
  border-radius: 5px;
  font-size: var(--fs11, 11px);
  font-weight: 500;
  color: var(--text-dim);
  vertical-align: middle;
}

/* Tappable detail row (used by location). Same geometry but adds
   a chevron and click affordance. The action strip lives outside
   this row and is sibling-toggled by the JS. */
.gyst-detail-row.is-tappable {
  background: transparent;
  border: 0;
  padding: 0;
  width: 100%;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: inherit;
}
.gyst-detail-row.is-tappable .gyst-detail-row-chevron {
  flex-shrink: 0;
  margin-left: 8px;
  padding-top: 6px;
  color: var(--text-dimmer);
  transition: transform 0.18s ease;
  font-size: var(--fs12, 12px);
  line-height: 1;
}
.gyst-detail-row.is-tappable.open .gyst-detail-row-chevron {
  transform: rotate(180deg);
}

/* Notes row "Show more / Show less" toggle. */
.gyst-detail-row .gyst-detail-notes-toggle {
  align-self: flex-start;
  background: transparent;
  border: 0;
  padding: 0;
  margin-top: 4px;
  font-family: inherit;
  font-size: var(--fs12, 12px);
  font-weight: 500;
  color: var(--accent);
  cursor: pointer;
}
.gyst-detail-row .gyst-detail-row-text.collapsed {
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Linked-tasks sub-list — renders below a detail row.
   Each task is its own clickable row, indented to align with the
   detail-row text column. */
.gyst-detail-row .gyst-linked-tasks-list {
  margin-top: 8px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.gyst-linked-task-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0;
  background: transparent;
  border: 0;
  font-family: inherit;
  font-size: var(--fs14, 14px);
  color: var(--text);
  cursor: pointer;
  text-align: left;
  width: 100%;
}
.gyst-linked-task-row.is-complete {
  color: var(--text-dim);
  text-decoration: line-through;
}
.gyst-linked-task-row .gyst-linked-task-status {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 16px;
  color: var(--text-dim);
}
.gyst-linked-task-row.is-complete .gyst-linked-task-status {
  color: var(--success);
}
.gyst-linked-task-row .gyst-linked-task-title {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gyst-linked-task-row .gyst-linked-task-chev {
  flex-shrink: 0;
  color: var(--text-dimmer);
  font-size: var(--fs11, 11px);
}

/* ──────────────────────────────────────────────────────────
   Event folder tab — colored band at the very top of the
   detail modal, signaling category color at a glance.
   ────────────────────────────────────────────────────────── */
.gyst-event-folder-tab {
  width: 100%;
  height: 28px;
  border-top-left-radius: var(--radius-lg);
  border-top-right-radius: var(--radius-lg);
}

/* ──────────────────────────────────────────────────────────
   Event detail header — 4pt left bar + title + category line.
   ────────────────────────────────────────────────────────── */
.gyst-event-header {
  display: flex;
  align-items: stretch;
  gap: 12px;
  min-height: 48px;
  font-family: var(--gyst-font-stack);
}
.gyst-event-header > .gyst-event-header-bar {
  flex-shrink: 0;
  width: 4px;
  border-radius: 2px;
}
.gyst-event-header > .gyst-event-header-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.gyst-event-header-title {
  font-size: var(--fs22, 22px);
  font-weight: 700;
  line-height: 1.2;
  color: var(--text);
  letter-spacing: 0;
  text-transform: none;
  margin: 0;
  word-wrap: break-word;
}
.gyst-event-header-title.cancelled {
  text-decoration: line-through;
  color: var(--text-dimmer);
}
.gyst-event-header-cat {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs13, 13px);
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gyst-event-header-cat-icon {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
}
.gyst-event-header-cat-icon i[data-lucide],
.gyst-event-header-cat-icon svg {
  width: 14px;
  height: 14px;
  color: currentColor;
}
.gyst-event-header-cat-sep {
  color: var(--text-dimmer);
}

/* ──────────────────────────────────────────────────────────
   Cancelled banner — full-width muted strip shown when the
   event is cancelled.
   ────────────────────────────────────────────────────────── */
.gyst-event-cancelled-banner {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 12px;
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  border-radius: var(--gyst-chip-radius);
  color: var(--danger);
  font-family: var(--gyst-font-stack);
}
.gyst-event-cancelled-banner-head {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--fs13, 13px);
  font-weight: 500;
}
.gyst-event-cancelled-banner-head i[data-lucide],
.gyst-event-cancelled-banner-head svg {
  width: 14px;
  height: 14px;
  color: currentColor;
}
.gyst-event-cancelled-banner-note {
  padding-left: 22px;
  font-size: var(--fs12, 12px);
  color: color-mix(in srgb, var(--danger) 85%, transparent);
}

/* ──────────────────────────────────────────────────────────
   Event detail container — vertical rhythm between sections.
   ────────────────────────────────────────────────────────── */
.gyst-event-detail-body {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 18px 20px 32px;
}

/* Single thin divider between data section and Actions panel. */
.gyst-event-detail-rule {
  height: 0.5px;
  background: var(--gyst-row-divider);
  margin: 0;
}

/* Actions panel container — section label + tile grid stack. */
.gyst-event-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.gyst-event-actions-tiles {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Sub-row inside an expansion card — share / link-task / reminder
   list rows. Smaller geometry than .gyst-row to read as compact
   list items inside the card. */
.gyst-expansion-row {
  display: flex;
  align-items: center;
  gap: var(--gyst-row-gap);
  width: 100%;
  min-height: 40px;
  padding: 10px var(--gyst-row-px);
  background: transparent;
  border: 0;
  font-family: inherit;
  font-size: var(--fs14, 14px);
  font-weight: 500;
  color: var(--text);
  cursor: pointer;
  text-align: left;
}
.gyst-expansion-row:hover { background: var(--bg-3); }
.gyst-expansion-row:disabled { opacity: 0.6; cursor: default; }
.gyst-expansion-row .gyst-expansion-row-icon {
  flex-shrink: 0;
  width: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-dim);
}
.gyst-expansion-row .gyst-expansion-row-icon i[data-lucide],
.gyst-expansion-row .gyst-expansion-row-icon svg {
  width: 14px;
  height: 14px;
  color: currentColor;
}
.gyst-expansion-row .gyst-expansion-row-label {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gyst-expansion-row .gyst-expansion-row-trailing {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  color: var(--text-dim);
  font-size: var(--fs11, 11px);
}
.gyst-expansion-row .gyst-expansion-row-color-dot {
  flex-shrink: 0;
  width: 14px;
  height: 14px;
  border-radius: 50%;
}
/* Hairline divider between adjacent expansion rows. */
.gyst-tile-expansion-card > .gyst-expansion-row + .gyst-expansion-row {
  border-top: 0.5px solid var(--gyst-row-divider);
}

/* Active-reminder rows that sit at the top of the Remind tile expansion
   (one per existing reminder, X to delete). Visually a sibling to
   .gyst-expansion-row but with the bell icon tinted accent + a trailing
   X button. */
.gyst-reminder-active-row {
  display: flex;
  align-items: center;
  gap: var(--gyst-row-gap);
  width: 100%;
  min-height: 40px;
  padding: 10px var(--gyst-row-px);
  font-family: inherit;
  font-size: var(--fs14, 14px);
  font-weight: 500;
  color: var(--text);
}
.gyst-reminder-active-row + .gyst-reminder-active-row {
  border-top: 0.5px solid var(--gyst-row-divider);
}
.gyst-reminder-active-row-icon {
  flex-shrink: 0;
  width: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--accent);
}
.gyst-reminder-active-row-icon i[data-lucide],
.gyst-reminder-active-row-icon svg {
  width: 14px;
  height: 14px;
  color: currentColor;
  /* iOS uses bell.fill on existing reminder rows (ui-ref-15) — fill the
     Lucide stroked bell with currentColor so the glyph reads filled. */
  fill: currentColor;
}
.gyst-reminder-active-row-label {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gyst-reminder-active-row-remove {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font-size: var(--fs18, 18px);
  line-height: 1;
  cursor: pointer;
  border-radius: 6px;
}
.gyst-reminder-active-row-remove:hover {
  background: var(--bg-3);
  color: var(--text);
}
/* Separator between the active-list and the option rows below it. */
.gyst-reminder-expansion-active-sep {
  height: 0.5px;
  background: var(--gyst-row-divider);
  margin: 4px 0;
}

/* ── Reminder sub-rows (Custom time / On arrive) with inline expansions ── */
/* Wraps a trigger row + collapsible content. The trigger uses the same
   chrome as a regular .gyst-expansion-row; clicking toggles the expansion
   beneath and rotates the trailing chevron. */
.gyst-reminder-subrow {
  display: flex;
  flex-direction: column;
}
.gyst-reminder-subrow + .gyst-reminder-subrow,
.gyst-reminder-subrow-trigger + .gyst-reminder-subrow,
.gyst-tile-expansion-card > .gyst-reminder-subrow + .gyst-reminder-subrow {
  border-top: 0.5px solid var(--gyst-row-divider);
}
.gyst-reminder-subrow-trigger.gyst-expansion-row .gyst-expansion-row-trailing {
  font-size: var(--fs14, 14px);
  color: var(--text-dim);
  transition: transform 180ms cubic-bezier(0.32, 0.72, 0, 1);
}
.gyst-reminder-subrow-trigger.gyst-expansion-row.open {
  background: var(--bg-3);
}
.gyst-reminder-subrow-trigger.gyst-expansion-row.open .gyst-expansion-row-trailing {
  transform: rotate(180deg);
  color: var(--accent);
}
.gyst-reminder-inline-expansion {
  background: var(--bg-3);
  padding: 12px var(--gyst-row-px);
}
/* When the inline-expansion hosts a drum (Beforehand picker), drop the
   horizontal padding so the drum's "Add reminder" row + its top-border
   span the full expansion width — matches iOS where the divider rules
   align with the row chrome above. The drum's own grid carries the
   inner side padding via .gyst-reminder-drum's 16px x-padding. */
.gyst-reminder-inline-expansion:has(> .gyst-reminder-drum) {
  padding-left: 0;
  padding-right: 0;
  padding-top: 0;
  padding-bottom: 0;
}

/* ── Inline custom-time picker (count + unit + Add) ── */
.gyst-inline-custom-time {
  display: flex;
  align-items: center;
  gap: 8px;
}
.gyst-inline-custom-count {
  width: 64px;
  height: 36px;
  padding: 0 10px;
  border-radius: 8px;
  border: 0.5px solid var(--gyst-panel-border);
  background: var(--bg-2);
  color: var(--text);
  font-size: var(--fs14, 14px);
  font-weight: 500;
  font-family: inherit;
  text-align: center;
}
.gyst-inline-custom-unit {
  flex: 1;
  height: 36px;
  padding: 0 28px 0 10px;
  border-radius: 8px;
  border: 0.5px solid var(--gyst-panel-border);
  background: var(--bg-2);
  color: var(--text);
  font-size: var(--fs14, 14px);
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'><path d='M2 4l3 3 3-3' stroke='%23888' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 10px center;
}
.gyst-inline-custom-add {
  height: 36px;
  padding: 0 16px;
  border-radius: 8px;
  border: 0;
  background: var(--accent);
  color: var(--bg-1);
  font-size: var(--fs13, 13px);
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}
.gyst-inline-custom-add:hover { filter: brightness(1.05); }

/* ── Reminder drum (Beforehand picker) ──
   iOS analog: GystUI.ReminderDrumRow + two DrumColumn primitives.
   Two scroll-snap columns side-by-side (count + unit), each showing
   3 rows: previous (dim), selected (capsule + bold), next (dim).
   Reference: /tmp/ui-ref-18-beforehand-picker.jpg. */
.gyst-reminder-drum {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
  align-items: center;
  gap: 24px;
  padding: 4px 16px 12px;
}
.gyst-reminder-drum-column {
  /* 3 visible rows × 28px = 84px column height. Top + bottom padding equal
     to one row keeps the first and last items centre-snappable. */
  height: 84px;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding: 28px 0;
  text-align: center;
  /* Disable iOS rubber-banding past the first/last item — the picker
     snaps to its rows, not to overscroll. */
  overscroll-behavior: contain;
}
.gyst-reminder-drum-column::-webkit-scrollbar { display: none; }
.gyst-reminder-drum-row {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 28px;
  line-height: 28px;
  font-size: var(--fs17, 17px);
  font-weight: 500;
  color: var(--text-dimmer);
  scroll-snap-align: center;
  scroll-snap-stop: always;
  transition: color 0.15s ease, background-color 0.15s ease;
  white-space: nowrap;
  cursor: pointer;
}
.gyst-reminder-drum-column[data-axis="count"] .gyst-reminder-drum-row {
  font-variant-numeric: tabular-nums;
}
.gyst-reminder-drum-row.is-active {
  color: var(--text);
  font-weight: 600;
  background: var(--bg-3);
  border-radius: 9999px;
  /* Horizontal capsule padding pulled in via negative margin so the
     active row's visual width hugs its content, not the column width. */
  padding: 0 18px;
  margin: 0 -18px;
}
/* Add reminder confirm button below the drum — accent-tinted row with
   plus icon, mirrors iOS ReminderDrumRow's bottom "Add reminder" row. */
.gyst-reminder-drum-add {
  display: flex;
  align-items: center;
  gap: var(--gyst-row-gap);
  width: 100%;
  min-height: 44px;
  padding: 10px var(--gyst-row-px);
  background: transparent;
  border: 0;
  border-top: 0.5px solid var(--gyst-row-divider);
  color: var(--accent);
  font-family: inherit;
  font-size: var(--fs14, 14px);
  font-weight: 600;
  cursor: pointer;
  text-align: left;
}
.gyst-reminder-drum-add:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); }
/* Nudge fire-time row — "at 9:00 AM" beneath the drum; tap expands the
   GystUI.timePicker in a .gyst-menu-time-picker-mount sibling. Quiet
   (text color, not accent) so the accent Add row stays the confirm. */
.gyst-reminder-drum-time {
  display: block;
  width: 100%;
  min-height: 38px;
  padding: 8px var(--gyst-row-px);
  background: transparent;
  border: 0;
  border-top: 0.5px solid var(--gyst-row-divider);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs14, 14px);
  cursor: pointer;
  text-align: left;
}
.gyst-reminder-drum-time:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); }
.gyst-reminder-drum-add-icon {
  flex: 0 0 auto;
  width: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--accent);
}
.gyst-reminder-drum-add-icon i[data-lucide],
.gyst-reminder-drum-add-icon svg {
  width: 16px;
  height: 16px;
}

/* ──────────────────────────────────────────────────────────
   LOCATION REMINDER SHEET (gyst-location-sheet)
   iOS parity 4.3: full GystUI sub-sheet matching LocationReminderSheet
   in ReminderPicker.swift (lines 545-849). Mobile: slides up from
   bottom. Desktop: centered modal. Body sections rendered via pure
   DOM by openLocationReminderSheet() in app.js — TRIGGER 2-segment
   toggle, PLACE panel (Location row + inline saved-places list +
   "Use current location"), DETAILS panel (Radius row + ± stepper),
   permission footnote, sticky Cancel + Add footer.
   ────────────────────────────────────────────────────────── */
.gyst-location-sheet-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  display: none;
  opacity: 0;
  transition: opacity 0.18s ease;
  z-index: 2050;
  align-items: flex-end;          /* mobile default — bottom-anchored */
  justify-content: center;
}
.gyst-location-sheet-overlay.is-open {
  display: flex;
  opacity: 1;
}
.gyst-location-sheet {
  width: 100%;
  max-width: min(520px, var(--content-w));
  max-height: 90vh;
  background: var(--bg-1);
  border-top-left-radius: 24px;
  border-top-right-radius: 24px;
  display: flex;
  flex-direction: column;
  transform: translateY(100%);
  transition: transform 0.22s cubic-bezier(0.2, 0.7, 0.2, 1);
  box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.18);
  font-family: var(--gyst-font-stack);
}
.gyst-location-sheet-overlay.is-open .gyst-location-sheet {
  transform: translateY(0);
}
.gyst-location-sheet-grabber {
  width: 36px;
  height: 4px;
  border-radius: 2px;
  background: var(--border);
  margin: 8px auto 0;
  flex-shrink: 0;
}
.gyst-location-sheet-header {
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  padding: 14px 56px 10px;
  flex-shrink: 0;
}
.gyst-location-sheet-title {
  font-size: var(--fs13, 13px);
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--text);
  text-align: center;
  flex: 1;
}
.gyst-location-sheet-close {
  position: absolute;
  right: 16px;
  top: 50%;
  transform: translateY(-50%);
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent-secondary, var(--accent)) 18%, transparent);
  border: 0;
  color: var(--accent-secondary, var(--accent));
  font-size: var(--fs14, 14px);
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--gyst-font-stack);
}
.gyst-location-sheet-close:hover {
  background: color-mix(in srgb, var(--accent-secondary, var(--accent)) 28%, transparent);
}
.gyst-location-sheet-body {
  overflow-y: auto;
  padding: 8px 16px 16px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.gyst-location-section-label {
  margin: 6px 0 6px 4px;
  letter-spacing: 1px;
}
.gyst-location-section-label:first-child {
  margin-top: 0;
}

/* TRIGGER — 2-segment toggle. iOS uses GystUI.SegmentedControl with
   accent fill on the active segment; web mirrors that exactly. */
.gyst-location-trigger-toggle {
  display: flex;
  background: var(--bg-2);
  border: 0.5px solid color-mix(in srgb, var(--border) 60%, transparent);
  border-radius: 14px;
  padding: 4px;
  gap: 4px;
}
.gyst-location-trigger-btn {
  flex: 1;
  height: 44px;
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font-size: var(--fs15, 15px);
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  border-radius: 10px;
  transition: background 0.15s, color 0.15s;
}
.gyst-location-trigger-btn:hover:not(.is-active) {
  color: var(--text);
}
.gyst-location-trigger-btn.is-active {
  background: var(--accent-active, var(--accent));
  color: var(--bg);
}

/* PLACE / DETAILS panels — reuse the .gyst-panel chrome (bg2 fill,
   border, 12px radius, row dividers between siblings). */
.gyst-location-place-panel,
.gyst-location-details-panel {
  display: flex;
  flex-direction: column;
}

/* Location row — primary entry point. Trailing value mirrors
   iOS LocationField placeholder ("Search for an address") or the
   selected place label. */
.gyst-location-row {
  cursor: pointer;
  background: transparent;
  border: 0;
  width: 100%;
  text-align: left;
  font-family: inherit;
  color: inherit;
}
.gyst-location-row:hover { background: color-mix(in srgb, var(--text) 4%, transparent); }
.gyst-location-row-icon,
.gyst-location-current-icon,
.gyst-location-radius-icon {
  /* Wider icon slot — aligns the column with the saved-place rows
     (28px) below it. iOS uses GystUI.Tokens.rowIconFrameWidth = 28. */
  flex: 0 0 auto;
  width: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.gyst-location-row-icon {
  color: var(--accent-secondary, var(--accent));
}
.gyst-location-row-icon i[data-lucide],
.gyst-location-row-icon svg {
  width: 16px;
  height: 16px;
}
.gyst-location-row-label {
  flex: 0 0 auto;
  font-size: var(--fs15, 15px);
  font-weight: 500;
}
.gyst-location-row-value {
  flex: 1 1 auto;
  text-align: right;
  color: var(--text-dim);
  font-size: var(--fs14, 14px);
}
.gyst-location-row-value.is-placeholder {
  color: var(--text-dim);
}

/* Inline saved-places list — toggled visible by tapping the
   Location row. Each row uses a leading icon column (home /
   briefcase / map-pin) + a stacked label + multi-line address. */
.gyst-location-saved-list {
  display: flex;
  flex-direction: column;
}
.gyst-location-saved-place {
  display: flex;
  align-items: flex-start;
  gap: var(--gyst-row-gap);
  padding: 10px var(--gyst-row-px);
  background: transparent;
  border: 0;
  border-top: 0.5px solid var(--gyst-row-divider);
  cursor: pointer;
  width: 100%;
  text-align: left;
  font-family: inherit;
  color: inherit;
}
.gyst-location-saved-place:hover {
  background: color-mix(in srgb, var(--text) 4%, transparent);
}
.gyst-location-saved-place-icon {
  flex: 0 0 auto;
  width: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--accent-secondary, var(--accent));
  margin-top: 2px;
}
.gyst-location-saved-place-icon i[data-lucide],
.gyst-location-saved-place-icon svg {
  width: 16px;
  height: 16px;
}
.gyst-location-saved-place-text {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.gyst-location-saved-place-label {
  font-size: var(--fs15, 15px);
  font-weight: 600;
  color: var(--text);
}
.gyst-location-saved-place-address {
  font-size: var(--fs13, 13px);
  color: var(--text-dim);
  line-height: 1.35;
  word-break: break-word;
}

/* Status slot — surfaces "Looking up …" / "Couldn't find …" /
   resolved label under the Location row, divided from it by the
   panel's own row-divider rule. */
.gyst-location-status-slot {
  padding: 8px var(--gyst-row-px) 10px calc(var(--gyst-row-px) + 28px + var(--gyst-row-gap));
  font-size: var(--fs12, 12px);
  color: var(--text-dim);
  border-top: 0.5px solid var(--gyst-row-divider);
}
.gyst-location-status-label.is-error { color: var(--danger); }
.gyst-location-status-label.is-ok    { color: var(--accent-secondary, var(--accent)); }

/* "Use current location" row — sibling under the Location row. */
.gyst-location-current-row {
  cursor: pointer;
  background: transparent;
  border: 0;
  border-top: 0.5px solid var(--gyst-row-divider);
  width: 100%;
  text-align: left;
  font-family: inherit;
  color: inherit;
}
.gyst-location-current-row:hover {
  background: color-mix(in srgb, var(--text) 4%, transparent);
}
.gyst-location-current-icon {
  color: var(--accent-secondary, var(--accent));
}
.gyst-location-current-icon i[data-lucide],
.gyst-location-current-icon svg {
  width: 18px;
  height: 18px;
}

/* Radius row — value + ± stepper at the trailing edge. */
.gyst-location-radius-row {
  cursor: default;
}
.gyst-location-radius-icon {
  color: var(--text-dim);
}
.gyst-location-radius-icon i[data-lucide],
.gyst-location-radius-icon svg {
  width: 18px;
  height: 18px;
}
.gyst-location-radius-value {
  font-variant-numeric: tabular-nums;
  font-size: var(--fs15, 15px);
  color: var(--text);
  margin-right: 10px;
}
.gyst-location-radius-stepper {
  display: inline-flex;
  align-items: stretch;
  background: var(--bg-2);
  border: 0.5px solid color-mix(in srgb, var(--border) 60%, transparent);
  border-radius: 10px;
  overflow: hidden;
}
.gyst-location-radius-stepper-btn {
  flex: 0 0 auto;
  min-width: 40px;
  height: 32px;
  border: 0;
  background: transparent;
  color: var(--text);
  font-size: var(--fs18, 18px);
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  padding: 0 12px;
  line-height: 1;
}
.gyst-location-radius-stepper-btn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--text) 6%, transparent);
}
.gyst-location-radius-stepper-btn:disabled {
  color: var(--text-dimmer);
  cursor: not-allowed;
}
.gyst-location-radius-stepper-divider {
  width: 0.5px;
  background: color-mix(in srgb, var(--border) 60%, transparent);
  align-self: stretch;
}

/* Footnote under the Details panel — iOS spec text. */
.gyst-location-footnote {
  font-size: var(--fs12, 12px);
  color: var(--text-dim);
  padding: 0 6px;
  margin-top: -4px;
}

/* Footer — Cancel (secondary fill) + Add (primary accent fill,
   disabled until a place is captured). Cancel is fixed-width;
   Add fills the remaining space. */
.gyst-location-sheet-footer {
  display: flex;
  gap: 12px;
  padding: 12px 16px calc(14px + env(safe-area-inset-bottom));
  border-top: 0.5px solid color-mix(in srgb, var(--border) 30%, transparent);
  background: var(--bg-1);
  flex-shrink: 0;
}
.gyst-location-sheet-cancel,
.gyst-location-sheet-add {
  height: 56px;
  border-radius: 14px;
  font-size: var(--fs17, 17px);
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  border: 0;
  transition: background 0.15s, color 0.15s, opacity 0.15s;
}
.gyst-location-sheet-cancel {
  flex: 0 0 30%;
  background: var(--bg-2);
  color: var(--text);
  border: 0.5px solid color-mix(in srgb, var(--border) 50%, transparent);
}
.gyst-location-sheet-cancel:hover {
  background: var(--bg-3);
}
.gyst-location-sheet-add {
  flex: 1 1 auto;
  background: var(--accent-active, var(--accent));
  color: var(--bg);
}
.gyst-location-sheet-add:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent-active, var(--accent)) 92%, white);
}
.gyst-location-sheet-add:disabled {
  /* Match .gyst-btn-primary:disabled — readable, not ghosted (audit 1.2). */
  background: var(--bg-3);
  color: var(--text-dimmer);
  cursor: not-allowed;
}

@media (min-width: 769px) {
  .gyst-location-sheet-overlay {
    align-items: center;             /* desktop — centered modal */
  }
  .gyst-location-sheet {
    border-radius: 24px;
    max-height: min(90vh, 720px);
    transform: scale(0.92);
    opacity: 0;
    transition: transform 0.18s ease, opacity 0.18s ease;
  }
  .gyst-location-sheet-overlay.is-open .gyst-location-sheet {
    transform: scale(1);
    opacity: 1;
  }
  .gyst-location-sheet-grabber {
    display: none;
  }
}

/* Cancel-expansion textarea — note field above the confirm rows. */
.gyst-cancel-note-wrap {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px var(--gyst-row-px);
}
.gyst-cancel-note-label {
  font-size: var(--fs11, 11px);
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-dimmer);
}
.gyst-cancel-note-input {
  width: 100%;
  min-height: 60px;
  padding: 8px 10px;
  background: color-mix(in srgb, var(--bg-3) 50%, transparent);
  border: 1px solid color-mix(in srgb, var(--border) 40%, transparent);
  border-radius: 8px;
  font-family: inherit;
  font-size: var(--fs14, 14px);
  color: var(--text);
  resize: vertical;
}
.gyst-cancel-note-input:focus {
  outline: none;
  border-color: var(--accent);
}
.gyst-tile-expansion-card > .gyst-cancel-note-wrap + .gyst-confirm-row {
  border-top: 0.5px solid var(--gyst-row-divider);
}

/* ───────── INLINE DATE PICKER (matches iOS InlineDatePickerRow) ───────── */
.gyst-inline-date-picker {
  background: var(--bg-2);
  padding: 12px var(--gyst-row-px);
}
.gyst-inline-date-picker-quick {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}
.gyst-inline-date-picker-quick-chip {
  flex: 1 1 auto;
  min-width: 0;
  padding: 6px 10px;
  background: var(--bg-3);
  border: 0.5px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-size: var(--fs12, 12px);
  font-weight: 500;
  cursor: pointer;
  text-align: center;
  white-space: nowrap;
}
.gyst-inline-date-picker-quick-chip:hover { background: color-mix(in srgb, var(--accent) 12%, var(--bg-3)); }
.gyst-inline-date-picker-quick-chip.is-selected {
  background: var(--accent-active, var(--accent));
  border-color: var(--accent-active, var(--accent));
  color: var(--bg-1);
}
.gyst-inline-date-picker-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
}
.gyst-inline-date-picker-nav {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 4px 0 8px;
}
.gyst-inline-date-picker-nav-label {
  font-size: var(--fs13, 13px);
  font-weight: 600;
  color: var(--text);
}
.gyst-inline-date-picker-nav-btn {
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--accent);
  cursor: pointer;
  border-radius: 6px;
}
.gyst-inline-date-picker-nav-btn:hover { background: var(--bg-3); }
.gyst-inline-date-picker-dow {
  font-size: var(--fs10, 10px);
  font-weight: 600;
  letter-spacing: 0.5px;
  color: var(--text-dim);
  text-align: center;
  padding: 4px 0;
  text-transform: uppercase;
}
.gyst-inline-date-picker-day {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--text);
  font-size: var(--fs13, 13px);
  cursor: pointer;
  border-radius: 8px;
}
.gyst-inline-date-picker-day:hover { background: var(--bg-3); }
.gyst-inline-date-picker-day.is-other-month { color: var(--text-dimmer); }
.gyst-inline-date-picker-day.is-today { font-weight: 700; color: var(--accent); }
.gyst-inline-date-picker-day.is-selected {
  background: var(--accent-active, var(--accent));
  color: var(--bg-1);
  font-weight: 600;
}

/* ───────── METADATA CHIP (compact inline meta, e.g. duration) ───────── */
.gyst-metadata-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--bg-2);
  border-radius: 6px;
  padding: 3px 8px;
  font-size: var(--fs11, 11px);
  font-weight: 500;
  color: var(--text-dim);
  white-space: nowrap;
}

/* ───────── META STRIP (short facts, hairline-divided) ─────────
   iOS twin: GystUI.MetaStrip in GystComponents.swift.

       Monthly │ due Sep 10

   Two properties, and they are the whole component:

   1. Segments are ATOMIC. `white-space: nowrap` on the segment plus
      `flex-wrap` on the strip means a line break can only land BETWEEN two
      facts — never between "Aug" and "25".

   2. A divider never survives at the edge of a wrapped line. Each segment
      carries its own divider as a pseudo-element, and the strip is pulled one
      full divider-footprint OUT of an `overflow: hidden` wrapper on whichever
      side the lines are packed against. Every flex line starts at that
      negative offset, so the outermost divider on EVERY line — wrapped lines
      included — falls in the clipped region. No :first-child rule can do this,
      because CSS cannot see which item began a line.

   `.is-trailing` is the mirror image, for a strip sitting under a
   right-aligned number: lines pack to the right, the divider becomes a
   ::after, and the clip moves to the right edge.

   The footprint is expressed in `em` so it tracks the density tier along with
   the type; the +1px is the hairline itself, which is not a density choice.
   The segment sets NO flex `gap` on purpose — a gap would be added on top of
   the pseudo-element's margins and push every divider off-centre. */
.gyst-meta-strip {
  overflow: hidden;
  font-size: var(--fs12, 12px);
  line-height: 1.35;
  color: var(--text-dim);
}
.gyst-meta-strip-inner {
  display: flex;
  flex-wrap: wrap;
  row-gap: 2px;
  /* Must stay in lockstep with the pseudo-element margins below. */
  margin-left: calc(-1.2em - 1px);
}
.gyst-meta-strip-seg {
  display: inline-flex;
  align-items: center;
  min-width: 0;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gyst-meta-strip-seg::before {
  content: '';
  flex: none;
  width: 1px;
  height: 0.85em;
  margin: 0 0.6em;
  background: var(--border);
}
.gyst-meta-strip-seg > .gyst-meta-strip-text {
  overflow: hidden;
  text-overflow: ellipsis;
}
.gyst-meta-strip-seg > svg { flex: none; margin-right: 0.34em; }
.gyst-meta-strip-seg.is-alert { color: var(--danger); }

.gyst-meta-strip.is-trailing .gyst-meta-strip-inner {
  justify-content: flex-end;
  margin-left: 0;
  margin-right: calc(-1.2em - 1px);
}
.gyst-meta-strip.is-trailing .gyst-meta-strip-seg::before { content: none; }
.gyst-meta-strip.is-trailing .gyst-meta-strip-seg::after {
  content: '';
  flex: none;
  width: 1px;
  height: 0.85em;
  margin: 0 0.6em;
  background: var(--border);
}

/* ───────── SHEET HEADER NAV BAR (matches iOS Cancel / Title / Save bar) ───────── */
/* Applied via .modal-header.gyst-sheet-header to avoid conflicting with
   the flex-based .gyst-sheet-header used in profile sheets */
.modal-header.gyst-sheet-header {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: 14px var(--gyst-row-px);
  border-bottom: 0.5px solid var(--border);
}
.modal-header.gyst-sheet-header .gyst-sheet-header-title {
  font-size: var(--fs17, 17px);
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text);
  text-align: center;
  margin: 0;
}
.modal-header.gyst-sheet-header .gyst-sheet-header-btn {
  background: transparent;
  border: none;
  color: var(--accent);
  font-size: var(--fs14, 14px);
  font-weight: 500;
  cursor: pointer;
  padding: 4px 0;
  font-family: inherit;
}
.modal-header.gyst-sheet-header .gyst-sheet-header-btn-start { justify-self: start; }
.modal-header.gyst-sheet-header .gyst-sheet-header-btn-end { justify-self: end; }
.modal-header.gyst-sheet-header .gyst-sheet-header-btn-primary { font-weight: 600; }
.modal-header.gyst-sheet-header .gyst-sheet-header-btn-primary:disabled { opacity: 0.4; cursor: default; }

/* ── Shorthand input classes ── */
.gyst-text-input,
.gyst-num-input {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs13, 13px);
  padding: 6px 10px;
  outline: none;
  box-sizing: border-box;
}
.gyst-text-input:focus,
.gyst-num-input:focus { border-color: var(--accent); }

/* ───────── PILL CHIP (quick-pick buttons in location section) ───────── */
.gyst-pill-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  background: var(--bg-3);
  border: 0.5px solid var(--border);
  border-radius: 12px;
  color: var(--text-dim);
  font-size: var(--fs11, 11px);
  font-weight: 500;
  cursor: pointer;
  font-family: inherit;
}
.gyst-pill-chip:hover { background: color-mix(in srgb, var(--accent) 12%, var(--bg-3)); color: var(--text); }
.gyst-pill-chip i[data-lucide] { width: 12px; height: 12px; }

/* ───────── TEXT FIELD ROW (left-aligned input with leading icon) ───────── */
/* Differs from .gyst-field-row (which is a label/value layout): the input
   fills the row from after the icon to the right edge and is left-aligned.
   Used for Title, Location, etc. where the input IS the row content. */
.gyst-text-field-row {
  display: flex;
  align-items: center;
  min-height: var(--gyst-row-min-height, 44px);
  padding: var(--gyst-row-py) var(--gyst-row-px);
  gap: 12px;
}
.gyst-text-field-row > .gyst-row-icon {
  flex: 0 0 auto;
  color: var(--accent);
  font-size: var(--fs16, 16px);
  width: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.gyst-text-field-row > input,
.gyst-text-field-row > input[type="text"],
.gyst-text-field-row > input[type="search"] {
  flex: 1 1 auto;
  min-width: 0;
  background: transparent;
  border: none;
  outline: none;
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs14, 14px);
  text-align: left;
  padding: 0;
}
.gyst-text-field-row > input::placeholder {
  color: var(--text-dim);
}
.gyst-text-field-row > input:focus,
.gyst-text-field-row > input:focus-visible {
  outline: none;
  border: none;
  box-shadow: none;
}

/* Labeled variant — icon + label on the left, right-aligned input.
   Mirrors iOS GystUI.TextFieldRow when its `title` param is set
   (RoutineEditSheet Icon row, etc.). */
.gyst-text-field-row.with-label > input {
  text-align: right;
}
.gyst-text-field-row.with-label > .gyst-row-label {
  flex: 0 0 auto;
  margin-right: 8px;
}

/* ───────── TEXT EDITOR ROW (multi-line input with icon header) ───────── */
/* Two-line layout: leading icon + label across the top, textarea below.
   Used for Notes inside DETAILS panel. */
.gyst-text-editor-row {
  display: flex;
  flex-direction: column;
  padding: var(--gyst-row-py) var(--gyst-row-px);
  gap: 6px;
}
.gyst-text-editor-row-header {
  display: flex;
  align-items: center;
  gap: 12px;
}
.gyst-text-editor-row-header > .gyst-row-icon {
  color: var(--accent);
  font-size: var(--fs16, 16px);
  width: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.gyst-text-editor-row-header > .gyst-row-label {
  flex: 1;
  font-size: var(--fs14, 14px);
  color: var(--text);
}
.gyst-text-editor-row > textarea {
  width: calc(100% - 32px);
  margin-left: 32px;
  background: transparent;
  border: none;
  outline: none;
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs13, 13px);
  line-height: 1.4;
  resize: vertical;
  min-height: 60px;
  padding: 4px 0 0 0;
  text-align: left;
  box-sizing: border-box;
}
.gyst-text-editor-row > textarea:focus {
  outline: none;
}
.gyst-text-editor-row > textarea::placeholder {
  color: var(--text-dim);
}

/* ───────── DANGER ROW (destructive action with subtitle, e.g. Delete Event) ───────── */
/* Self-contained card — no parent .gyst-form-panel needed. Red icon, red title,
   dim subtitle. Tap target is the whole row. */
.gyst-danger-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px var(--gyst-row-px);
  background: color-mix(in srgb, var(--danger) var(--gyst-danger-wash), var(--bg-2));
  border-radius: var(--gyst-card-radius, 12px);
  border: 0.5px solid color-mix(in srgb, var(--danger) 30%, transparent);
  color: var(--danger);
  font-family: inherit;
  font-size: var(--fs14, 14px);
  cursor: pointer;
  width: 100%;
  text-align: left;
}
.gyst-danger-row:hover {
  background: color-mix(in srgb, var(--danger) 14%, var(--bg-2));
}
.gyst-danger-row > .gyst-row-icon {
  flex: 0 0 auto;
  color: var(--danger);
  font-size: var(--fs18, 18px);
  width: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 2px;
}
.gyst-danger-row-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
}
.gyst-danger-row-title {
  font-size: var(--fs14, 14px);
  font-weight: 600;
  color: var(--danger);
  display: block;
}
.gyst-danger-row-subtitle {
  font-size: var(--fs12, 12px);
  color: var(--text-dim);
  display: block;
}

/* ───────── STICKY FOOTER (Cancel + primary action button bar) ───────── */
/* Sits at the bottom of a scrollable modal. Cancel is outline, primary is
   filled accent with optional icon. */
.gyst-sticky-footer {
  position: sticky;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  gap: 10px;
  padding: 12px var(--gyst-row-px);
  padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  background: var(--bg-1);
  border-top: 0.5px solid var(--border);
  z-index: 10;
}
.gyst-sticky-footer-btn-cancel {
  flex: 0 0 auto;
  padding: 10px 18px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 12px;
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs14, 14px);
  font-weight: 500;
  cursor: pointer;
}
.gyst-sticky-footer-btn-cancel:hover {
  background: var(--bg-2);
}
.gyst-sticky-footer-btn-save {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 18px;
  background: var(--accent);
  border: none;
  border-radius: 12px;
  color: var(--bg-1);
  font-family: inherit;
  font-size: var(--fs14, 14px);
  font-weight: 600;
  cursor: pointer;
}
.gyst-sticky-footer-btn-save:hover {
  filter: brightness(1.05);
}
.gyst-sticky-footer-btn-save:disabled {
  /* Match .gyst-btn-primary:disabled — readable, not ghosted (audit 1.2). */
  background: var(--bg-3);
  color: var(--text-dimmer);
  cursor: default;
}
.gyst-sticky-footer-btn-save i[data-lucide] {
  width: 16px;
  height: 16px;
}

/* ───────── HABIT LOG ENTRY ─────────
   Direct analogs of LogEntrySheet.swift's entryControlPanel —
   stepper for count/timer, stamp for binary/negative, plus the
   habit-name header and Apple-Health footnote. Source of truth:
   ios/Gyst/Views/Habits/LogEntrySheet.swift. */

.gyst-log-entry-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 0;
}
.gyst-log-entry-header-icon {
  font-size: var(--fs24, 24px);
  line-height: 1;
}
.gyst-log-entry-header-name {
  font-size: var(--fs22, 22px);
  font-weight: 700;
  color: var(--text);
  flex: 1;
  min-width: 0;
}
.gyst-log-entry-footnote {
  font-size: var(--fs12, 12px);
  color: var(--text-dimmer);
  padding: 0 4px;
}

.gyst-stepper-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 14px 16px 16px;
}
.gyst-stepper-card-prompt {
  font-size: var(--fs13, 13px);
  color: var(--text-dim);
  text-align: center;
}
.gyst-stepper-card-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 24px;
}
.gyst-stepper-card-value {
  font-size: var(--fs48, 48px);
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  min-width: 80px;
  text-align: center;
  line-height: 1;
}
.gyst-stepper-btn {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  cursor: pointer;
  font-family: inherit;
  color: var(--bg-1);
  transition: transform 0.08s ease;
}
.gyst-stepper-btn i[data-lucide],
.gyst-stepper-btn svg {
  width: 18px;
  height: 18px;
}
.gyst-stepper-btn.is-minus {
  background: var(--text-dim);
}
.gyst-stepper-btn.is-plus {
  background: var(--accent-primary);
}
.gyst-stepper-btn:active {
  transform: scale(0.94);
}

/* Stamp card — large centered status icon for binary + negative
   log entries (no numeric input needed; just a "marked complete"
   visual confirmation before the user taps Save). */
.gyst-stamp-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 22px 16px;
}
.gyst-stamp-card-icon {
  font-size: var(--fs56, 56px);
  line-height: 1;
  color: var(--success);
}
.gyst-stamp-card-icon i[data-lucide],
.gyst-stamp-card-icon svg {
  width: 56px;
  height: 56px;
  color: var(--success);
}

/* The log-entry modal builds its own padding via the body wrapper +
   sticky-footer pattern, so strip the modal-content's default
   `padding: 0 24px 20px` (matches the parallel #habitDetailContent
   override). */
#habitLogContent {
  padding: 0;
}
.gyst-log-entry-body {
  display: flex;
  flex-direction: column;
  gap: 22px;
  padding: 6px 20px 20px;
}

/* Habit-edit clean rebuild (web 1.3) builds its own header / body /
   sticky footer in DOM. Drop the modal box's intrinsic padding when
   the new shell is active. The legacy `#habitModalLegacyShell`
   sibling keeps its own padding via the original child elements. */
#habitModalContent {
  padding: 0;
}
.gyst-habit-edit-body {
  display: flex;
  flex-direction: column;
  gap: 22px;
  padding: 6px 20px 20px;
}

/* ───────── HYDRATION CALCULATOR (web parity 1.7) ─────────
   The Hydration Calculator sheet uses the shared `.gyst-habit-edit-body`
   layout + GystUI rows. The only bespoke piece is the result panel — a
   centered accent-tinted oz value with two muted subtitles beneath.
   Source of truth: ios/Gyst/Views/Habits/HydrationCalculatorSheet.swift
   `resultSection`. */
#hydrationCalcContent {
  padding: 0;
}
.gyst-hydration-result {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 16px 14px;
}
.gyst-hydration-result-oz {
  font-size: var(--fs32, 32px);
  font-weight: 800;
  color: var(--accent-secondary, var(--accent));
  letter-spacing: -0.5px;
  line-height: 1.1;
}
.gyst-hydration-result-sub {
  font-size: var(--fs13, 13px);
  color: var(--text-dim);
  text-align: center;
}
.gyst-hydration-result-sub-dim {
  color: var(--text-dimmer);
}
/* Inline number input used by the Weight row — borderless, right-aligned,
   no native spinner buttons. Sized for "150" / "150.5" without truncation. */
.gyst-hydration-weight-input {
  flex: 0 0 auto;
  width: 72px;
  text-align: right;
  background: transparent;
  border: 0;
  outline: 0;
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs15, 15px);
  font-weight: 500;
  padding: 4px 0;
  -moz-appearance: textfield;
}
.gyst-hydration-weight-input::-webkit-outer-spin-button,
.gyst-hydration-weight-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* ───────── HABIT / ROUTINE TEMPLATE PICKER (web parity 3.3) ─────────
   Clone of iOS HabitTemplatePickerSheet. Reuses gyst-detail-sheet-header
   for the centered title (with a left-side Cancel pill via the new
   `.gyst-detail-sheet-header-cancel` variant), gyst-segmented for the
   Habits/Routines tabs, gyst-section-label + gyst-panel + gyst-row-divider
   for the category groups. The bespoke pieces are the template row
   (icon tile + multi-line text stack + trailing accent ⊕ button) and the
   sticky pill-shaped search bar pinned to the modal bottom.
   Source of truth: ios/Gyst/Views/Habits/HabitTemplatePickerSheet.swift. */
#habitModulesContent {
  padding: 0;
  /* Flex layout pins the sticky search bar to the bottom without
     overlapping content. The scroll region is the middle child. */
  display: flex;
  flex-direction: column;
  max-height: min(720px, calc(100vh - 80px));
}
.gyst-template-picker-body {
  flex: 1 1 auto;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 6px 20px 20px;
}
/* Section labels inside the picker body collapse internal top padding
   so the panels sit at the rhythm iOS uses (mirrors .gyst-habit-detail-body). */
.gyst-template-picker-body > div > .gyst-section-label,
.gyst-template-picker-body > .gyst-section-label {
  padding-top: 0;
  padding-left: 4px;
  padding-right: 4px;
}

/* Template row — leading icon tile + multi-line text stack + trailing ⊕.
   iOS pattern: 36×36 rounded tile (8px corners, accent-tinted bg) with
   centered emoji, then VStack { title / description (≤2 lines) / chip row },
   then a 20px filled circle plus button. */
.gyst-template-row {
  display: flex;
  align-items: flex-start;
  gap: var(--gyst-row-gap);
  padding: var(--gyst-row-py) var(--gyst-row-px);
  background: transparent;
  border: 0;
  width: 100%;
  text-align: left;
  font-family: inherit;
  color: inherit;
  cursor: pointer;
  transition: background-color 0.12s ease;
}
.gyst-template-row:hover {
  background: var(--gyst-surface-2);
}
.gyst-template-tile {
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  border-radius: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--accent-secondary, var(--accent)) 12%, transparent);
  font-size: var(--fs18, 18px);
  line-height: 1;
  margin-top: 2px;
}
.gyst-template-tile.is-workout {
  background: color-mix(in srgb, var(--accent-secondary, var(--accent)) 18%, transparent);
}
.gyst-template-row-text {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.gyst-template-row-title {
  font-size: var(--fs15, 15px);
  font-weight: 600;
  color: var(--text);
  line-height: 1.25;
}
.gyst-template-row-desc {
  font-size: var(--fs13, 13px);
  color: var(--text-dim);
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.gyst-template-row-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 2px;
}
.gyst-template-row-meta-text {
  font-size: var(--fs11, 11px);
  font-weight: 500;
  color: var(--text-dimmer);
}
.gyst-template-add-btn {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 0;
  background: var(--accent-secondary, var(--accent));
  color: var(--bg);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-family: inherit;
  margin-top: 4px;
  padding: 0;
  transition: filter 0.12s ease;
}
.gyst-template-add-btn:hover {
  filter: brightness(1.08);
}
.gyst-template-add-btn > i {
  width: 16px;
  height: 16px;
}

/* Type badge — the small "Binary" / "Count" / "Timer" / "Negative" capsule
   shown beside each habit template's metadata. iOS uses
   `.background(.quaternary).clipShape(Capsule())` — we mirror via a soft
   text-tint fill + pill radius. Distinct from gyst-metadata-chip in that
   it's caps and uses a stronger weight (matches iOS `font(.system(size: 9,
   weight: .semibold))`). */
.gyst-template-type-badge {
  display: inline-flex;
  align-items: center;
  font-size: var(--fs9, 9px);
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--text-dim);
  background: color-mix(in srgb, var(--text) 8%, transparent);
  padding: 2px 6px;
  border-radius: 999px;
}

/* No-match panel — shown when the search filter empties the list. iOS
   centers a magnifying glass + "No matching templates" text inside a
   FormGroupPanel. */
.gyst-template-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 24px;
  color: var(--text-dim);
}
.gyst-template-empty-icon {
  color: var(--text-dimmer);
}
.gyst-template-empty-icon > i {
  width: 24px;
  height: 24px;
}
.gyst-template-empty-label {
  font-size: var(--fs13, 13px);
  color: var(--text-dim);
}

/* Sticky search bar — pinned to the modal bottom, pill input with leading
   magnifying glass. iOS uses `.searchable` (native chrome) which sits as
   a translucent bar below the scroll content; on web we render an inline
   bar with --bg-1 fill + top hairline border. */
.gyst-template-search-bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px 14px;
  background: var(--bg-1);
  border-top: 0.5px solid color-mix(in srgb, var(--border) 35%, transparent);
}
.gyst-template-search-pill {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  background: var(--gyst-surface-2);
  border: 0.5px solid color-mix(in srgb, var(--border) 35%, transparent);
  border-radius: 999px;
  color: var(--text-dim);
}
.gyst-template-search-pill > i {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
}
.gyst-template-search-input {
  flex: 1 1 auto;
  min-width: 0;
  background: transparent;
  border: 0;
  outline: 0;
  font-family: inherit;
  font-size: var(--fs14, 14px);
  color: var(--text);
}
.gyst-template-search-input::placeholder {
  color: var(--text-dimmer);
}

/* Header Cancel pill — left-side equivalent of `.gyst-detail-sheet-header-done`.
   iOS uses a Cancel toolbar button in `ToolbarItem(placement: .cancellationAction)`
   styled with `.foregroundStyle(theme.textDim)`. On web we render an inline
   pill that mirrors the Done pill's size + radius but justifies to the start. */
.gyst-detail-sheet-header-cancel {
  justify-self: start;
  padding: 6px 14px;
  border-radius: 999px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  color: var(--text-dim);
  font-family: inherit;
  font-size: var(--fs14, 14px);
  font-weight: 600;
  cursor: pointer;
}
.gyst-detail-sheet-header-cancel:hover {
  background: var(--bg-3);
  color: var(--text);
}

/* ───────── HABIT DETAIL ─────────
   Direct analogs of the iOS HabitDetailSheet primitives:
     • StatCard           → .gyst-stat-card  (icon+label header, big value+unit body)
     • LazyVGrid 2-col    → .gyst-stat-grid  (always 2-col, even on phone)
     • tierHeroSection    → .gyst-tier-hero  (avatar + tier label + name)
     • milestoneProgress  → .gyst-milestone-card
     • toolbar Done       → .gyst-detail-sheet-header
   `--tier-color` is set inline on the hero / milestone elements per
   active habit tier — falls back to --accent when unset so the same
   classes work outside the habit context. Source of truth:
   ios/Gyst/Views/Habits/HabitDetailSheet.swift +
   ios/Gyst/Views/Components/GystComponents.swift StatCard. */

/* The habit detail modal builds its own header + body padding +
   sticky footer in DOM, so the modal-content's default `padding:
   0 24px 20px` would double up against each child's internal padding.
   Strip it (matches the parallel `#modalContent` override for event
   detail). */
#habitDetailContent {
  padding: 0;
}

/* Habit detail body — vertical rhythm between sections + side
   padding that aligns with the sticky-header / footer. iOS uses
   `Tokens.sectionSpacing` (22pt) — we use 22px on web for parity. */
.gyst-habit-detail-body {
  display: flex;
  flex-direction: column;
  gap: 22px;
  padding: 6px 20px 20px;
}
/* Section labels inside the body already have their own 20px top
   padding; collapse the gap so consecutive sections don't double
   up. iOS does the equivalent via section-internal layout — we match
   visually by zeroing the section-label top padding inside the
   habit-detail body. */
.gyst-habit-detail-body > div > .gyst-section-label,
.gyst-habit-detail-body > .gyst-section-label {
  padding-top: 0;
  padding-left: 4px;
  padding-right: 4px;
}

/* Detail sheet sticky header — title centered + Done pill on right.
   The 60px outer columns lock the title's true horizontal center
   (text-align alone shifts the title left when the Done pill is wide). */
.gyst-detail-sheet-header {
  display: grid;
  grid-template-columns: 60px 1fr 60px;
  align-items: center;
  padding: 14px 20px 6px;
  position: sticky;
  top: 0;
  background: var(--bg-1);
  z-index: 5;
}
.gyst-detail-sheet-header-title {
  text-align: center;
  font-size: var(--fs15, 15px);
  font-weight: 600;
  color: var(--text);
}
.gyst-detail-sheet-header-done {
  justify-self: end;
  padding: 6px 14px;
  border-radius: 999px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  color: var(--accent-secondary, var(--accent));
  font-family: inherit;
  font-size: var(--fs14, 14px);
  font-weight: 600;
  cursor: pointer;
}
.gyst-detail-sheet-header-done:hover {
  background: var(--bg-3);
}

/* Tier hero — circular avatar with a tier-tinted ring sitting above
   "Tier N · Name" + habit name. Avatar shows TIER_IMAGES, the habit's
   own icon glyph, or a tier emoji — first match wins. */
.gyst-tier-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 12px 0;
}
.gyst-tier-hero-avatar {
  position: relative;
  width: 88px;
  height: 88px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--tier-color, var(--accent)) 15%, transparent);
  border: 2px solid color-mix(in srgb, var(--tier-color, var(--accent)) 35%, transparent);
  box-sizing: border-box;
}
.gyst-tier-hero-avatar img {
  width: 64px;
  height: 64px;
  object-fit: contain;
}
.gyst-tier-hero-avatar-glyph {
  font-size: var(--fs28, 28px);
  line-height: 1;
}
/* Framed animated hero medallion (JS-driven sprite sheet, tier-medallion.js) —
 * self-framed, ~fills the sheet width; replaces the avatar circle. iOS parity:
 * HabitDetailSheet tierHeroSection. */
.gyst-tier-hero.is-framed { gap: 8px; }
.gyst-tier-hero-medallion {
  background-repeat: no-repeat;
  max-width: 100%;
  margin: 0 auto;
}
.gyst-tier-hero-text {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.gyst-tier-hero-tier {
  font-size: var(--fs16, 16px);
  font-weight: 600;
  color: var(--tier-color, var(--accent));
}
.gyst-tier-hero-name {
  font-size: var(--fs13, 13px);
  color: var(--text-dim);
  text-align: center;
}

/* Stats grid — 2 columns of StatCards.
   NO mobile collapse: iOS uses fixed 2-col `LazyVGrid` regardless of
   viewport width, and the modal is the primary mobile surface. The
   stat cards (icon + label / value + unit) sit comfortably in 2-col
   even at 320px viewports — the modal is `max-width: 480px`, so each
   card lands at ~140-180px, plenty for "Current Streak / 0 days".
   Earlier 480px collapse forced 5 cards to stack vertically and
   pushed the next-milestone / log history / danger zone below the
   fold on iPhone. */
.gyst-stat-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.gyst-stat-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  padding: 14px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-sizing: border-box;
}
.gyst-stat-card-head {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--text-dim);
  font-size: var(--fs11, 11px);
  font-weight: 500;
}
.gyst-stat-card-head i[data-lucide],
.gyst-stat-card-head svg {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
}
.gyst-stat-card-body {
  display: flex;
  align-items: baseline;
  gap: 4px;
}
.gyst-stat-card-value {
  font-size: var(--fs28, 28px);
  font-weight: 700;
  color: var(--text);
  line-height: 1;
}
.gyst-stat-card-unit {
  font-size: var(--fs12, 12px);
  color: var(--text-dimmer);
}

/* Milestone progress card — emoji + label / count, plus an 8px-tall
   progress-bar with a tier-color gradient fill. */
.gyst-milestone-card {
  padding: 14px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.gyst-milestone-card-head {
  display: flex;
  align-items: center;
  gap: 6px;
}
.gyst-milestone-card-emoji {
  font-size: var(--fs16, 16px);
  line-height: 1;
}
.gyst-milestone-card-label {
  font-size: var(--fs14, 14px);
  font-weight: 500;
  color: var(--text);
}
.gyst-milestone-card-count {
  margin-left: auto;
  font-size: var(--fs12, 12px);
  color: var(--text-dim);
}
.gyst-milestone-card-bar {
  position: relative;
  height: 8px;
  border-radius: 4px;
  background: var(--bg-3);
  overflow: hidden;
}
.gyst-milestone-card-fill {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: 4px;
  background: linear-gradient(90deg,
    var(--tier-color, var(--accent)),
    color-mix(in srgb, var(--tier-color, var(--accent)) 70%, transparent));
  transition: width 0.4s ease;
}

/* ───────── Milestone ladder ─────────
   Expanded body of the habit detail's "Next milestone" card: one rung per
   tier with a state dot (earned / current / locked), emoji, label,
   threshold, and a right slot (earned date · count + mini fill · empty).
   iOS parity: GystUI.MilestoneLadder. The card header is the tap target
   (.gyst-milestone-card-head gains a chevron via -toggle). */
.gyst-milestone-card-head.gyst-milestone-toggle { cursor: pointer; }
.gyst-milestone-chevron {
  margin-left: 6px;
  color: var(--text-dim);
  transition: transform 0.25s ease;
  display: inline-flex;
}
.gyst-milestone-chevron i,
.gyst-milestone-chevron svg { width: 12px; height: 12px; }
.gyst-milestone-card.expanded .gyst-milestone-chevron { transform: rotate(180deg); }
.gyst-milestone-ladder {
  display: none;
  margin: 6px -14px -14px;                /* bleed to card edges */
  border-top: 1px solid var(--border);
}
.gyst-milestone-card.expanded .gyst-milestone-ladder { display: block; }
@media (prefers-reduced-motion: no-preference) {
  .gyst-milestone-card.expanded .gyst-milestone-ladder {
    animation: gyst-milestone-ladder-in 0.22s ease;
  }
  @keyframes gyst-milestone-ladder-in {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: none; }
  }
}
.gyst-milestone-rung {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  padding: 9px 14px;
  box-sizing: border-box;
}
.gyst-milestone-rung + .gyst-milestone-rung { border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); }
.gyst-milestone-rung-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  flex: none;
}
.gyst-milestone-rung.earned .gyst-milestone-rung-dot { background: var(--tier-color, var(--accent)); }
.gyst-milestone-rung.current .gyst-milestone-rung-dot {
  width: 10px; height: 10px;
  background: transparent;
  border: 2px solid var(--tier-color, var(--accent));
}
.gyst-milestone-rung.locked .gyst-milestone-rung-dot {
  background: transparent;
  border: 1.5px solid color-mix(in srgb, var(--text-dimmer) 50%, transparent);
}
.gyst-milestone-rung-emoji { font-size: var(--fs16, 16px); line-height: 1; }
.gyst-milestone-rung.locked .gyst-milestone-rung-emoji { opacity: 0.45; }
.gyst-milestone-rung-label { font-size: var(--fs15, 15px); font-weight: 500; color: var(--text); }
.gyst-milestone-rung.locked .gyst-milestone-rung-label { color: var(--text-dim); }
.gyst-milestone-rung-threshold { font-size: var(--fs12, 12px); color: var(--text-dimmer); }
.gyst-milestone-rung-trailing {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs12, 12px);
  color: var(--text-dim);
}
.gyst-milestone-rung-check { color: var(--success); font-weight: 600; }
.gyst-milestone-rung-count { font-weight: 500; color: var(--text); }
.gyst-milestone-rung-minibar {
  position: relative;
  width: 44px; height: 5px;
  border-radius: 999px;
  background: var(--bg-3);
  overflow: hidden;
}
.gyst-milestone-rung-minifill {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: 999px;
  background: var(--tier-color, var(--accent));
}

/* ───────── PEOPLE chip group ─────────
   Mirrors iOS GystUI.ChipGroup with `capsule: false` — rectangular chips
   at chipRadius (8px), bg3 fill when unselected, accent fill + theme-
   background text when selected. No color dot — iOS doesn't render one
   in the People section, just the kid's name.
   Layout inside the panel: VStack(spacing:8) — chips wrap on the first
   row, "No one assigned" sits muted underneath when nothing is selected. */
.gyst-people-row {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-start;
  padding: 12px var(--gyst-row-px);
}
.gyst-people-chip-group {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.gyst-people-chip {
  display: inline-flex;
  align-items: center;
  height: 34px;
  padding: 0 14px;
  background: var(--bg-3);
  border: 1px solid color-mix(in srgb, var(--border) 40%, transparent);
  border-radius: var(--gyst-chip-radius);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs14, 14px);
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.gyst-people-chip:hover {
  border-color: color-mix(in srgb, var(--border) 55%, transparent);
}
.gyst-people-chip.is-selected {
  background: var(--accent-active, var(--accent));
  border-color: transparent;
  /* iOS uses theme.background for the foreground (dark text on accent
     fill) — readable on the warm-orange accent in dark theme. */
  color: var(--bg-1);
}
.gyst-people-chip.is-selected .gyst-people-chip-dot {
  /* Dot is a legacy/optional element. Keep the rule so legacy callers
     that still render a dot don't see it bleed through the filled chip. */
  display: none;
}
.gyst-people-chip-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: 0 0 auto;
}
.gyst-people-chip-name {
  white-space: nowrap;
}
.gyst-people-empty-message {
  font-size: var(--fs13, 13px);
  color: var(--text-dim);
  padding-top: 4px;
}

/* ───────── PEOPLE row-based UI (orphaned — kept for safety) ───────── */
.gyst-people-remove {
  background: transparent;
  border: none;
  color: var(--text-dimmer);
  font-size: var(--fs18, 18px);
  line-height: 1;
  width: 24px;
  height: 24px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 4px;
}
.gyst-people-remove:hover {
  background: var(--bg-3);
  color: var(--text);
}
.gyst-people-available {
  display: flex;
  flex-direction: column;
}
.gyst-people-available-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px var(--gyst-row-px);
  background: transparent;
  border: none;
  border-bottom: 0.5px solid var(--border);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs13, 13px);
  cursor: pointer;
  width: 100%;
}
.gyst-people-available-item:last-child {
  border-bottom: none;
}
.gyst-people-available-item:hover {
  background: var(--bg-3);
}
.gyst-people-available-item.is-selected {
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}

/* When a .gyst-menu-expansion contains a .gyst-menu-options-list, remove the
   expansion's own horizontal padding — option rows carry their own padding. */
.gyst-menu-expansion:has(.gyst-menu-options-list) {
  padding-left: 0;
  padding-right: 0;
  padding-bottom: 0;
}

/* ───────── MENU OPTIONS LIST (iOS MenuRow expand pattern) ───────── */
/* Plain rows inside a .gyst-menu-expansion. Each row: icon + label + checkmark
   on selected. Matches iOS GystUI.MenuRow option rendering. */
.gyst-menu-options-list {
  display: flex;
  flex-direction: column;
}
.gyst-menu-option-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: var(--gyst-row-py) var(--gyst-row-px);
  background: transparent;
  border: none;
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs14, 14px);
  cursor: pointer;
  width: 100%;
  text-align: left;
  position: relative;
}
.gyst-menu-option-row + .gyst-menu-option-row::before {
  content: '';
  position: absolute;
  top: 0;
  left: var(--gyst-row-px);
  right: 0;
  height: 0.5px;
  background: var(--border);
  pointer-events: none;
}
/* Divider between an option row and a following reminder sub-row (drum
   container) or an expansion row inside the same .gyst-menu-options-list.
   Mirrors iOS ReminderPicker's GystUI.RowDivider() between every row. */
.gyst-menu-options-list > .gyst-menu-option-row + .gyst-reminder-subrow,
.gyst-menu-options-list > .gyst-menu-option-row + .gyst-expansion-row,
.gyst-menu-options-list > .gyst-reminder-subrow + .gyst-expansion-row {
  border-top: 0.5px solid var(--border);
}
.gyst-menu-option-row:hover {
  background: color-mix(in srgb, var(--accent) 6%, transparent);
}
.gyst-menu-option-row.is-selected {
  font-weight: 600;
}
.gyst-menu-option-row > .gyst-menu-option-icon {
  width: 20px;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--accent);
  font-size: var(--fs16, 16px);
}
.gyst-menu-option-row > .gyst-menu-option-icon i[data-lucide] {
  width: 16px;
  height: 16px;
}
.gyst-menu-option-row > .gyst-menu-option-label {
  flex: 1;
  min-width: 0;
}
.gyst-menu-option-row > .gyst-menu-option-check {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
}
.gyst-menu-option-row:not(.is-selected) > .gyst-menu-option-check {
  visibility: hidden;
}

/* Effect-preview rows: widen the leading slot so the live fx demo chip
   (built by _buildFxPreviewChip) has room to animate. */
.gyst-menu-option-row.has-fx-preview > .gyst-menu-option-icon {
  width: 52px;
}
.fx-preview-chip {
  position: relative;
  width: 52px;
  height: 22px;
  border-radius: 6px;
  overflow: hidden;
  flex: 0 0 auto;
  background: var(--bg-2);
  box-shadow: inset 0 0 0 1px var(--overlay-10);
}
.fx-preview-none {
  background: var(--bg-2);
  box-shadow: inset 0 0 0 1px var(--border);
}

/* Time picker mount inside a .gyst-menu-expansion — adds vertical padding
   so the drum wheels sit inside the panel. The .gyst-time-picker itself
   handles its own layout (3 cols + center pill). */
.gyst-menu-time-picker-mount {
  padding: 8px var(--gyst-row-px) 12px;
}

/* ───────── GystUI modal body — section spacing ─────────
   Every section label inside an event/task edit modal body gets 24px top
   breathing room and 6px bottom (matches iOS sectionSpacing rhythm). The
   first section's label is reduced so it doesn't push too far below the
   modal header. Same rule applies to both the Event Edit modal and the
   Task Edit modal so they share rhythm. */
#addEventModalBody .gyst-section-label,
#taskModalContent .gyst-section-label {
  padding-top: 24px;
  padding-bottom: 6px;
}
#addEventModalBody > div:first-child > .gyst-section-label,
#taskModalContent > div:first-child > .gyst-section-label {
  padding-top: 4px;
}

/* ───────── Repeats picker — preset + day chips (rounded rectangles) ─────
   Mirrors iOS EventEditSheet exactly:
   - Day chips (S M T W T F S): RoundedRectangle, GystUI.Tokens.chipRadius
     (8px), font 13/700, fill the 7-col grid column.
   - Preset chips (Weekdays / Weekend / Daily): RoundedRectangle, 6px
     corners, font 13/600, fill the 3-col grid column.
   Both are RECTANGULAR — pills/circles read like a different design.
   Selected state in both: filled accent + white text. */
#addEventModalBody .gyst-preset-chip,
#taskModalContent .gyst-preset-chip {
  border: 1px solid var(--gyst-panel-border);
  background: var(--bg-3);
  border-radius: 6px;
  width: 100%;
  height: 34px;
  padding: 0 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: inherit;
  font-size: var(--fs13, 13px);
  font-weight: 600;
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
#addEventModalBody .gyst-preset-chip:hover,
#taskModalContent .gyst-preset-chip:hover {
  border-color: color-mix(in srgb, var(--border) 55%, transparent);
}
#addEventModalBody .gyst-preset-chip.is-selected,
#taskModalContent .gyst-preset-chip.is-selected {
  background: var(--accent-active, var(--accent));
  border-color: var(--accent-active, var(--accent));
  color: var(--accent-active-text, #fff);
}
#addEventModalBody .gyst-day-chip,
#taskModalContent .gyst-day-chip {
  border: 1px solid var(--gyst-panel-border);
  background: var(--bg-3);
  border-radius: var(--gyst-chip-radius);
  width: 100%;
  min-height: 34px; /* was height — tier 2/3 type must not clip */
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: inherit;
  font-size: var(--fs13, 13px);
  font-weight: 700;
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
#addEventModalBody .gyst-day-chip:hover,
#taskModalContent .gyst-day-chip:hover {
  border-color: color-mix(in srgb, var(--border) 55%, transparent);
}
#addEventModalBody .gyst-day-chip.is-selected,
#taskModalContent .gyst-day-chip.is-selected {
  background: var(--accent-active, var(--accent));
  border-color: var(--accent-active, var(--accent));
  color: var(--accent-active-text, #fff);
}

/* ───────── Sticky footer — drop modal-content's bottom padding ─────────
   .modal-content (modal.css:749) ships with `padding: 0 24px 20px`. The
   20px padding-bottom sits INSIDE the scroll container, between the
   sticky footer's natural bottom and the modal's inner edge — and that
   slice is transparent, so the rest of the scroll content is visible
   below the Cancel/Save buttons. Drop the bottom padding on modal-content
   for the modals that own a gyst-sticky-footer; the footer's own padding
   already handles spacing + safe-area inset. */
#addEventModal .modal-content,
#taskModal .modal-content {
  padding-bottom: 0;
}

/* ───────── People section — empty-state hint inside the panel ───────── */
#addEventModalBody .gyst-people-empty,
#taskModalContent .gyst-people-empty {
  font-size: var(--fs13, 13px);
  color: var(--text-dim);
  padding: 4px 0;
}

/* ───────── Task Edit modal — Priority 3-cell row ─────────
   Three rectangular cells inside the Priority panel. Selected cell takes
   on the priority's semantic color (Normal=accent, High=orange, Urgent=
   danger) with white text. Unselected cells use the panel's bg-3 with
   dim text. Matches iOS TaskEditSheet.priorityCell. */
#taskModalContent .gyst-priority-cells {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 8px;
  padding: 12px var(--gyst-row-px);
}
#taskModalContent .gyst-priority-cell {
  height: 34px;
  border-radius: var(--gyst-chip-radius);
  border: 1px solid var(--gyst-panel-border);
  background: var(--bg-3);
  color: var(--text-dim);
  font-family: inherit;
  font-size: var(--fs13, 13px);
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
#taskModalContent .gyst-priority-cell:hover {
  border-color: color-mix(in srgb, var(--border) 55%, transparent);
}
#taskModalContent .gyst-priority-cell.is-selected {
  color: var(--bg-1);
  border-color: transparent;
}
#taskModalContent .gyst-priority-cell[data-priority="normal"].is-selected {
  background: var(--accent-active, var(--accent));
}
#taskModalContent .gyst-priority-cell[data-priority="high"].is-selected {
  background: var(--warning, #f59e0b);
}
#taskModalContent .gyst-priority-cell[data-priority="urgent"].is-selected {
  background: var(--danger, #d92d20);
}

/* ───────── Task Edit modal — muted variant of the danger row ─────────
   The "Disregard" row reuses .gyst-danger-row geometry but tones the
   color down: it's a logged action, not a deletion. */
#taskModalContent .gyst-danger-row-muted,
#taskModalContent .gyst-danger-row-muted .gyst-danger-row-title,
#taskModalContent .gyst-danger-row-muted .gyst-danger-row-subtitle,
#taskModalContent .gyst-danger-row-muted > .gyst-row-icon {
  color: var(--text-dim);
}
#taskModalContent .gyst-danger-row-muted {
  background: transparent;
}

/* ───────── REPEATS chips (rectangular iOS weekday/preset chips) ───────── */
/* Override the legacy pill styling for chips inside the addEventModal REPEATS section.
   Clones iOS: RoundedRectangle(cornerRadius: 6) for preset, 8px for day chips,
   accent active state, 3-col grid for presets, 7-col grid for day chips. */

#addEventModal #recurWeekdayPicker {
  display: flex !important;
  flex-direction: column;
  gap: 10px;
}

#addEventModal #recurWeekdayPicker > div:first-child {
  /* Preset row — Weekdays / Weekend / Daily (Clear button hidden to match iOS) */
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin: 0 !important;
}

#addEventModal #recurWeekdayPicker .recurrence-chip[data-recur-preset="clear"] {
  display: none;
}

#addEventModal #recurWeekdayPicker > div:last-child {
  /* Day chip row — S M T W T F S */
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}

#addEventModal #recurWeekdayPicker .recurrence-chip {
  width: 100%;
  height: auto;
  padding: 10px 8px;
  background: var(--bg-3);
  border: 1px solid color-mix(in srgb, var(--border) 40%, transparent);
  border-radius: 6px;
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs13, 13px);
  font-weight: 600;
  cursor: pointer;
  text-align: center;
  box-sizing: border-box;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}

#addEventModal #recurWeekdayPicker .recurrence-chip:hover {
  border-color: color-mix(in srgb, var(--border) 55%, transparent);
}

#addEventModal #recurWeekdayPicker .recurrence-chip.active {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border-color: var(--accent);
  color: var(--accent);
}

#addEventModal #recurWeekdayPicker .recur-day-chip {
  width: 100%;
  min-height: 34px; /* was height — tier 2/3 type must not clip */
  padding: 0;
  background: var(--bg-3);
  border: 1px solid color-mix(in srgb, var(--border) 40%, transparent);
  border-radius: var(--gyst-chip-radius);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs13, 13px);
  font-weight: 700;
  cursor: pointer;
  text-align: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}

#addEventModal #recurWeekdayPicker .recur-day-chip:hover {
  border-color: color-mix(in srgb, var(--border) 55%, transparent);
}

#addEventModal #recurWeekdayPicker .recur-day-chip.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg-1);
}

/* ============================================================
   NEON ECLIPSE — universal CTA role split
   Per /tmp/neon-eclipse-visual-spec.md (Pass 4).
   Magenta = primary action / hot attention.
   Cyan    = secondary CTA / Add / Links.
   Violet  = active selection state.
   Lime    = electric hover spark.
   Synth pink/orange = focus / brand heat (per-surface).
   ============================================================ */

[data-theme="neonEclipse"] .gyst-btn-primary {
  background: var(--accent-primary);
  color: var(--white);
  border-color: var(--accent-primary);
  box-shadow: 0 0 12px var(--fx-glow-primary);
  text-shadow: 0 0 6px rgba(255,0,255,0.40);
}
[data-theme="neonEclipse"] .gyst-btn-primary:hover:not(:disabled) {
  box-shadow: 0 0 18px rgba(255,0,255,0.50), 0 0 32px rgba(255,0,255,0.18);
}

[data-theme="neonEclipse"] .gyst-btn-secondary {
  border-color: var(--chrome-border);
  color: var(--accent-secondary);
  background: var(--surface-raised);
}
[data-theme="neonEclipse"] .gyst-btn-secondary:hover {
  background: var(--surface-hover);
  color: var(--accent-secondary);
  border-color: var(--accent-secondary);
  box-shadow: 0 0 10px var(--fx-glow-secondary);
}

[data-theme="neonEclipse"] .gyst-btn-destructive {
  background: var(--danger);
  color: var(--white);
  border-color: var(--danger);
  box-shadow: 0 0 10px rgba(255,42,109,0.35);
}
[data-theme="neonEclipse"] .gyst-btn-destructive:hover {
  box-shadow: 0 0 16px rgba(255,42,109,0.55);
}

/* Selected / active states — segmented controls, pills, toggles,
   active nav rows. Anywhere "selected" would otherwise paint with
   --accent, paint with --accent-active (violet) so primary CTAs
   (magenta) and selection (violet) read as different roles. */
[data-theme="neonEclipse"] .gyst-segmented .gyst-segmented-option.is-selected,
[data-theme="neonEclipse"] .gyst-pill.is-active,
[data-theme="neonEclipse"] .gyst-nav-row.is-active {
  background: var(--accent-active);
  border-color: var(--accent-active);
  color: var(--white);
  box-shadow: 0 0 10px var(--fx-glow-active);
}

/* Focus ring */
[data-theme="neonEclipse"] *:focus-visible {
  outline-color: var(--accent-focus);
  box-shadow: 0 0 0 3px rgba(255,0,204,0.25);
}

/* Add buttons / "+" affordances — cyan secondary CTA. */
[data-theme="neonEclipse"] .gyst-add-btn,
[data-theme="neonEclipse"] .task-add-btn,
[data-theme="neonEclipse"] .chip-add-btn {
  color: var(--accent-secondary);
  border-color: rgba(0,255,255,0.35);
}
[data-theme="neonEclipse"] .gyst-add-btn:hover,
[data-theme="neonEclipse"] .task-add-btn:hover,
[data-theme="neonEclipse"] .chip-add-btn:hover {
  border-color: var(--accent-secondary);
  background: var(--surface-hover);
  box-shadow: 0 0 10px rgba(0,255,255,0.30);
}

/* Generic input surfaces — pull the brighter chrome edge */
[data-theme="neonEclipse"] input[type="text"],
[data-theme="neonEclipse"] input[type="email"],
[data-theme="neonEclipse"] input[type="number"],
[data-theme="neonEclipse"] input[type="search"],
[data-theme="neonEclipse"] input[type="password"],
[data-theme="neonEclipse"] textarea,
[data-theme="neonEclipse"] select {
  border-color: var(--input-border);
}
[data-theme="neonEclipse"] input:focus,
[data-theme="neonEclipse"] textarea:focus,
[data-theme="neonEclipse"] select:focus {
  border-color: var(--accent-focus);
  box-shadow: 0 0 0 2px rgba(255,0,204,0.20);
}

/* ──────────────────────────────────────────────────────────
   TOP-SLIDE SHEET (gyst-top-sheet)

   Slides in from above, anchored below the top nav. iOS parity
   for AddChooserSheet (RootTabView.swift:344). Web-only chrome:
   the "+" entry point lives in the top nav, so the sheet should
   appear adjacent to it instead of forcing a thumb-trip to the
   bottom edge. The grabber sits at the BOTTOM edge of the sheet,
   closer to the user's swipe direction for dismissal (visual
   only — drag gestures aren't wired up).
   ────────────────────────────────────────────────────────── */
.gyst-top-sheet-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.35);
  display: none;
  z-index: 1000;
  opacity: 0;
  transition: opacity 0.18s ease;
}
.gyst-top-sheet-overlay.is-open {
  display: block;
  opacity: 1;
}
.gyst-top-sheet {
  position: absolute;
  top: var(--top-nav-height, 56px);
  /* Anchor the menu to the top-right, directly under the "+" button that
     spawns it, instead of floating centered — so it reads as that button's
     dropdown rather than a detached card. It grows out of the button's
     corner (transform-origin below). */
  right: 12px;
  left: auto;
  transform-origin: top right;
  width: min(420px, calc(100% - 24px));
  max-height: calc(100vh - var(--top-nav-height, 56px) - 24px);
  background: var(--bg-1);
  border: 1px solid var(--gyst-panel-border);
  border-top: none;
  border-radius: 0 0 16px 16px;
  box-shadow: 0 16px 32px rgba(0, 0, 0, 0.18);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Closed: tucked up into the button, shrunk and faded. The springy
     back-out easing gives the drop a little overshoot so it feels alive. */
  opacity: 0;
  transform: translateY(-14px) scale(0.94);
  transition: transform 0.34s cubic-bezier(0.34, 1.42, 0.52, 1), opacity 0.2s ease;
  font-family: var(--gyst-font-stack);
}
.gyst-top-sheet-overlay.is-open .gyst-top-sheet {
  opacity: 1;
  transform: translateY(0) scale(1);
}
/* The five rows cascade in one after another as the sheet settles — the
   "fun" signature. Each is nudged up + faded, then springs into place on a
   staggered delay. */
@keyframes gystAddRowIn {
  from { opacity: 0; transform: translateY(-9px); }
  to   { opacity: 1; transform: translateY(0); }
}
.gyst-top-sheet-overlay.is-open .gyst-add-chooser-row {
  animation: gystAddRowIn 0.3s cubic-bezier(0.34, 1.4, 0.5, 1) both;
}
.gyst-top-sheet-overlay.is-open .gyst-add-chooser-row:nth-child(1) { animation-delay: 0.07s; }
.gyst-top-sheet-overlay.is-open .gyst-add-chooser-row:nth-child(2) { animation-delay: 0.11s; }
.gyst-top-sheet-overlay.is-open .gyst-add-chooser-row:nth-child(3) { animation-delay: 0.15s; }
.gyst-top-sheet-overlay.is-open .gyst-add-chooser-row:nth-child(4) { animation-delay: 0.19s; }
.gyst-top-sheet-overlay.is-open .gyst-add-chooser-row:nth-child(5) { animation-delay: 0.23s; }
.gyst-top-sheet-header {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 14px 16px 10px;
  position: relative;
}
/* Override .gyst-section-label's default vertical padding so the
   centered label sits tidily in the header band (header supplies
   its own padding). */
.gyst-top-sheet-header .gyst-section-label {
  padding: 0;
  text-align: center;
}
/* Sheet title — title case, no caps/tracking (audit 2.1). Sheet titles
   aren't section eyebrows; paired with iOS GystUI top-sheet header. */
.gyst-top-sheet-title {
  font-size: var(--fs15, 15px);
  font-weight: 600;
  color: var(--text-dim);
  text-align: center;
  /* Clear the absolutely-positioned Cancel on both sides (so the centered
     title never runs under it at scaled tiers — it wraps instead). 176px =
     2 × Cancel's widest tier-2 footprint. No-op at tier 0: titles fit. */
  max-width: calc(100% - 176px);
  overflow-wrap: break-word;
}
.gyst-top-sheet-cancel {
  position: absolute;
  right: 16px;
  top: 50%;
  transform: translateY(-50%);
  background: transparent;
  border: none;
  color: var(--text-dim);
  font-size: var(--fs15, 15px);
  font-weight: 500;
  cursor: pointer;
  padding: 6px 8px;
  font-family: inherit;
}
.gyst-top-sheet-cancel:hover {
  color: var(--text);
}
.gyst-top-sheet-body {
  padding: 4px 16px 14px;
  overflow-y: auto;
  flex: 1 1 auto;
}
.gyst-top-sheet-grabber {
  width: 36px;
  height: 4px;
  border-radius: 2px;
  background: var(--border);
  margin: 4px auto 10px;
  flex-shrink: 0;
}

/* Add Chooser row variant — tappable button rendered as a row inside
   .gyst-panel. Hover dims the background so cursor users see the row
   highlight; the auto-divider rule above the panel handles inter-row
   hairlines via the .gyst-panel > * + * sibling selectors. */
.gyst-add-chooser-row {
  transition: background-color 0.12s ease;
}
.gyst-add-chooser-row:hover {
  background: var(--gyst-surface-2);
}
.gyst-add-chooser-row:active {
  background: var(--gyst-surface);
}
/* Auto-divider between consecutive add-chooser rows inside the panel
   (the generic .gyst-row + .gyst-row::before rule already fires here
   since each row carries the .gyst-row class; this block exists as
   documentation — no override needed). */

@media (max-width: 768px) {
  .gyst-top-sheet {
    left: 0;
    right: 0;
    width: 100%;
    border-radius: 0 0 20px 20px;
    /* Still grows from the top-right corner (where the "+" lives) even at
       full width, so the connection to the button reads on mobile too. */
    transform-origin: top right;
  }
}

/* Honor reduced-motion: no spring, no scale, no cascade — just a quick fade. */
@media (prefers-reduced-motion: reduce) {
  .gyst-top-sheet {
    transform: none;
    transition: opacity 0.15s ease;
  }
  .gyst-top-sheet-overlay.is-open .gyst-top-sheet {
    transform: none;
  }
  .gyst-top-sheet-overlay.is-open .gyst-add-chooser-row {
    animation: none;
  }
}

/* ──────────────────────────────────────────────────────────
   ConfirmationSheet — modal sheet with title + message +
   stacked action buttons. Mobile: bottom-anchored, slides up.
   Desktop: centered modal with rounded corners. Direct analog
   of iOS GystUI.ConfirmationSheet (see GystComponents.swift).
   ────────────────────────────────────────────────────────── */
.gyst-confirm-sheet-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  display: none;
  opacity: 0;
  transition: opacity 0.18s ease;
  z-index: 2100;
  align-items: flex-end;          /* mobile default — bottom-anchored */
  justify-content: center;
}
.gyst-confirm-sheet-overlay.is-open {
  display: flex;
  opacity: 1;
}
.gyst-confirm-sheet {
  width: 100%;
  max-width: min(480px, var(--content-w));
  background: var(--bg-1);
  border-top-left-radius: 24px;
  border-top-right-radius: 24px;
  padding: 22px 18px calc(22px + env(safe-area-inset-bottom));
  transform: translateY(100%);
  transition: transform 0.22s cubic-bezier(0.2, 0.7, 0.2, 1);
  box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.18);
}
.gyst-confirm-sheet-overlay.is-open .gyst-confirm-sheet {
  transform: translateY(0);
}
.gyst-confirm-sheet-grabber {
  width: 36px;
  height: 4px;
  border-radius: 2px;
  background: var(--border);
  margin: 0 auto 16px;
}
.gyst-confirm-sheet-title {
  font-size: var(--fs22, 22px);
  font-weight: 700;
  color: var(--text);
  margin-bottom: 8px;
  font-family: var(--gyst-font-stack);
}
.gyst-confirm-sheet-message {
  font-size: var(--fs15, 15px);
  color: var(--text-dim);
  margin-bottom: 18px;
  line-height: 1.4;
  font-family: var(--gyst-font-stack);
}
.gyst-confirm-sheet-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.gyst-confirm-sheet-btn {
  width: 100%;
  padding: 18px 16px;
  border-radius: 14px;
  font-size: var(--fs17, 17px);
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background 0.12s, transform 0.08s, color 0.12s;
  font-family: var(--gyst-font-stack);
  -webkit-appearance: none;
  appearance: none;
}
.gyst-confirm-sheet-btn:active {
  transform: scale(0.99);
}
.gyst-confirm-sheet-btn-primary {
  background: var(--accent);
  color: var(--bg);
  border-color: var(--accent);
}
.gyst-confirm-sheet-btn-primary:hover {
  background: color-mix(in srgb, var(--accent) 92%, white);
}
.gyst-confirm-sheet-btn-secondary {
  background: transparent;
  color: var(--text);
  border-color: var(--border);
}
.gyst-confirm-sheet-btn-secondary:hover {
  background: var(--bg-2);
}
.gyst-confirm-sheet-btn-cancel {
  background: transparent;
  color: var(--text-dim);
  border: none;
  font-weight: 500;
  padding: 14px 16px;
  margin-top: 4px;
}
.gyst-confirm-sheet-btn-cancel:hover {
  color: var(--text);
}
.gyst-confirm-sheet-btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.gyst-confirm-sheet-btn-icon i[data-lucide],
.gyst-confirm-sheet-btn-icon svg {
  width: 18px;
  height: 18px;
}

@media (min-width: 769px) {
  .gyst-confirm-sheet-overlay {
    align-items: center;           /* desktop — centered modal */
  }
  .gyst-confirm-sheet {
    border-radius: 24px;
    padding: 28px 24px 24px;
    transform: scale(0.92);
    opacity: 0;
    transition: transform 0.18s ease, opacity 0.18s ease;
  }
  .gyst-confirm-sheet-overlay.is-open .gyst-confirm-sheet {
    transform: scale(1);
    opacity: 1;
  }
  .gyst-confirm-sheet-grabber {
    display: none;
  }
}

/* ──────────────────────────────────────────────────────────
   FILTER SHEET (gyst-filter-sheet)
   iOS parity: CalendarFilterView.swift — full-sheet modal
   with mode toggle / category chips / health row /
   friends rows / clear-all. Mobile: slides up from the
   bottom. Desktop: centered modal.
   ────────────────────────────────────────────────────────── */
.gyst-filter-sheet-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  display: none;
  opacity: 0;
  transition: opacity 0.18s ease;
  z-index: 1000;
  align-items: flex-end;          /* mobile default — bottom-anchored */
  justify-content: center;
}
.gyst-filter-sheet-overlay.is-open {
  display: flex;
  opacity: 1;
}
.gyst-filter-sheet {
  width: 100%;
  max-width: min(520px, var(--content-w));
  max-height: 85vh;
  background: var(--bg-1);
  border-top-left-radius: 24px;
  border-top-right-radius: 24px;
  display: flex;
  flex-direction: column;
  transform: translateY(100%);
  transition: transform 0.22s cubic-bezier(0.2, 0.7, 0.2, 1);
  box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.18);
  font-family: var(--gyst-font-stack);
}
.gyst-filter-sheet-overlay.is-open .gyst-filter-sheet {
  transform: translateY(0);
}
.gyst-filter-sheet-grabber {
  width: 36px;
  height: 4px;
  border-radius: 2px;
  background: var(--border);
  margin: 8px auto 0;
  flex-shrink: 0;
}
/* Sticky two-row header. Row 1 holds Clear All / title / ✕ on the
   edges + center; Row 2 holds the primary Filter + Search pills that
   moved up from the bottom action bar. Anchored at the top of the
   sheet so the actions stay visible while the user scrolls criteria. */
.gyst-filter-sheet-header {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 16px 12px;
  flex-shrink: 0;
  background: var(--bg-2);
  border-bottom: 0.5px solid color-mix(in srgb, var(--border) 30%, transparent);
}
.gyst-filter-sheet-header-row1 {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 12px;
  min-height: 30px;
}
.gyst-filter-sheet-title {
  font-size: var(--fs16, 16px);
  font-weight: 600;
  color: var(--text);
  text-align: center;
  font-family: var(--gyst-font-stack);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gyst-filter-sheet-clear {
  justify-self: start;
  background: transparent;
  border: none;
  color: var(--danger, #e74c4c);
  font-size: var(--fs13, 13px);
  font-weight: 600;
  padding: 4px 0;
  cursor: pointer;
  font-family: var(--gyst-font-stack);
}
.gyst-filter-sheet-clear:hover {
  opacity: 0.75;
}
.gyst-filter-sheet-close {
  justify-self: end;
  width: 30px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-3);
  border: 0.5px solid color-mix(in srgb, var(--border) 40%, transparent);
  border-radius: 50%;
  color: var(--text-dim);
  cursor: pointer;
  padding: 0;
}
.gyst-filter-sheet-close i,
.gyst-filter-sheet-close svg {
  width: 14px;
  height: 14px;
}
.gyst-filter-sheet-close:hover {
  background: color-mix(in srgb, var(--bg-3) 60%, var(--border) 40%);
}
.gyst-filter-sheet-header-row2 {
  display: flex;
  gap: 10px;
}
.gyst-filter-sheet-filter-btn,
.gyst-filter-sheet-search-btn {
  flex: 1;
  font-size: var(--fs15, 15px);
  font-weight: 600;
  padding: 10px 16px;
  border-radius: 12px;
  cursor: pointer;
  font-family: var(--gyst-font-stack);
}
.gyst-filter-sheet-header-row2[data-active-action="filter"] .gyst-filter-sheet-filter-btn {
  background: var(--accent);
  color: var(--background, #fff);
  border: none;
}
.gyst-filter-sheet-header-row2[data-active-action="filter"] .gyst-filter-sheet-filter-btn:hover {
  filter: brightness(1.05);
}
.gyst-filter-sheet-header-row2[data-active-action="filter"] .gyst-filter-sheet-search-btn {
  background: transparent;
  color: var(--text);
  border: 0.5px solid color-mix(in srgb, var(--border) 55%, transparent);
}
.gyst-filter-sheet-header-row2[data-active-action="filter"] .gyst-filter-sheet-search-btn:hover {
  background: color-mix(in srgb, var(--text) 6%, transparent);
}
.gyst-filter-sheet-header-row2[data-active-action="search"] .gyst-filter-sheet-search-btn {
  background: var(--accent);
  color: var(--background, #fff);
  border: none;
}
.gyst-filter-sheet-header-row2[data-active-action="search"] .gyst-filter-sheet-search-btn:hover {
  filter: brightness(1.05);
}
.gyst-filter-sheet-header-row2[data-active-action="search"] .gyst-filter-sheet-filter-btn {
  background: transparent;
  color: var(--text);
  border: 0.5px solid color-mix(in srgb, var(--border) 55%, transparent);
}
.gyst-filter-sheet-header-row2[data-active-action="search"] .gyst-filter-sheet-filter-btn:hover {
  background: color-mix(in srgb, var(--text) 6%, transparent);
}
.gyst-filter-sheet-filter-btn,
.gyst-filter-sheet-search-btn {
  transition: background 120ms ease, color 120ms ease, filter 120ms ease;
}
.gyst-filter-sheet-body {
  overflow-y: auto;
  padding: 8px 20px 32px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

/* Section labels — 15px bold, iOS spec. Standalone vs paired header. */
.gyst-filter-section-label {
  font-size: var(--fs15, 15px);
  font-weight: 700;
  color: var(--text);
  margin-bottom: 12px;
  font-family: var(--gyst-font-stack);
}
.gyst-filter-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}
.gyst-filter-section-header .gyst-filter-section-label {
  margin-bottom: 0;
}
.gyst-filter-section-clear {
  background: transparent;
  border: none;
  color: var(--accent-secondary, var(--accent));
  font-size: var(--fs13, 13px);
  font-weight: 600;
  cursor: pointer;
  padding: 0;
  font-family: var(--gyst-font-stack);
}
.gyst-filter-section-clear:hover {
  text-decoration: underline;
}

/* Mode toggle — 2-segment with subtitle text. iOS uses theme.surfacePanel
   bg + accentActive fill when selected. */
.gyst-filter-mode {
  display: flex;
  border-radius: 12px;
  background: var(--bg-2);
  border: 0.5px solid color-mix(in srgb, var(--border) 60%, transparent);
  overflow: hidden;
}
.gyst-filter-mode-btn {
  flex: 1;
  background: transparent;
  border: none;
  padding: 10px 8px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 3px;
  align-items: center;
  color: var(--text);
  transition: background 0.15s, color 0.15s;
  font-family: var(--gyst-font-stack);
}
.gyst-filter-mode-btn-label {
  font-size: var(--fs13, 13px);
  font-weight: 600;
}
.gyst-filter-mode-btn-sub {
  font-size: var(--fs10, 10px);
  color: var(--text-dim);
  text-align: center;
  line-height: 1.2;
}
.gyst-filter-mode-btn.is-active {
  background: var(--accent-active, var(--accent));
  color: var(--bg);
}
.gyst-filter-mode-btn.is-active .gyst-filter-mode-btn-sub {
  color: var(--bg);
  opacity: 0.85;
}

/* Category chips — flex wrap, capsule shape, per-category color tint
   via CSS custom properties (--gfc-bg, --gfc-border, --gfc-text). */
.gyst-filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.gyst-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 8px 12px;
  border-radius: 9999px;
  background: var(--gfc-bg);
  border: 0.5px solid var(--gfc-border);
  color: var(--gfc-text, var(--text));
  font-size: var(--fs13, 13px);
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
  white-space: nowrap;
  font-family: var(--gyst-font-stack);
}
.gyst-filter-chip i[data-lucide],
.gyst-filter-chip svg {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
}

/* Grouped panels — Health, Kids, Friends rows live inside .gyst-filter-panel. */
.gyst-filter-panel {
  background: var(--bg-2);
  border-radius: 12px;
  overflow: hidden;
}
.gyst-filter-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  cursor: pointer;
  background: transparent;
  border: none;
  width: 100%;
  text-align: left;
  font-family: var(--gyst-font-stack);
  color: inherit;
}
.gyst-filter-row + .gyst-filter-row {
  border-top: 0.5px solid color-mix(in srgb, var(--border) 35%, transparent);
}
.gyst-filter-row:hover {
  background: color-mix(in srgb, var(--text) 4%, transparent);
}
.gyst-filter-row-avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.gyst-filter-row-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.gyst-filter-row-title {
  font-size: var(--fs14, 14px);
  font-weight: 500;
  color: var(--text);
}
.gyst-filter-row-subtitle {
  font-size: var(--fs12, 12px);
  color: var(--text-dim);
  line-height: 1.3;
}
.gyst-filter-row-check {
  width: 20px;
  height: 20px;
  color: var(--text-dimmer);
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.gyst-filter-row-check i[data-lucide],
.gyst-filter-row-check svg {
  width: 20px;
  height: 20px;
}
.gyst-filter-row.is-active .gyst-filter-row-check {
  color: var(--accent-secondary, var(--accent));
}

/* Clear All — danger-tinted button. Uses the color-mix(--danger) pattern
   already in use elsewhere in this file. */
.gyst-filter-clear-all {
  background: color-mix(in srgb, var(--danger) 8%, transparent);
  border: 0.5px solid color-mix(in srgb, var(--danger) 15%, transparent);
  color: var(--danger);
  border-radius: 12px;
  padding: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-size: var(--fs14, 14px);
  font-weight: 600;
  cursor: pointer;
  font-family: var(--gyst-font-stack);
}
.gyst-filter-clear-all:hover {
  background: color-mix(in srgb, var(--danger) 14%, transparent);
}
.gyst-filter-clear-all i[data-lucide],
.gyst-filter-clear-all svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

@media (min-width: 769px) {
  .gyst-filter-sheet-overlay {
    align-items: center;             /* desktop — centered modal */
  }
  .gyst-filter-sheet {
    border-radius: 24px;
    max-height: 85vh;
    transform: scale(0.92);
    opacity: 0;
    transition: transform 0.18s ease, opacity 0.18s ease;
  }
  .gyst-filter-sheet-overlay.is-open .gyst-filter-sheet {
    transform: scale(1);
    opacity: 1;
  }
  .gyst-filter-sheet-grabber {
    display: none;
  }
}

/* ============================================================
   BOOK DETAIL SHEET — web parity 1.6
   Clone of ios/Gyst/Views/Lists/BookDetailSheet.swift via the
   renderBookDetailContent() pattern in lists.js. Layout reuses
   .gyst-detail-sheet-header / .gyst-section-label / .gyst-panel /
   .gyst-text-field-row / .gyst-text-editor-row / .gyst-row /
   .gyst-danger-row / .gyst-sticky-footer — only the rating row,
   Mark-as-Read row and inline delete-confirm panel are scoped
   below.
   ============================================================ */

/* Body wrapper — section spacing matches the Event / Task edit
   modals (24px top above each .gyst-section-label, 6px below). */
#bookDetailContent .gyst-detail-sheet-header {
  padding: 14px 16px 6px;
}
#bookDetailContent .gyst-book-detail-body {
  padding: 0 16px 12px;
}
#bookDetailContent .gyst-book-detail-body > .gyst-section-label {
  padding-top: 24px;
  padding-bottom: 6px;
}
#bookDetailContent .gyst-book-detail-body > .gyst-section-label:first-child {
  padding-top: 4px;
}

/* Sticky footer eats modal-content's bottom padding — same rule
   already shipped for #addEventModal / #taskModal (gyst-ui.css line
   ~4571). */
#bookDetailModal .modal-content {
  padding: 0;
}

/* "Open link" sibling row inside the Link panel. Mirrors iOS
   accentSecondary tinted text + leading arrow-up-right glyph. */
.gyst-book-open-link {
  text-decoration: none;
  cursor: pointer;
  color: var(--accent-secondary, var(--accent));
}
.gyst-book-open-link > .gyst-row-icon {
  color: var(--accent-secondary, var(--accent));
}
.gyst-book-open-link > .gyst-row-label {
  color: var(--accent-secondary, var(--accent));
  font-weight: 500;
}
.gyst-book-open-link:hover {
  background: color-mix(in srgb, var(--accent) 6%, transparent);
}

/* Rating row — 5 stars centered inside the panel. Active star uses
   --gold token (falls back to themed amber). Tap a filled star to
   clear (matches iOS line 259). */
.gyst-book-rating-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 14px var(--gyst-row-px);
}
.gyst-book-rating-star {
  background: transparent;
  border: none;
  padding: 0;
  margin: 0;
  font-size: var(--fs28, 28px);
  line-height: 1;
  cursor: pointer;
  color: color-mix(in srgb, var(--text-dim) 40%, transparent);
  font-family: inherit;
  transition: color 0.12s, transform 0.12s;
}
.gyst-book-rating-star:hover {
  transform: scale(1.1);
}
.gyst-book-rating-star.is-active {
  color: var(--gold, #f0c878);
}

/* Status row — Mark as Read / Move back to Reading. Inside a panel,
   so .gyst-row baseline padding + min-height apply. iOS tints with
   accentPrimary when unread and textDim when checked. */
.gyst-book-status-row {
  width: 100%;
  background: transparent;
  border: none;
  padding: var(--gyst-row-py) var(--gyst-row-px);
  display: flex;
  align-items: center;
  gap: 12px;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  color: var(--accent);
}
.gyst-book-status-row > .gyst-row-icon {
  color: var(--accent);
}
.gyst-book-status-row > .gyst-row-label {
  color: var(--accent);
  font-weight: 500;
  flex: 1;
}
.gyst-book-status-row.is-checked,
.gyst-book-status-row.is-checked > .gyst-row-icon,
.gyst-book-status-row.is-checked > .gyst-row-label {
  color: var(--text-dim);
}
.gyst-book-status-row:hover {
  background: color-mix(in srgb, var(--accent) 6%, transparent);
}
.gyst-book-status-row.is-checked:hover {
  background: color-mix(in srgb, var(--text-dim) 6%, transparent);
}
.gyst-book-status-date {
  color: var(--text-dim);
  font-size: var(--fs12, 12px);
  font-variant-numeric: tabular-nums;
}

/* Inline delete-confirm panel — replaces native confirm() and the
   legacy book-delete-confirm toast. Lives inside the same panel as
   the Delete Book row so the row disappears and this slides in. */
.gyst-book-confirm {
  padding: 12px var(--gyst-row-px);
  background: color-mix(in srgb, var(--danger) 6%, transparent);
  border-top: 0.5px solid color-mix(in srgb, var(--danger) 25%, transparent);
}
.gyst-book-confirm-message {
  font-size: var(--fs13, 13px);
  color: var(--text);
  margin-bottom: 10px;
  line-height: 1.4;
}
.gyst-book-confirm-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}
.gyst-book-confirm-delete {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 16px;
  background: var(--danger);
  border: none;
  border-radius: 12px;
  color: #fff; /* deliberate: danger fill — --accent-active-text tracks the ACCENT's luminance, not danger's */
  font-family: inherit;
  font-size: var(--fs13, 13px);
  font-weight: 600;
  cursor: pointer;
}
.gyst-book-confirm-delete:hover {
  filter: brightness(1.08);
}
.gyst-book-confirm-delete i[data-lucide] {
  width: 14px;
  height: 14px;
}
/* Card variant — the rounded danger well iOS GystUI.ConfirmActionPanel draws
   (nestedRadius, dangerBgOpacity fill, dangerBorderOpacity 1px stroke), for a
   confirm that floats inside a panel or on its own rather than replacing a
   flush row. New code: class="gyst-book-confirm is-card". The three domain
   classes below are THIN ALIASES for it (GystUI Lead 2026-09-29, item 4):
   each used to re-declare this look in its own sheet — habits.css and
   lists.css load before this file, so their fill and top border lost to
   .gyst-book-confirm and those two cards drew differently from Money's.
   Their sheets now keep only their own spacing. */
.gyst-book-confirm.is-card,
.gyst-book-confirm.money-unlink-confirm,
.gyst-book-confirm.habit-slip-confirm,
.gyst-book-confirm.list-leave-confirm {
  border: 1px solid color-mix(in srgb, var(--danger) var(--gyst-danger-border), transparent);
  border-radius: var(--gyst-nested-radius);
  background: color-mix(in srgb, var(--danger) var(--gyst-danger-wash), transparent);
}

/* ============================================================
   ITEM EDIT SHEET — web parity 1.5
   Clone of ios/Gyst/Views/Lists/ItemEditSheet.swift via the
   renderItemEditContent() pattern in lists.js. Same layout shape
   as the Book Detail sheet — reuses .gyst-detail-sheet-header /
   .gyst-section-label / .gyst-panel / .gyst-text-field-row /
   .gyst-text-editor-row / .gyst-row / .gyst-danger-row /
   .gyst-sticky-footer / .gyst-book-open-link / .gyst-book-confirm*
   from the existing inventory. Only the body wrapper rhythm is
   scoped here — matches the book detail spacing exactly so the
   two sheets feel like siblings.
   ============================================================ */
#itemEditContent .gyst-detail-sheet-header {
  padding: 14px 16px 6px;
}
#itemEditContent .gyst-item-edit-body {
  padding: 0 16px 12px;
}
#itemEditContent .gyst-item-edit-body > .gyst-section-label {
  padding-top: 24px;
  padding-bottom: 6px;
}
#itemEditContent .gyst-item-edit-body > .gyst-section-label:first-child {
  padding-top: 4px;
}
/* Sticky footer eats modal-content's bottom padding — same rule
   shipped for #bookDetailModal / #addEventModal / #taskModal. */
#itemEditModal .modal-content {
  padding: 0;
}

/* ============================================================
   EVENT CATEGORY PICKER (Surface 3.1)

   Clone of iOS `EventCategoryPickerSheet` — first-step chooser
   shown when creating a new event. Mobile: full-width slide-up
   sheet anchored at the bottom (matches gyst-filter-sheet). 
   Desktop: centered modal (480px max).

   iOS spec lives in ios/Gyst/Views/Events/EventCategoryPickerSheet.swift.
   ============================================================ */
.gyst-event-cat-sheet-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  display: none;
  opacity: 0;
  transition: opacity 0.18s ease;
  z-index: 1000;
  align-items: flex-end;
  justify-content: center;
}
.gyst-event-cat-sheet-overlay.is-open {
  display: flex;
  opacity: 1;
}
.gyst-event-cat-sheet {
  width: 100%;
  max-width: min(480px, var(--content-w));
  max-height: 85vh;
  background: var(--bg-1);
  border-top-left-radius: 24px;
  border-top-right-radius: 24px;
  display: flex;
  flex-direction: column;
  transform: translateY(100%);
  transition: transform 0.22s cubic-bezier(0.2, 0.7, 0.2, 1);
  box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.18);
  font-family: var(--gyst-font-stack);
}
.gyst-event-cat-sheet-overlay.is-open .gyst-event-cat-sheet {
  transform: translateY(0);
}
.gyst-event-cat-sheet-grabber {
  width: 36px;
  height: 4px;
  border-radius: 2px;
  background: color-mix(in srgb, var(--text) 22%, transparent);
  margin: 8px auto 4px;
  flex: 0 0 auto;
}
.gyst-event-cat-sheet-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 8px 16px 24px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.gyst-event-cat-headline {
  font-size: var(--fs22, 22px);
  font-weight: 800;
  letter-spacing: 2.5px;
  color: var(--text);
  margin: 4px 0 0;
}
.gyst-event-cat-subtitle {
  font-size: var(--fs12, 12px);
  font-weight: 500;
  letter-spacing: 0.4px;
  color: var(--text-dim);
  margin-top: -14px;
}
.gyst-event-cat-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px;
}
.gyst-event-cat-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px;
  min-height: 130px;
  background: var(--bg-2);
  border: 1px solid var(--gyst-panel-border, var(--border));
  border-radius: 14px;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
  color: var(--text);
  align-items: stretch;
  justify-content: flex-start;
  transition: background 0.12s ease, transform 0.08s ease;
}
.gyst-event-cat-card:hover {
  background: color-mix(in srgb, var(--bg-2) 92%, var(--text) 8%);
}
.gyst-event-cat-card:active {
  transform: scale(0.985);
}
.gyst-event-cat-card-circle {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--gec-color, var(--accent)) 18%, transparent);
  flex: 0 0 auto;
}
.gyst-event-cat-card-icon {
  width: 22px;
  height: 22px;
  color: var(--gec-color, var(--accent));
  stroke-width: 2;
}
.gyst-event-cat-card-text {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.gyst-event-cat-card-label {
  font-size: var(--fs15, 15px);
  font-weight: 600;
  color: var(--text);
  line-height: 1.25;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gyst-event-cat-card-desc {
  font-size: var(--fs11, 11px);
  color: var(--text-dim);
  line-height: 1.3;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Desktop: centered modal instead of bottom sheet */
@media (min-width: 769px) {
  .gyst-event-cat-sheet-overlay { align-items: center; }
  .gyst-event-cat-sheet {
    border-radius: 24px;
    max-height: 80vh;
    transform: translateY(20px) scale(0.96);
    opacity: 0;
    transition: transform 0.22s cubic-bezier(0.2, 0.7, 0.2, 1), opacity 0.18s ease;
  }
  .gyst-event-cat-sheet-overlay.is-open .gyst-event-cat-sheet {
    transform: translateY(0) scale(1);
    opacity: 1;
  }
  .gyst-event-cat-sheet-grabber { display: none; }
}

/* ============================================================
 * LIST GALLERY — clone of iOS NewListSheetView (ListsPanelView.swift)
 *
 * Template picker that opens from the "+ ADD → List" entry point.
 * Layout: section label → full-width Master Grocery featured card →
 * 2-col grid of template cards → "Your Lists" rail of existing lists.
 * Visually mirrors iOS TemplateCard + ExistingListRow.
 * ============================================================ */

.gyst-list-template-section-label {
  font-size: var(--fs11, 11px);
  font-weight: 700;
  letter-spacing: 1.5px;
  color: var(--text-dimmer);
  text-transform: uppercase;
  padding: 12px 4px 8px;
}

.gyst-list-template-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin-bottom: 4px;
}

.gyst-list-template-card {
  position: relative;
  background: var(--surface-panel, var(--bg-2));
  border: 1px solid transparent;
  border-radius: 12px;
  padding: 12px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  /* Left-aligned header-row layout (2026-06-11) — the edit-starters
     control sits in-flow in the top row instead of floating over the
     name, and the text aligns with the featured card's. */
  align-items: stretch;
  gap: 6px;
  text-align: left;
  transition: transform 0.12s ease, border-color 0.15s ease, background 0.15s ease;
  -webkit-tap-highlight-color: transparent;
  font-family: inherit;
}

/* Header row: emoji left, optional edit-starters control right. */
.gyst-list-template-card-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  width: 100%;
}

.gyst-list-template-card:hover {
  border-color: color-mix(in srgb, var(--accent-active, var(--accent)) 50%, transparent);
  transform: translateY(-1px);
}

.gyst-list-template-card.is-selected {
  background: color-mix(in srgb, var(--accent-active, var(--accent)) 12%, var(--surface-panel, var(--bg-2)));
  border-color: color-mix(in srgb, var(--accent-active, var(--accent)) 50%, transparent);
}

.gyst-list-template-card-emoji {
  font-size: var(--fs28, 28px);
  line-height: 1;
}

.gyst-list-template-card-name {
  font-size: var(--fs12, 12px);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--text);
  line-height: 1.25;
  /* iOS allows up to 2 lines for long names like "Date Night Ideas" */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.gyst-list-template-card-desc {
  font-size: var(--fs10, 10px);
  font-weight: 400;
  color: var(--text-dimmer);
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Featured (full-width) variant — Master Grocery */
.gyst-list-template-card.is-featured {
  grid-column: 1 / -1;
  flex-direction: row;
  align-items: center;
  text-align: left;
  gap: 14px;
  padding: 16px;
}

.gyst-list-template-card.is-featured .gyst-list-template-card-emoji {
  font-size: var(--fs32, 32px);
  flex-shrink: 0;
}

.gyst-list-template-card-stack {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.gyst-list-template-card.is-featured .gyst-list-template-card-name {
  font-size: var(--fs14, 14px);
  -webkit-line-clamp: 1;
  text-align: left;
}

.gyst-list-template-card.is-featured .gyst-list-template-card-desc {
  font-size: var(--fs11, 11px);
  text-align: left;
  -webkit-line-clamp: 2;
  color: var(--text-dim);
}

.gyst-list-template-card-trailing {
  flex-shrink: 0;
  font-size: var(--fs14, 14px);
  color: var(--text-dimmer);
  width: 16px;
  height: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.gyst-list-template-card-trailing.is-new {
  /* "Star" indicator when this template doesn't exist yet — iOS uses
   * star.fill in yellow to signal "new / featured option". */
  color: var(--gold, #F2C94C);
}

/* "Your Lists" rail below the template grid */
.gyst-list-existing-rail {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.gyst-list-existing-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  background: var(--surface-panel, var(--bg-2));
  border: 1px solid var(--chrome-border, var(--border));
  border-radius: 8px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: border-color 0.15s ease, background 0.15s ease;
  font-family: inherit;
  text-align: left;
}

.gyst-list-existing-row:hover {
  border-color: color-mix(in srgb, var(--accent-active, var(--accent)) 50%, transparent);
  background: var(--surface-hover, var(--bg-3));
}

.gyst-list-existing-row-icon {
  font-size: var(--fs22, 22px);
  line-height: 1;
  width: 32px;
  text-align: center;
  flex-shrink: 0;
}

.gyst-list-existing-row-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.gyst-list-existing-row-name {
  font-size: var(--fs13, 13px);
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.gyst-list-existing-row-meta {
  font-size: var(--fs11, 11px);
  color: var(--text-dimmer);
  line-height: 1.2;
}

.gyst-list-existing-row-chevron {
  color: var(--text-dimmer);
  flex-shrink: 0;
  font-size: var(--fs11, 11px);
  display: flex;
  align-items: center;
}

/* Grocery flow inline-choice panel (Start Fresh vs Use Master).
 * iOS uses GystUI.InlineChoicePanel; web reuses gyst-confirm patterns. */
.gyst-list-grocery-choice {
  background: var(--surface-panel, var(--bg-2));
  border: 1px solid var(--chrome-border, var(--border));
  border-radius: 12px;
  padding: 14px;
  margin-bottom: 4px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.gyst-list-grocery-choice-message {
  font-size: var(--fs13, 13px);
  color: var(--text);
  line-height: 1.35;
}

.gyst-list-grocery-choice-actions {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.gyst-list-grocery-choice-btn {
  background: var(--surface-raised, var(--bg-3));
  border: 1px solid var(--chrome-border, var(--border));
  border-radius: 8px;
  padding: 10px 12px;
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  text-align: left;
  font-family: inherit;
  color: var(--text);
}

.gyst-list-grocery-choice-btn:hover {
  border-color: color-mix(in srgb, var(--accent-active, var(--accent)) 50%, transparent);
}

.gyst-list-grocery-choice-btn-icon {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  color: var(--accent-active, var(--accent));
  display: flex;
  align-items: center;
  justify-content: center;
}

.gyst-list-grocery-choice-btn-stack {
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1;
  min-width: 0;
}

.gyst-list-grocery-choice-btn-title {
  font-size: var(--fs13, 13px);
  font-weight: 600;
  color: var(--text);
}

.gyst-list-grocery-choice-btn-subtitle {
  font-size: var(--fs11, 11px);
  color: var(--text-dimmer);
}

/* Body container for the list gallery — replaces modal-content default
 * padding so the section labels can hug the edges like iOS. */
#listGalleryContent.gyst-list-gallery-body {
  padding: 14px 20px 24px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}


/* ============================================================
 * TOAST — clone of iOS ErrorToastView (Views/Shared/ErrorToastView.swift)
 *
 * Top-anchored short-lived message strip. iOS variant always renders
 * in the danger family (warning glyph + danger border). The web variant
 * extends the same chrome to three intents (error / success / info) so
 * legacy `alert()` swaps land on a single primitive. Slides in from the
 * top, auto-dismisses after 5s, dismissable via the × button.
 *
 * Helpers in popover.js:
 *   window.showErrorToast(message, options?)
 *   window.showSuccessToast(message, options?)
 *   window.showInfoToast(message, options?)
 * ============================================================ */

.gyst-toast-host {
  position: fixed;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 9999;
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
  width: min(420px, calc(100vw - 24px));
}

.gyst-toast {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 14px;
  border-radius: 12px;
  background: var(--bg-1, var(--bg, #fffefa));
  border: 1px solid color-mix(in srgb, var(--danger, #e57373) 30%, transparent);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
  pointer-events: auto;
  animation: gyst-toast-in 0.22s cubic-bezier(0.22, 0.61, 0.36, 1);
  max-width: 100%;
}

.gyst-toast.is-leaving {
  animation: gyst-toast-out 0.18s ease-in forwards;
}

@keyframes gyst-toast-in {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes gyst-toast-out {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(-8px); }
}

.gyst-toast-icon {
  width: 16px;
  height: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  margin-top: 1px;
  color: var(--danger, #e57373);
}
.gyst-toast-icon svg { width: 14px; height: 14px; }

.gyst-toast-message {
  flex: 1;
  font-size: var(--fs13, 13px);
  font-weight: 500;
  color: var(--text);
  line-height: 1.35;
  word-wrap: break-word;
}

.gyst-toast-dismiss {
  background: transparent;
  border: none;
  color: var(--text-dim);
  font-size: var(--fs11, 11px);
  font-weight: 600;
  cursor: pointer;
  padding: 2px 4px;
  margin: -2px -4px -2px 0;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  -webkit-tap-highlight-color: transparent;
}
.gyst-toast-dismiss:hover { color: var(--text); }
.gyst-toast-dismiss svg { width: 10px; height: 10px; }

/* Intent variants — swap the icon color + border tint */
.gyst-toast.is-success {
  border-color: color-mix(in srgb, var(--success, #6dd6a0) 30%, transparent);
}
.gyst-toast.is-success .gyst-toast-icon { color: var(--success, #6dd6a0); }

.gyst-toast.is-info {
  border-color: color-mix(in srgb, var(--accent-secondary, var(--accent)) 30%, transparent);
}
.gyst-toast.is-info .gyst-toast-icon { color: var(--accent-secondary, var(--accent)); }

/* ============================================================
 * EMPTY STATE — clone of iOS EmptyStateView (Views/Shared/EmptyStateView.swift)
 *
 * Centered icon + title + subtitle (+ optional CTA chip) for empty
 * lists / tabs / panels. iOS source uses a 44pt SF Symbol; web pairs
 * the wrapper with a Lucide SVG (consumer chooses the icon). Use
 * `window.buildEmptyState({ icon, title, subtitle, actionLabel,
 * onAction })` in popover.js to produce the DOM node.
 * ============================================================ */

.gyst-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  padding: 48px 16px;
  max-width: 100%;
  text-align: center;
}

.gyst-empty-state-icon {
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* --gyst-empty-tint (set by buildEmptyState({tint}) on .gyst-empty-state)
   * lets an identity-tinted surface (e.g. Money's finance accent) override
   * the default accent — falls back to the original expression so every
   * call site that doesn't pass tint is unaffected. */
  color: var(--gyst-empty-tint, color-mix(in srgb, var(--accent-secondary, var(--accent)) 40%, transparent));
  flex-shrink: 0;
}
.gyst-empty-state-icon svg { width: 44px; height: 44px; }

.gyst-empty-state-text {
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: center;
}

.gyst-empty-state-title {
  font-size: var(--fs18, 18px);
  font-weight: 600;
  color: var(--text);
  line-height: 1.25;
}

.gyst-empty-state-subtitle {
  font-size: var(--fs14, 14px);
  font-weight: 400;
  color: var(--text-dim);
  line-height: 1.4;
  max-width: 360px;
  padding: 0 32px;
}

.gyst-empty-state-action {
  margin-top: 4px;
  /* Same --gyst-empty-tint fallback chain as .gyst-empty-state-icon above —
   * unset (the common case) resolves to the original expression. */
  background: color-mix(in srgb, var(--gyst-empty-tint, var(--accent-secondary, var(--accent))) 12%, transparent);
  color: var(--gyst-empty-tint, var(--accent-secondary, var(--accent)));
  border: none;
  border-radius: 999px;
  padding: 10px 20px;
  font-size: var(--fs14, 14px);
  font-weight: 600;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.12s;
}
.gyst-empty-state-action:not(:disabled):hover {
  background: color-mix(in srgb, var(--gyst-empty-tint, var(--accent-secondary, var(--accent))) 20%, transparent);
}

/* Inert/"coming soon" CTA — e.g. Money's "Link your first account" chip
 * ahead of Plaid Link landing in Phase 3. Stays visible (iOS parity: an
 * inactive chip, not a hidden one) but reads as non-interactive. Rendered
 * as a rectangular chip (10px), not the enabled state's pill (999px) —
 * iOS parity: the Phase-1 Money shell's RoundedRectangle(cornerRadius: 10)
 * chip (MoneyDestinationView.swift, since replaced by MoneyOverviewView,
 * whose empty-state CTA is live and therefore a pill).
 * Only :disabled is scoped this way; the enabled pill (tasks.js "Add Task")
 * is untouched. */
.gyst-empty-state-action:disabled {
  color: var(--text-dimmer);
  background: var(--surface-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  cursor: default;
  pointer-events: none;
}

/* ============================================================
 * SKELETON — clone of iOS SkeletonView (Views/Shared/SkeletonView.swift)
 *
 * Loading placeholder: rounded rect filled with surfaceRaised that has
 * a soft text-tinted gradient sliding across at 1.2s linear repeat.
 * Sized via CSS custom properties so consumers can override per
 * instance: `style="--gs-w: 80px; --gs-h: 12px; --gs-r: 4px"` or via
 * the inline width/height passed by buildSkeleton.
 * ============================================================ */

.gyst-skeleton {
  display: inline-block;
  width: var(--gs-w, 100%);
  height: var(--gs-h, 16px);
  border-radius: var(--gs-r, 8px);
  background: var(--surface-raised, var(--bg-3, #f0eee8));
  position: relative;
  overflow: hidden;
}
.gyst-skeleton::after {
  content: '';
  position: absolute;
  top: 0;
  left: -150%;
  width: 60%;
  height: 100%;
  background: linear-gradient(90deg,
    transparent 0%,
    color-mix(in srgb, var(--text) 6%, transparent) 50%,
    transparent 100%);
  animation: gyst-skeleton-shimmer 1.2s linear infinite;
}
@keyframes gyst-skeleton-shimmer {
  from { transform: translateX(0); }
  to   { transform: translateX(417%); /* (100% + 150%) / 60% ≈ moves the band off-stage */ }
}

/* ============================================================
 * UNDO TOAST — clone of iOS UndoToastView (Views/Shared/UndoToastView.swift)
 *
 * Spawns at the user's last pointer release point after any
 * `undoPush()` records a destructive action; falls back to bottom-
 * center when no pointer context is available. Shows the action
 * label + event title + Undo button + dismiss ×. Auto-dismisses
 * after 6 seconds.
 * ============================================================ */

.gyst-undo-toast {
  position: fixed;
  z-index: 9998; /* one below regular toasts */
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  background: var(--bg-1, var(--bg, #fffefa));
  border: 0.5px solid color-mix(in srgb, var(--chrome-border, var(--border)) 45%, transparent);
  border-radius: 12px;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.22);
  pointer-events: auto;
  animation: gyst-undo-toast-in 0.22s cubic-bezier(0.22, 0.61, 0.36, 1);
  max-width: 320px;
  width: max-content;
}
.gyst-undo-toast.is-leaving {
  animation: gyst-undo-toast-out 0.18s ease-in forwards;
}
@keyframes gyst-undo-toast-in {
  from { opacity: 0; transform: translateY(8px) scale(0.96); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes gyst-undo-toast-out {
  from { opacity: 1; transform: translateY(0) scale(1); }
  to   { opacity: 0; transform: translateY(8px) scale(0.96); }
}

.gyst-undo-toast-icon {
  width: 14px;
  height: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-dim);
  flex-shrink: 0;
}
.gyst-undo-toast-icon svg { width: 12px; height: 12px; }

.gyst-undo-toast-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  flex: 1;
}
.gyst-undo-toast-label {
  font-size: var(--fs11, 11px);
  font-weight: 600;
  color: var(--text-dim);
  line-height: 1.2;
}
.gyst-undo-toast-title {
  font-size: var(--fs12, 12px);
  font-weight: 500;
  color: var(--text);
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.gyst-undo-toast-undo {
  background: transparent;
  border: none;
  color: var(--accent-secondary, var(--accent));
  font-size: var(--fs13, 13px);
  font-weight: 700;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 6px;
  -webkit-tap-highlight-color: transparent;
  flex-shrink: 0;
  transition: background 0.12s;
}
.gyst-undo-toast-undo:hover {
  background: color-mix(in srgb, var(--accent-secondary, var(--accent)) 10%, transparent);
}

.gyst-undo-toast-dismiss {
  background: var(--surface-raised, var(--bg-3, #f0eee8));
  border: none;
  color: var(--text-dim);
  width: 18px;
  height: 18px;
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  flex-shrink: 0;
  -webkit-tap-highlight-color: transparent;
}
.gyst-undo-toast-dismiss svg { width: 9px; height: 9px; }

/* ============================================================
 * API KEY VAULT — clones iOS ProfileView's API Keys section
 *
 * Each provider renders as a `.gyst-row.gyst-row-tappable` inside a
 * `.gyst-panel` with leading icon-tile, name + (when connected)
 * masked-key sublabel, trailing chip ("Connected" / "Add"), and a
 * chevron. Tapping the row reveals the inline `.gyst-api-key-edit`
 * panel for paste/save/cancel/remove.
 * ============================================================ */

.gyst-api-key-vault { display: flex; flex-direction: column; gap: 8px; }

.gyst-api-key-row {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.gyst-api-key-row:hover {
  background: color-mix(in srgb, var(--text) 4%, transparent);
}

.gyst-api-key-icon {
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text);
  flex-shrink: 0;
}
.gyst-api-key-icon svg { width: 18px; height: 18px; }

.gyst-api-key-row.is-connected .gyst-api-key-icon {
  color: var(--accent-secondary, var(--accent));
}

.gyst-row-label-stack {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}
.gyst-row-label-stack .gyst-row-label {
  font-size: var(--fs15, 15px);
  font-weight: 500;
  color: var(--text);
  line-height: 1.2;
}
.gyst-row-label-stack .gyst-row-sublabel {
  font-size: var(--fs11, 11px);
  color: var(--text-dim);
  line-height: 1.2;
  font-family: var(--mono);
  letter-spacing: 0.3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.gyst-api-key-chip {
  font-size: var(--fs11, 11px);
  font-weight: 600;
  letter-spacing: 0.3px;
  padding: 4px 8px;
  border-radius: 999px;
  flex-shrink: 0;
}
.gyst-api-key-chip.is-on {
  color: var(--success, #6dd6a0);
  background: color-mix(in srgb, var(--success, #6dd6a0) 12%, transparent);
}
.gyst-api-key-chip.is-tap {
  color: var(--accent-secondary, var(--accent));
  background: color-mix(in srgb, var(--accent-secondary, var(--accent)) 12%, transparent);
}

.gyst-api-key-edit {
  margin-top: 8px;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.gyst-api-key-edit-label {
  margin: 0;
}
.gyst-api-key-input-wrap {
  display: flex;
  align-items: center;
  gap: 8px;
}
.gyst-api-key-input {
  flex: 1;
  min-width: 0;
  background: var(--surface-panel, var(--bg-2));
  border: 0.5px solid color-mix(in srgb, var(--input-border, var(--border)) 50%, transparent);
  border-radius: 8px;
  color: var(--text);
  font-family: var(--mono);
  font-size: var(--fs13, 13px);
  padding: 8px 12px;
}
.gyst-api-key-input:focus {
  outline: none;
  border-color: var(--accent-secondary, var(--accent));
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent-secondary, var(--accent)) 20%, transparent);
}
.gyst-api-key-save {
  background: var(--accent-secondary, var(--accent));
  color: var(--bg-1, #fff);
  border: none;
  border-radius: 8px;
  padding: 8px 14px;
  font-size: var(--fs13, 13px);
  font-weight: 600;
  cursor: pointer;
  flex-shrink: 0;
}
.gyst-api-key-save:hover { filter: brightness(1.05); }
.gyst-api-key-cancel {
  background: transparent;
  color: var(--text-dim);
  border: 0.5px solid color-mix(in srgb, var(--input-border, var(--border)) 50%, transparent);
  border-radius: 8px;
  width: 32px;
  height: 32px;
  font-size: var(--fs14, 14px);
  cursor: pointer;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.gyst-api-key-cancel:hover {
  color: var(--text);
  background: color-mix(in srgb, var(--text) 6%, transparent);
}
.gyst-api-key-remove {
  border-top: 0.5px solid color-mix(in srgb, var(--chrome-border, var(--border)) 40%, transparent);
  padding-top: 10px;
}
.gyst-api-key-remove-btn {
  background: color-mix(in srgb, var(--danger, #e57373) 10%, transparent);
  color: var(--danger, #e57373);
  border: none;
  border-radius: 8px;
  padding: 8px 14px;
  font-size: var(--fs13, 13px);
  font-weight: 600;
  cursor: pointer;
}
.gyst-api-key-remove-btn:hover {
  background: color-mix(in srgb, var(--danger, #e57373) 18%, transparent);
}

/* ============================================================
 * IMPORT / EXPORT modal — clones iOS ImportExportInlineView pattern
 *
 * Each option is a tappable `.gyst-row` inside a `.gyst-panel` with
 * leading 28×28 icon-tile (tinted via the `--ie-tint` inline CSS
 * variable), name + dim sublabel, and trailing chevron. Buttons
 * preserve the legacy `ie-btn` class + IDs so the existing app.js
 * import/export handlers fire unchanged.
 * ============================================================ */

.gyst-ie-body {
  padding: 14px 18px 18px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.gyst-ie-row {
  background: transparent;
  border: none;
  width: 100%;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.gyst-ie-row:hover {
  background: color-mix(in srgb, var(--text) 4%, transparent);
}

.gyst-ie-icon {
  --ie-tint: var(--accent-secondary, var(--accent));
  width: 28px;
  height: 28px;
  background: color-mix(in srgb, var(--ie-tint) 18%, transparent);
  color: var(--ie-tint);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.gyst-ie-icon svg { width: 16px; height: 16px; }

/* Import/export sublabel descriptions are intentionally multi-line —
 * override the default single-line truncation from `.gyst-row-sublabel`
 * so the source description ("Exported from Google Calendar" etc.)
 * isn't ellipsised. */
.gyst-ie-row .gyst-row-sublabel {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  font-family: var(--font);
  letter-spacing: normal;
  line-height: 1.3;
}

/* Notifications panel — Enable button matches the iOS PrimaryActionButton
 * tinted variant (accent-secondary @ 12% bg + accent-secondary text +
 * 999px radius). Sized compact so the row's flex layout keeps it
 * trailing-aligned without growing into the chevron slot. */
.gyst-notif-enable-btn {
  background: color-mix(in srgb, var(--accent-secondary, var(--accent)) 12%, transparent);
  color: var(--accent-secondary, var(--accent));
  border: none;
  border-radius: 999px;
  padding: 6px 14px;
  font-size: var(--fs12, 12px);
  font-weight: 600;
  letter-spacing: 0.2px;
  cursor: pointer;
  flex-shrink: 0;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.12s;
}
.gyst-notif-enable-btn:hover {
  background: color-mix(in srgb, var(--accent-secondary, var(--accent)) 20%, transparent);
}
.gyst-notif-enable-btn:disabled {
  opacity: 0.5;
  cursor: default;
}

/* ============================================================
   Filter & Search — full feature stylesheet (2026-05-15)
   Pixel parity with iOS FilterAndSearchSheet.
   ============================================================ */
.gyst-status-icon {
  display: inline-block;
  vertical-align: middle;
  flex-shrink: 0;
}

.fns-section { margin-bottom: 24px; }
.fns-section:last-child { margin-bottom: 0; }

.fns-section-label {
  font-size: var(--fs11, 11px);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-dim);
  margin: 0 0 10px 4px;
}

.fns-block { margin-bottom: 16px; }
.fns-block:last-child { margin-bottom: 0; }

.fns-subhead {
  font-size: var(--fs13, 13px);
  font-weight: 700;
  color: var(--text);
  margin: 0 0 8px 0;
}

.fns-empty-panel {
  background: var(--surface-panel);
  border: 0.5px solid var(--chrome-border);
  border-radius: 12px;
  padding: 12px 14px;
  color: var(--text-dim);
  font-size: var(--fs13, 13px);
}

.fns-divider {
  height: 0.5px;
  background: var(--chrome-border);
  opacity: 0.4;
  margin: 0 14px;
}

/* Saved filters */
.fns-saved-panel {
  background: var(--surface-panel);
  border: 0.5px solid var(--chrome-border);
  border-radius: 12px;
  overflow: hidden;
}
.fns-saved-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  cursor: pointer;
}
.fns-saved-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex-shrink: 0;
}
.fns-saved-name {
  flex: 1;
  font-size: var(--fs14, 14px);
  font-weight: 500;
  color: var(--text);
}
.fns-saved-check {
  font-size: var(--fs20, 20px);
  line-height: 1;
  color: var(--text-dimmer);
}
.fns-saved-check.is-on { color: var(--accent); }

.fns-saved-row-wrap { display: block; }

/* Bespoke toggle switch — web analog of iOS GystUI.ThemedToggle */
.fns-switch {
  position: relative;
  width: 44px;
  height: 24px;
  border-radius: 5px;
  background: var(--surface-raised);
  border: 1px solid color-mix(in srgb, var(--text-dim) 45%, transparent);
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.18s ease;
}
.fns-switch.is-on {
  background: var(--accent-active, var(--accent));
  border-color: transparent;
}
.fns-switch-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 3px;
  background: var(--text-dimmer);
  transition: transform 0.18s ease, background 0.18s ease;
}
.fns-switch.is-on .fns-switch-thumb {
  background: var(--bg);
  transform: translateX(20px);
}

/* Hint shown inside the panel when no filters exist yet. Lives in
   the same panel as the action row, so the panel itself never reads
   as "empty". */
.fns-saved-empty-hint {
  padding: 10px 14px;
  font-size: var(--fs13, 13px);
  color: var(--text-dim);
}

/* Saved filter inline-expand action pane — tinted bg-3 surround
   so it visually nests under the row that triggered it. */
.fns-saved-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px 10px 14px;
  background: color-mix(in srgb, var(--bg-3) 55%, transparent);
  border-top: 0.5px solid color-mix(in srgb, var(--chrome-border) 50%, transparent);
}
.fns-saved-actions-menu {
  gap: 6px;
}
.fns-saved-actions-confirm,
.fns-saved-actions-input {
  gap: 8px;
}
.fns-saved-action-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border-radius: 8px;
  background: var(--surface-panel);
  border: 0.5px solid color-mix(in srgb, var(--chrome-border) 35%, transparent);
  color: var(--text);
  font-size: var(--fs13, 13px);
  font-weight: 600;
  cursor: pointer;
  font-family: var(--gyst-font-stack);
}
.fns-saved-action-btn:hover {
  background: color-mix(in srgb, var(--text) 6%, var(--surface-panel));
}
.fns-saved-action-btn.is-danger {
  color: var(--danger, #e74c4c);
}
.fns-saved-action-btn i,
.fns-saved-action-btn svg {
  width: 12px;
  height: 12px;
}
/* Pill Save — accent fill, matches the new top-bar Filter pill. */
.fns-saved-action-save {
  padding: 6px 14px;
  border: none;
  border-radius: 10px;
  background: var(--accent);
  color: var(--background, #fff);
  font-size: var(--fs13, 13px);
  font-weight: 600;
  cursor: pointer;
  font-family: var(--gyst-font-stack);
}
.fns-saved-action-save:hover {
  filter: brightness(1.05);
}
.fns-saved-action-save-danger {
  background: var(--danger, #e74c4c);
}
/* Ghost Cancel — neutral text, no chrome. */
.fns-saved-action-cancel {
  padding: 6px 8px;
  border: none;
  background: transparent;
  color: var(--text-dim);
  font-size: var(--fs13, 13px);
  cursor: pointer;
  font-family: var(--gyst-font-stack);
}
.fns-saved-action-cancel:hover {
  color: var(--text);
}
/* Polished input — rounded surface, accent focus border. */
.fns-saved-rename-input {
  flex: 1;
  min-width: 0;
  padding: 8px 12px;
  background: var(--bg-3, var(--bg-2));
  border: 0.5px solid color-mix(in srgb, var(--chrome-border) 40%, transparent);
  border-radius: 8px;
  color: var(--text);
  font-size: var(--fs14, 14px);
  outline: none;
  font-family: var(--gyst-font-stack);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.fns-saved-rename-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent);
}
.fns-saved-delete-msg {
  font-size: var(--fs13, 13px);
  color: var(--text);
}
.fns-overflow-btn.is-active { color: var(--accent-secondary, var(--accent)); }
.fns-overflow-btn {
  background: none;
  border: none;
  color: var(--text-dim);
  padding: 4px 6px;
  cursor: pointer;
  font-size: var(--fs18, 18px);
  line-height: 1;
}

/* Trailing "+ Save current as filter" row inside the Saved Filters
   panel — looks like a row, behaves like a button. Disabled when no
   criteria are configured (mirrors prior fns-save-current `is-enabled`
   gating). */
.fns-saved-save-current-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 12px 14px;
  background: transparent;
  border: none;
  color: var(--accent-secondary, var(--accent));
  font-size: var(--fs14, 14px);
  font-weight: 600;
  cursor: pointer;
  text-align: left;
  font-family: var(--gyst-font-stack);
}
.fns-saved-save-current-row:hover:not(.is-disabled) {
  background: color-mix(in srgb, var(--accent) 6%, transparent);
}
.fns-saved-save-current-row.is-disabled {
  color: var(--text-dimmer);
  cursor: not-allowed;
}
.fns-saved-save-current-plus {
  font-size: var(--fs16, 16px);
  line-height: 1;
  font-weight: 700;
}

/* Search field */
.fns-search-field {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: var(--surface-panel);
  border: 0.5px solid var(--chrome-border);
  border-radius: 12px;
  margin-bottom: 10px;
}
.fns-search-input {
  flex: 1;
  background: transparent;
  border: none;
  color: var(--text);
  font-size: var(--fs14, 14px);
  outline: none;
}
.fns-search-clear {
  background: none;
  border: none;
  color: var(--text-dimmer);
  cursor: pointer;
  font-size: var(--fs18, 18px);
  line-height: 1;
  padding: 2px 4px;
}

/* Date range chips — equal width row */
.fns-date-chips {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 6px;
  padding: 2px 0;
}
.fns-date-chip {
  padding: 8px 4px;
  border-radius: 8px;
  background: var(--surface-panel);
  border: 0.5px solid var(--chrome-border);
  color: var(--text);
  font-size: var(--fs12, 12px);
  font-weight: 600;
  cursor: pointer;
  text-align: center;
  white-space: nowrap;
  user-select: none;
  line-height: 1.1;
}
.fns-date-chip.is-active {
  background: var(--accent-active, var(--accent));
  color: var(--bg);
  border-color: transparent;
}

/* Mode toggle */
.fns-mode-row {
  display: flex;
  background: var(--surface-panel);
  border: 0.5px solid var(--chrome-border);
  border-radius: 12px;
  overflow: hidden;
}
.fns-mode-btn {
  flex: 1;
  padding: 10px 12px;
  font-size: var(--fs13, 13px);
  font-weight: 600;
  border: none;
  cursor: pointer;
  background: transparent;
  color: var(--text);
}
.fns-mode-btn.is-active {
  background: var(--accent-active, var(--accent));
  color: var(--bg);
}

/* Category card grid */
.fns-card-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
}
.fns-card-filler {
  /* empty placeholder when odd number of categories */
}
.fns-card {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  min-height: 56px;
  background: var(--surface-panel);
  border: 0.5px solid var(--chrome-border);
  border-radius: 12px;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: inherit;
}
.fns-card.is-tinted {
  background: var(--cat-bg, var(--bg-2));
}
.fns-card.is-active {
  border-color: var(--cat-color);
  border-width: 1.5px;
  background: var(--cat-bg, var(--bg-2));
}
.fns-card-icon {
  width: 36px;
  height: 36px;
  border-radius: 10px;
  background: var(--cat-icon-bg, rgba(127,127,127,0.15));
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.fns-card-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.fns-card-title {
  font-size: var(--fs14, 14px);
  font-weight: 600;
  color: var(--text);
  line-height: 1.2;
}
.fns-card-meta {
  font-size: var(--fs11, 11px);
  color: var(--text-dim);
  line-height: 1.1;
}
.fns-card-chevron {
  font-size: var(--fs12, 12px);
  color: var(--text-dim);
  display: inline-flex;
  transition: transform 0.2s ease;
}
.fns-card.is-active .fns-card-chevron {
  transform: rotate(180deg);
}

/* Sub panel — spans full grid width below its parent's row */
.fns-sub-panel {
  grid-column: 1 / -1;
  background: var(--surface-panel);
  border: 0.5px solid var(--cat-color, var(--border));
  border-radius: 12px;
  overflow: hidden;
  margin: 2px 0;
}
.fns-sub-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border-bottom: 0.5px solid var(--chrome-border);
}
.fns-sub-header-label {
  flex: 1;
  font-size: var(--fs11, 11px);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text);
}
.fns-sub-action {
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px 8px;
  font-size: var(--fs12, 12px);
  font-weight: 600;
}
.fns-sub-action-all  { color: var(--accent-secondary, var(--accent)); }
.fns-sub-action-none { color: var(--text-dim); }

.fns-sub-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  cursor: pointer;
  border-bottom: 0.5px solid var(--chrome-border);
}
.fns-sub-row:last-child { border-bottom: none; }
.fns-sub-row:hover { background: var(--surface-raised); }
.fns-sub-icon {
  width: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.fns-sub-label {
  flex: 1;
  font-size: var(--fs14, 14px);
  font-weight: 500;
  color: var(--text);
}
.fns-sub-check {
  font-size: var(--fs18, 18px);
  color: var(--text-dimmer);
  line-height: 1;
}
.fns-sub-row.is-on .fns-sub-check { color: var(--accent); }

/* Member panels (Family / Friends / Health) */
.fns-member-panel {
  background: var(--surface-panel);
  border: 0.5px solid var(--chrome-border);
  border-radius: 12px;
  overflow: hidden;
}
.fns-member-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  cursor: pointer;
}
.fns-member-avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: var(--fs14, 14px);
  flex-shrink: 0;
}
.fns-member-name {
  flex: 1;
  font-size: var(--fs14, 14px);
  font-weight: 500;
  color: var(--text);
}
.fns-member-check {
  font-size: var(--fs18, 18px);
  color: var(--text-dimmer);
  line-height: 1;
}
.fns-member-row.is-on .fns-member-check { color: var(--accent); }

.fns-health-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  cursor: pointer;
}
.fns-health-tile {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: rgba(231, 76, 76, 0.15);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.fns-health-info { flex: 1; }
.fns-health-title {
  font-size: var(--fs14, 14px);
  font-weight: 500;
  color: var(--text);
}
.fns-health-sub {
  font-size: var(--fs12, 12px);
  color: var(--text-dim);
  margin-top: 2px;
}
.fns-health-check {
  font-size: var(--fs18, 18px);
  color: var(--text-dimmer);
  line-height: 1;
}
.fns-health-row.is-on .fns-health-check { color: var(--accent-secondary, var(--accent)); }

/* Popover for overflow menus */
.fns-popover {
  position: absolute;
  background: var(--surface-panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.32);
  padding: 6px 0;
  z-index: 1200;
  min-width: 160px;
}
.fns-popover-row {
  padding: 8px 12px;
  cursor: pointer;
  font-size: var(--fs13, 13px);
  color: var(--text);
  border-radius: 6px;
  margin: 0 4px;
}
.fns-popover-row:hover { background: var(--surface-raised); }
.fns-popover-row.is-danger { color: var(--danger, #e74c4c); }

/* Search result rows */
.fns-search-panel {
  background: var(--surface-panel);
  border: 0.5px solid var(--chrome-border);
  border-radius: 12px;
  overflow: hidden;
}
.fns-search-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  cursor: pointer;
}
.fns-search-stripe {
  width: 4px;
  height: 30px;
  border-radius: 2px;
  flex-shrink: 0;
}
.fns-search-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
/* Title + leave badge on one row. Title shrinks first so the badge
   stays visible no matter how long the event title is. */
.fns-search-title-row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.fns-search-title {
  font-size: var(--fs13, 13px);
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
  min-width: 0;
}
.fns-search-row.is-cancelled .fns-search-title {
  text-decoration: line-through;
  opacity: 0.55;
}
.fns-search-subtitle {
  font-size: var(--fs10, 10px);
  color: var(--text-dim);
  line-height: 1.3;
}
.fns-search-icons {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-top: 1px;
}
/* Bold accent month section break with a 1px rule below — replaces
   the prior quiet textDimmer caption. Gives long result lists clear
   vertical anchors per Jeff's 2026-05-26 polish call. */
.fns-search-month-header {
  font-size: var(--fs13, 13px);
  font-weight: 700;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--accent);
  padding: 18px 14px 8px;
  margin: 0;
  border-bottom: 1px solid var(--accent);
  margin-bottom: 4px;
}
.fns-search-month-header.is-first {
  padding-top: 12px;
}

/* Tally summary above the result panel — count + total hours; second
   line surfaces only when a leave-type status filter is active. */
.fns-search-tally {
  padding: 4px 14px 6px;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.fns-search-tally-primary {
  font-size: var(--fs12, 12px);
  font-weight: 600;
  color: var(--text);
}
.fns-search-tally-leave {
  font-size: var(--fs11, 11px);
  color: var(--text-dim);
}
/* Partial-leave subtitle line — only when the leave window is shorter
   than the shift it overlays. */
.fns-search-partial {
  color: var(--text-dim);
  opacity: 0.85;
}
/* Compact uppercase leave-type pill — mirrors iOS LeaveBadge and the
   PTOOverlay timeline convention. Colors source from theme tokens so
   the badge tracks every active theme (light/dark + the ten named
   palettes) without losing contrast against surfacePanel. Each
   variant tints background at 15% and uses the same hue for text. */
.fns-search-leave-badge {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  font-size: var(--fs9, 9px);
  font-weight: 700;
  letter-spacing: 0.5px;
  padding: 2px 5px;
  border-radius: 4px;
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  color: var(--accent);
}
.fns-search-leave-pto         { color: var(--accent-secondary); background: color-mix(in srgb, var(--accent-secondary) 15%, transparent); }
.fns-search-leave-sick        { color: var(--danger);           background: color-mix(in srgb, var(--danger) 15%, transparent); }
.fns-search-leave-lwop        { color: var(--text-dimmer);      background: color-mix(in srgb, var(--text-dimmer) 18%, transparent); }
.fns-search-leave-fmla        { color: var(--accent-active);    background: color-mix(in srgb, var(--accent-active) 15%, transparent); }
.fns-search-leave-bereavement { color: var(--accent-focus);     background: color-mix(in srgb, var(--accent-focus) 15%, transparent); }
.fns-search-leave-jury-duty   { color: var(--accent);           background: color-mix(in srgb, var(--accent) 15%, transparent); }
.fns-search-share {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-raised);
  border-radius: 8px;
  cursor: pointer;
  border: none;
  color: var(--text-dim);
  flex-shrink: 0;
}
.fns-search-loading {
  padding: 8px 14px;
  font-size: var(--fs12, 12px);
  color: var(--text-dim);
}
.fns-search-truncated {
  padding: 6px 14px;
  font-size: var(--fs11, 11px);
  color: var(--text-dim);
}

/* ─── Legacy gyst-popover kept for backward compat ─── */
.gyst-popover {
  position: absolute;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.32);
  padding: 6px 0;
  z-index: 1100;
  min-width: 220px;
  max-width: 320px;
}
.gyst-popover-header {
  display: flex; align-items: center;
  padding: 8px 14px 6px;
  border-bottom: 0.5px solid var(--border);
}
.gyst-popover-header .label {
  flex: 1;
  font-size: var(--fs10, 10px);
  font-weight: 700;
  text-transform: uppercase;
  color: var(--text-dim);
  letter-spacing: 0.04em;
}
.gyst-popover-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 14px;
  cursor: pointer;
  font-size: var(--fs13, 13px);
  color: var(--text);
}
.gyst-popover-row:hover { background: var(--bg-3); }
.gyst-popover-row .check {
  margin-left: auto;
  font-size: var(--fs16, 16px);
  color: var(--text-dimmer);
}
.gyst-popover-row.is-on .check { color: var(--accent); }

/* Filter & Search — Search section */
.gyst-search-field {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: var(--bg-2);
  border: 0.5px solid var(--border);
  border-radius: 12px;
}
.gyst-search-field input {
  flex: 1;
  background: transparent;
  border: none;
  color: var(--text);
  font-size: var(--fs14, 14px);
  outline: none;
}
.gyst-date-range-chips {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  padding: 4px 0;
}
.gyst-date-chip {
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--bg-2);
  color: var(--text);
  font-size: var(--fs12, 12px);
  font-weight: 600;
  cursor: pointer;
  border: 0.5px solid var(--border);
  user-select: none;
}
.gyst-date-chip.is-active {
  background: var(--accent);
  color: var(--bg);
  border-color: transparent;
}
.gyst-search-result-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  cursor: pointer;
}
.gyst-search-result-row .stripe {
  width: 4px;
  height: 30px;
  border-radius: 2px;
  flex-shrink: 0;
}
.gyst-search-result-row .body { flex: 1; min-width: 0; }
.gyst-search-result-row .title {
  font-size: var(--fs13, 13px);
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gyst-search-result-row .subtitle {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs10, 10px);
  color: var(--text-dim);
  margin-top: 2px;
}
.gyst-search-result-row.is-cancelled .title {
  text-decoration: line-through;
  opacity: 0.55;
}
.gyst-search-result-row .share {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-2);
  border-radius: 8px;
  cursor: pointer;
  border: none;
  color: var(--text-dim);
  flex-shrink: 0;
}

/* Active filter count badge on trigger button */
.gyst-count-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 16px;
  min-height: 16px; /* was height — lets the count grow with the type ladder */
  padding: 0 4px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--bg);
  font-size: var(--fs9, 9px);
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

/* ============================================================
   .gyst-up-next-card — Today view Up Next card (iOS parity)
   See ios/Gyst/Views/UpNext/UpNextCardView.swift
   ============================================================ */
.gyst-up-next {
  margin: 8px 0 0;
}
/* iOS puts the "UP NEXT" SectionLabel ABOVE the bordered FormGroupPanel
   (VStack spacing 6), not inside it. Outer margin (16) + 4px label inset
   = 20px, matching iOS .padding(.horizontal, 4) over the 16pt card inset. */
.gyst-up-next .gyst-section-label {
  padding: 0 4px 6px;
}
/* NOTE (2026-07-08): this block originally referenced --color-bg2 /
   --color-border / --color-text-dim etc. — names that do NOT exist in the
   theme system (real names carry no `color-` prefix: --bg-2, --border,
   --text-dim, --surface-panel…). Every var() fell back to nothing, so the
   panel, dividers, category bar, chip pill, and countdown line all rendered
   transparent and the card read as bare floating text. Values below map
   1:1 from UpNextCardView.swift + GystComponents.swift tokens. */
.gyst-up-next-panel {
  /* iOS FormGroupPanel: theme.surfacePanel bg + 1pt chromeBorder at
     panelBorderOpacity 0.55, radius = Tokens.panelRadius 12. */
  background: var(--surface-panel);
  border: 1px solid color-mix(in srgb, var(--chrome-border) 55%, transparent);
  border-radius: 12px;
  overflow: hidden;
}
.gyst-up-next-row {
  display: grid;
  grid-template-columns: 3px 64px 1fr auto;
  gap: 8px;
  align-items: center;
  min-height: 34px;
  padding: 9px 14px; /* Tokens.rowPaddingV / rowPaddingH */
  background: transparent;
  border: 0;
  width: 100%;
  text-align: left;
  cursor: pointer;
  color: inherit;
  font: inherit;
  position: relative;
}
/* Time column width mirrors the iOS Up Next time column (Tokens.scaled(64)
   → 80/96) so the tier-scaled time text (.gyst-up-next-time) doesn't wrap
   inside the fixed 64px column at larger tiers. */
html[data-tl-tier="1"] .gyst-up-next-row { grid-template-columns: 3px 80px 1fr auto; }
html[data-tl-tier="2"] .gyst-up-next-row { grid-template-columns: 3px 96px 1fr auto; }
/* iOS GystUI.RowDivider: 1pt chromeBorder at dividerOpacity 0.30,
   inset from the left by rowPaddingH (14) — not a full-bleed border. */
.gyst-up-next-row + .gyst-up-next-row::before {
  content: '';
  position: absolute;
  top: 0;
  left: 14px;
  right: 0;
  height: 1px;
  background: color-mix(in srgb, var(--chrome-border) 30%, transparent);
}
.gyst-up-next-countdown {
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 0;
  height: 1.5px;
  /* iOS: theme.accentSecondary.opacity(0.55) */
  background: color-mix(in srgb, var(--accent-secondary, var(--accent)) 55%, transparent);
  transform-origin: left center;
  transform: scaleX(var(--countdown-pct, 0));
  transition: transform 1s linear;
  pointer-events: none;
}
.gyst-up-next-bar {
  background: var(--bar-color, var(--border));
  border-radius: 1.5px;
  height: 16px;
  width: 3px;
}
.gyst-up-next-time {
  font-size: var(--fs12, 12px); /* Tokens.rowSubtitleSize, .medium, monospacedDigit */
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
}
/* Title column (1fr grid cell): optional sports crests + the title text in a
   flex row, so crests lead the matchup name (mirrors iOS UpNextRowView). */
.gyst-up-next-title-cell {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0; /* allow the title to ellipsize inside the grid cell */
}
.gyst-up-next-crests {
  flex: none;
  display: inline-flex;
  align-items: center;
}
.gyst-up-next-title {
  font-size: var(--fs14, 14px); /* Tokens.valueSize */
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0; /* ellipsis inside the flex title cell */
}
.gyst-up-next-chip {
  font-size: var(--fs8, 8px); /* Tokens.flairSize, .semibold, monospacedDigit */
  font-weight: 600;
  padding: 2px 6px;
  border-radius: 8px; /* Tokens.chipRadius */
  background: var(--bg-3);
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.gyst-up-next-chip.is-danger {
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  color: var(--danger);
}

/* ============================================================
   .gyst-status-pill — Event Status filter pills
   iOS parity: ios/Gyst/Views/Calendar/FilterAndSearch/StatusPillRow.swift
   ============================================================ */
.gyst-status-pill-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
  padding: 10px 12px;
  background: var(--bg-2);
  border: 0.5px solid color-mix(in srgb, var(--border) 55%, transparent);
  border-radius: 12px;
}
.gyst-status-pill {
  display: inline-flex;
  align-items: center;
  height: 28px;
  padding: 0 10px;
  font-size: var(--fs13, 13px);
  font-weight: 600;
  letter-spacing: 0.02em;
  border-radius: 8px;
  background: var(--bg-3);
  color: var(--text);
  border: 0.5px solid color-mix(in srgb, var(--border) 40%, transparent);
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
  font-family: inherit;
}
.gyst-status-pill:hover {
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-3));
}
.gyst-status-pill.is-active {
  /* iOS StatusPillRow: isOn fills theme.accentActive with theme.background
     text (StatusPillRow.swift:56-66) — not the base accent. */
  background: var(--accent-active);
  color: var(--bg);
  border-color: var(--accent-active);
}

/* ============================================================
   .gyst-fns-action-bar — Filter & Search bottom action bar
   iOS parity: ios/Gyst/Views/Calendar/FilterAndSearch/ActionBar.swift
   ============================================================ */
.gyst-fns-action-bar {
  padding: 12px 0 4px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.gyst-fns-action-buttons {
  display: flex;
  gap: 8px;
}
.gyst-fns-filter-btn,
.gyst-fns-search-btn {
  flex: 1;
  height: 44px;
  border-radius: 12px;
  font-size: var(--fs15, 15px);
  font-weight: 600;
  letter-spacing: 0.02em;
  cursor: pointer;
  font-family: inherit;
  transition: background 120ms ease, color 120ms ease;
}
.gyst-fns-filter-btn {
  background: var(--accent);
  color: var(--accent-active-text, var(--bg-1, #fff));
  border: 0;
}
.gyst-fns-filter-btn:hover {
  filter: brightness(0.95);
}
.gyst-fns-search-btn {
  background: transparent;
  color: var(--text);
  border: 0.5px solid color-mix(in srgb, var(--border) 55%, transparent);
}
.gyst-fns-search-btn:hover {
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}

/* =============================================================
   .gyst-disposition-panel — inline "what happened?" panel that
   expands under a Gift Ideas (for me) item row on check-off.
   Composes existing GystUI components (section label, panel,
   menu-option rows, text field, toggle, inline date picker) plus
   a Cancel / Done footer (gyst-btn-secondary / gyst-btn-primary).
   This block adds layout/spacing only — no new colors.
   ============================================================= */
.gyst-disposition-panel {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 0.5px solid var(--border);
  cursor: default;
}
.gyst-disposition-panel .gyst-section-label {
  margin: 0 0 2px;
}
.gyst-disposition-detail {
  display: flex;
  flex-direction: column;
}
.gyst-disposition-detail:empty {
  display: none;
}
/* Thank-you toggle dims + disables until a giver name is entered. */
.gyst-disposition-panel .gyst-toggle-row.is-disabled {
  opacity: 0.45;
  pointer-events: none;
}
.gyst-disposition-footer {
  display: flex;
  gap: 8px;
  margin-top: 4px;
}
.gyst-disposition-footer > .gyst-btn-secondary,
.gyst-disposition-footer > .gyst-btn-primary {
  flex: 1;
}

/* =============================================================
   .gift-history-section — received-history list under a Gift
   Ideas (for me) list. Reuses .books-completed-section's framing
   (top border + spacing). Each .gift-history-row shows the gift
   title, an outcome/date sub-label, and Edit / Undo icon buttons.
   Layout/spacing only — no new colors.
   ============================================================= */
.gift-history-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 0;
  border-bottom: 0.5px solid var(--border-dim);
}
.gift-history-row:last-child { border-bottom: none; }
.gift-history-main {
  flex: 1;
  min-width: 0;
}
.gift-history-title {
  font-size: var(--fs14, 14px);
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gift-history-sub {
  font-size: var(--fs12, 12px);
  color: var(--text-dim);
  margin-top: 2px;
}
.gift-history-actions {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}
.gift-history-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: none;
  background: transparent;
  color: var(--text-dim);
  font-size: var(--fs15, 15px);
  border-radius: 8px;
  cursor: pointer;
}
.gift-history-btn:hover {
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  color: var(--text);
}
/* When an edit panel expands inside a history row, stack vertically. */
.gift-history-row.disposition-open {
  flex-direction: column;
  align-items: stretch;
}

/* ── SheetHeroHeader — identity header for blueprint-editing sheets ─────
   Web mirror of iOS GystUI.SheetHeroHeader (first consumer: starter-set
   editor). Tint rides --list-template-color; the :where() default keeps
   zero specificity so per-template [data-template] rules always win. */
:where(.gyst-sheet-hero) { --list-template-color: #8C8C99; }
/* Keyed off the app's own mode switch (:root[data-mode], set by
   theme-loader.js) — NOT prefers-color-scheme, which reflects the OS
   setting and can disagree with the active GYST theme. Both halves stay
   inside :where() so specificity remains zero and per-template
   [data-template] rules always win (the original contract). */
:where(:root[data-mode="dark"]) :where(.gyst-sheet-hero) { --list-template-color: #A6A6B3; }
.gyst-sheet-hero {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 4px 2px 0;
}
.gyst-sheet-hero-top {
  display: flex;
  align-items: center;
  gap: 12px;
}
.gyst-sheet-hero-tile {
  width: 48px;
  height: 48px;
  flex: 0 0 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs24, 24px);
  border-radius: 10px;
  background: color-mix(in srgb, var(--list-template-color) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--list-template-color) 35%, transparent);
}
.gyst-sheet-hero-title {
  font-size: var(--fs22, 22px);
  font-weight: 700;
  color: var(--text);
  line-height: 1.15;
}
.gyst-sheet-hero-badge {
  display: inline-block;
  margin-top: 5px;
  padding: 3px 7px;
  font-size: var(--fs9, 9px);
  font-weight: 700;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  color: var(--list-template-color);
  border-radius: 8px;
  background: color-mix(in srgb, var(--list-template-color) 12%, transparent);
}
.gyst-sheet-hero-subtitle {
  font-size: var(--fs13, 13px);
  color: var(--text-dim);
  line-height: 1.45;
}

/* =============================================================
   MEMBER BUBBLE / MEMBER STACK (shared lists)
   iOS analog: GystUI.MemberBubble + GystUI.MemberBubbleStack in
   GystComponents.swift (a07153c3). The fill is the SERVER-resolved
   viewer-relative member color (#rrggbb); no color (the viewer
   themself) falls back to the accent.
   ============================================================= */
.gyst-member-bubble {
  --bubble-size: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--bubble-size);
  height: var(--bubble-size);
  border-radius: 50%;
  background: var(--bubble-color, var(--accent));
  color: #fff; /* deliberate: fill is a per-member DATA color, not a theme token */
  font-size: calc(var(--bubble-size) * 0.45);
  font-weight: 700;
  line-height: 1;
  flex-shrink: 0;
  user-select: none;
}

.gyst-member-stack {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
}
/* Background-colored ring so the overlap reads as separate people */
.gyst-member-stack .gyst-member-bubble {
  box-shadow: 0 0 0 1.5px var(--bg);
}
.gyst-member-stack .gyst-member-bubble + .gyst-member-bubble {
  margin-left: calc(var(--bubble-size) * -0.3);
}

.gyst-member-overflow {
  background: var(--bg-3);
  color: var(--text-dim);
  font-size: calc(var(--bubble-size) * 0.42);
}

/* ──────────────────────────────────────────────────────────
   List category section header (spec 2026-06-16-optional-list-categories)
   iOS analog: GystUI.ListCategoryHeader
   ────────────────────────────────────────────────────────── */
.gyst-list-cat-header {
  display: flex;
  align-items: center;
  gap: var(--gyst-row-gap);
  min-height: 40px;
  padding: 4px 2px 0;
  cursor: pointer;
  user-select: none;
}
.gyst-list-cat-header__caret {
  flex: 0 0 auto;
  width: 20px; height: 20px;
  display: grid; place-items: center;
  color: var(--text-dim);
  transition: transform 0.18s ease;
}
.gyst-list-cat-header.collapsed .gyst-list-cat-header__caret { transform: rotate(-90deg); }
.gyst-list-cat-header__name {
  font-size: var(--fs11, 11px); font-weight: 700; letter-spacing: 1px;
  text-transform: uppercase; color: var(--text-dim);
}
.gyst-list-cat-header__spacer { flex: 1 1 auto; }
.gyst-list-cat-header__checkall {
  flex: 0 0 auto; width: 20px; height: 20px; border-radius: 6px;
  border: 2px solid color-mix(in srgb, var(--text-dimmer) 60%, transparent);
  display: grid; place-items: center; box-sizing: border-box;
  background: transparent; color: var(--bg); cursor: pointer;
  font-size: var(--fs12, 12px); line-height: 1;
}
.gyst-list-cat-header__checkall.all,
.gyst-list-cat-header__checkall.some {
  border-color: transparent;
  background: var(--accent-active, var(--accent));
}
.gyst-list-cat-header__count {
  font-size: var(--fs10, 10px); font-weight: 500; color: var(--text-dimmer);
  font-variant-numeric: tabular-nums;
}
.gyst-list-cat-header__menu {
  flex: 0 0 auto; width: 28px; height: 28px;
  display: grid; place-items: center;
  color: var(--text-dim); background: transparent; border: 0; cursor: pointer;
  font-size: var(--fs15, 15px); line-height: 1;
}
.gyst-list-cat-section { margin-top: 6px; }
.gyst-list-cat-hairline {
  height: 2px; border-radius: 999px; margin: 4px 2px 2px;
  background: color-mix(in srgb, var(--border) 60%, transparent);
  overflow: hidden;
}
.gyst-list-cat-hairline > span {
  display: block; height: 100%;
  background: var(--accent-active, var(--accent));
  border-radius: 999px; transition: width 0.2s ease;
}
.gyst-list-cat-items.collapsed { display: none; }

/* Add-bar category chooser (custom-category lists) + inline new-category row.
   No native <select>; a hidden carrier (#listAddItemCategoryId) holds the id. */
.list-detail-add-cat-row {
  display: flex; align-items: center; gap: 8px;
}
.list-detail-add-cat-chooser {
  flex: 1 1 auto; display: flex; align-items: center; gap: 6px;
  min-height: 32px; padding: 4px 10px; box-sizing: border-box;
  background: var(--surface-2, var(--card)); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px;
  font-size: var(--fs13, 13px); cursor: pointer; text-align: left;
}
.list-detail-add-cat-label { flex: 1 1 auto; }
.list-detail-add-cat-caret { color: var(--text-dim); font-size: var(--fs10, 10px); }
.list-detail-add-cat-menu {
  display: flex; flex-direction: column;
  margin-top: 4px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface-2, var(--card)); overflow: hidden;
}
.list-detail-add-cat-opt {
  padding: 8px 12px; background: transparent; color: var(--text);
  border: 0; text-align: left; font-size: var(--fs13, 13px); cursor: pointer;
}
.list-detail-add-cat-opt:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.list-detail-add-cat-opt.is-selected { color: var(--accent); font-weight: 600; }
.list-detail-add-cat-opt.is-new {
  color: var(--accent); font-weight: 600;
  border-top: 1px solid var(--border);
}
.list-detail-add-cat-new-row {
  display: flex; align-items: center; gap: 6px; margin-top: 4px;
}
.list-detail-add-cat-new-save,
.list-detail-add-cat-new-cancel {
  flex: 0 0 auto; padding: 4px 10px; min-height: 32px;
  border: 1px solid var(--border); border-radius: 8px;
  font-size: var(--fs13, 13px); font-weight: 600; cursor: pointer; background: transparent;
}
.list-detail-add-cat-new-save { color: var(--accent); }
.list-detail-add-cat-new-cancel { color: var(--text-dim); }

/* Per-category overflow menu (Rename / Move up / Move down / Delete) — inline,
   absolutely positioned beside the ⋯ button. No native confirm()/prompt(). */
.list-cat-menu-pop {
  position: absolute; right: 2px; top: 32px; z-index: 30;
  display: flex; flex-direction: column; min-width: 160px;
  background: var(--surface-2, var(--card)); color: var(--text);
  border: 1px solid var(--border); border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.28); overflow: hidden; padding: 4px;
}
.list-cat-menu-opt {
  padding: 8px 10px; background: transparent; color: var(--text);
  border: 0; border-radius: 6px; text-align: left; font-size: var(--fs13, 13px); cursor: pointer;
}
.list-cat-menu-opt:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.list-cat-menu-opt.danger { color: var(--danger, #e5484d); }
.list-cat-menu-rename-row { display: flex; align-items: center; gap: 6px; margin: 4px; }
.list-cat-menu-rename { flex: 1 1 auto; min-width: 0; margin: 0; }
.list-cat-menu-rename-save {
  flex: 0 0 auto; width: 32px; height: 32px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent); color: var(--accent-active-text, #fff); border: 0; border-radius: 8px;
  font-size: var(--fs15, 15px); font-weight: 700; cursor: pointer;
}
.list-cat-menu-confirm {
  padding: 6px 10px; font-size: var(--fs12, 12px); color: var(--text-dim);
}

/* ============================================================
   SLEEP HISTORY DURATION BAR (iOS GystUI.SleepHistoryBar)
   Track + fill bar inside a Sleep History row.
   Width of __fill is set inline as a percentage by the JS
   builder (proportion of night's duration vs month's longest).
   ============================================================ */
/* Sleep History duration bar — length ∝ night duration vs month's longest. */
.gyst-sleep-history-bar {
  flex: 1;
  height: 10px;
  border-radius: 5px;
  background: var(--surface-panel, var(--bg-2));
  overflow: hidden;
}
.gyst-sleep-history-bar__fill {
  height: 100%;
  min-width: 10px;
  border-radius: 5px;
  background: var(--accent);
}

/* ============================================================
   WORK SCHEDULE MULTI-SHIFT EDITOR
   Used by the Profile work-schedule builder (Standard, Biweekly,
   Rotating). Each enabled day has a .ws-times-col containing a
   list of shift rows plus an "add shift" button and an optional
   read-only carryover line.
   ============================================================ */

.ws-times-col {
  display: flex;
  flex-direction: column;
  gap: 4px;
  transition: opacity 0.15s;
}

.ws-shift-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* One shift entry: [start] to [end] [overnight chip] [warn] [×] */
.ws-shift-row {
  display: flex;
  align-items: center;
  gap: 5px;
  flex-wrap: nowrap;
}

/* Time inputs inside a shift row — inherit profile.css input sizing */
.ws-shift-row input[type="time"] {
  flex: 1;
  min-width: 0;
  padding: 5px 7px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-size: var(--fs13, 13px);
  font-family: var(--mono);
  color-scheme: dark;
}

.ws-to-label {
  font-size: var(--fs11, 11px);
  color: var(--text-dimmer);
  font-weight: 500;
  flex-shrink: 0;
}

/* "ends Xa tomorrow" chip — replaces old "+1d" pill */
.ws-shift-overnight {
  display: none;
  font-size: var(--fs10, 10px);
  font-weight: 700;
  letter-spacing: 0.3px;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  padding: 2px 7px;
  border-radius: 6px;
  white-space: nowrap;
  flex-shrink: 0;
}

/* ⚠ overlap / ≥24h inline warn — muted, non-blocking */
.ws-shift-warn {
  font-size: var(--fs11, 11px);
  color: var(--text-dimmer);
  flex-shrink: 0;
  cursor: default;
}

/* × remove-shift button */
.ws-shift-remove {
  background: none;
  border: none;
  color: var(--text-dimmer);
  font-size: var(--fs15, 15px);
  line-height: 1;
  padding: 2px 4px;
  border-radius: 4px;
  cursor: pointer;
  flex-shrink: 0;
  transition: color 0.12s;
}
.ws-shift-remove:hover {
  color: var(--text);
}

/* "+ Add shift" button */
.ws-add-shift {
  background: none;
  border: none;
  color: var(--accent);
  font-size: var(--fs12, 12px);
  font-weight: 600;
  padding: 3px 0;
  cursor: pointer;
  text-align: left;
  transition: opacity 0.12s;
}
.ws-add-shift:hover {
  opacity: 0.75;
}

/* Read-only "↳ from PrevDay  12:00a–Xa" carryover note */
.ws-carryover {
  font-size: var(--fs11, 11px);
  color: var(--text-dimmer);
  font-style: italic;
  padding: 1px 0 2px;
  user-select: none;
}

/* ============================================================
   AMBIENT STYLE PICKER — live preview cards
   Rendered by profile.js buildAmbientStyleRow() directly under the
   Ambient depth toggle, only while ambient depth is on. iOS parity:
   the horizontal row of 92×118 live preview cards below the Ambient
   toggle in ProfileView. Each card runs a real mini-instance of its
   recipe: CSS styles (liquid/aurora/breathe) mount a scaled 390×560
   wrapper; canvas styles (drift/ink) paint into a <canvas> driven by
   window.gystAmbientPreview. Selection vocabulary mirrors the theme
   picker's .theme-card.selected (2px accent ring + glow).
   ============================================================ */
.gyst-ambient-style-row {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  padding: 12px 2px 4px;
  margin-top: 4px;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.gyst-ambient-style-row::-webkit-scrollbar { display: none; }

.gyst-ambient-card {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  font-family: inherit;
  -webkit-tap-highlight-color: transparent;
}

.gyst-ambient-card-stage {
  position: relative;
  width: 92px;
  height: 118px;
  border-radius: 14px;
  overflow: hidden;
  background: var(--bg);
  border: 1px solid var(--border);
  transition: box-shadow .18s ease, border-color .18s ease, transform .18s ease;
}
.gyst-ambient-card:hover .gyst-ambient-card-stage { transform: translateY(-1px); }
.gyst-ambient-card.selected .gyst-ambient-card-stage {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent), 0 4px 16px var(--accent-glow, color-mix(in srgb, var(--accent) 20%, transparent));
}
.gyst-ambient-card:focus-visible .gyst-ambient-card-stage {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.gyst-ambient-card-name {
  font-size: var(--fs12, 12px);
  font-weight: 600;
  color: var(--text-dimmer);
  line-height: 1.2;
  white-space: nowrap;
}
.gyst-ambient-card.selected .gyst-ambient-card-name { color: var(--text); }

/* Scaled 390×560 recipe instance — the recipe's vmax/vw units resolve
   against the viewport, so this is a light impression rather than a
   pixel-exact miniature (matches the brief). scale 0.236 fills 92px wide;
   overflow:hidden on the stage crops the taller-than-118 result. */
.gyst-ambient-preview-instance {
  position: absolute;
  top: 0; left: 0;
  width: 390px;
  height: 560px;
  transform: scale(0.236);
  transform-origin: top left;
  pointer-events: none;
}
/* Force the inner style wrapper visible regardless of the global
   data-ambient-style attribute (the #ambientLayer-scoped visibility
   switch doesn't reach here). */
.gyst-ambient-preview [class^="ambient-style-"] {
  position: absolute;
  inset: 0;
  opacity: 1;
  pointer-events: none;
}

.gyst-ambient-preview-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.gyst-ambient-preview-canvas.is-ink { filter: blur(3px); }

/* =============================================================
   BACKGROUND PICKER (2026-07-21) — GystUI.BackgroundTile + RecolorRow
   Large 9:16 background-preview tiles (per-theme graded stills) + a
   collapsed "Recolor" grade-swatch reveal. iOS parity: GystComponents.swift
   BackgroundTile / RecolorRow.
   ============================================================= */
#ambientStyleRow { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
.gyst-bg-tile {
  display: flex; flex-direction: column; gap: 7px;
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  text-align: left; color: inherit; font: inherit; width: 100%;
}
.gyst-bg-tile-frame {
  position: relative; aspect-ratio: 9 / 16; border-radius: 12px; overflow: hidden;
  background: var(--bg-3) center / cover no-repeat;
  border: 1px solid var(--border);
  transition: border-color .2s ease;
}
.gyst-bg-tile.is-selected .gyst-bg-tile-frame { border: 2.5px solid var(--accent); }
.gyst-bg-tile-check {
  position: absolute; top: 6px; right: 6px; display: none;
  color: var(--accent); font-size: var(--fs22, 22px); line-height: 1;
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, .3));
}
.gyst-bg-tile.is-selected .gyst-bg-tile-check { display: block; }
.gyst-bg-tile-name {
  font-size: var(--fs12, 12px); color: var(--text-dim); padding-left: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gyst-bg-tile.is-selected .gyst-bg-tile-name { color: var(--text); font-weight: 600; }

.gyst-bg-grade-chip {
  flex: none; scroll-snap-align: start;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  width: 60px; background: none; border: 0; padding: 0; cursor: pointer;
  color: inherit; font: inherit;
}
.gyst-bg-grade-chip-thumb {
  position: relative; width: 48px; height: 68px; border-radius: 9px;
  background: var(--bg-3) center / cover no-repeat;
  border: 1px solid var(--border); transition: border-color .18s ease;
}
.gyst-bg-grade-chip.is-selected .gyst-bg-grade-chip-thumb { border: 2.5px solid var(--accent); }
.gyst-bg-grade-chip-check {
  position: absolute; top: -6px; right: -6px; display: none;
  width: 16px; height: 16px; border-radius: 50%;
  background: #fff; /* deliberate: badge overlays the thumbnail IMAGE, not a themed surface */
  color: var(--accent); font-size: var(--fs12, 12px); line-height: 16px; text-align: center;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .3);
}
.gyst-bg-grade-chip.is-selected .gyst-bg-grade-chip-check { display: block; }
.gyst-bg-grade-auto-badge {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: var(--fs17, 17px); filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .55));
}
.gyst-bg-grade-chip-name {
  font-size: var(--fs10_5, 10.5px); color: var(--text-dim);
  white-space: nowrap; max-width: 60px; overflow: hidden; text-overflow: ellipsis;
}
.gyst-bg-grade-chip.is-selected .gyst-bg-grade-chip-name { color: var(--text); font-weight: 600; }

/* Row/option geometry that iOS scales through GystUI.Tokens and web had
   pinned (round 5): the leading icon column, and the MenuRow option rows'
   height + vertical padding. Without these the icon column and option rows
   stayed phone-sized inside rows that had grown. */
html[data-tl-tier="1"] { --gyst-row-icon-w: 25px; --gyst-option-min-h: 48px; --gyst-option-py: 11px; }
html[data-tl-tier="2"] { --gyst-row-icon-w: 30px; --gyst-option-min-h: 57px; --gyst-option-py: 14px; }
html[data-tl-tier="1"] .gyst-row-icon { width: 25px; height: 25px; }
html[data-tl-tier="2"] .gyst-row-icon { width: 30px; height: 30px; }

/* ───────── CHOICE PANEL (iOS GystUI.InlineChoicePanel) — 2026-09-25 ─────────
   Inline confirmation with N choices + Cancel; replaces native confirm().
   Neutral by default (consent-style questions); .is-destructive for deletes. */
.gyst-choice-panel {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 8px;
  padding: 12px var(--gyst-row-px, 16px) 6px;
  background: var(--surface-panel, var(--bg-2));
  border: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  border-radius: var(--gyst-nested-radius, 12px);
}
.gyst-choice-panel.is-destructive {
  background: color-mix(in srgb, var(--danger) 8%, var(--bg-2));
  border-color: color-mix(in srgb, var(--danger) 30%, transparent);
}
.gyst-choice-panel-message {
  margin: 0 0 6px;
  font-size: var(--fs13, 13px);
  line-height: 1.4;
  color: var(--text-dim);
}
.gyst-choice-panel-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 2.75em;
  padding: 0 4px;
  background: transparent;
  border: 0;
  border-top: 1px solid var(--gyst-row-divider, color-mix(in srgb, var(--border) 40%, transparent));
  color: var(--accent-secondary, var(--accent));
  font-family: inherit;
  font-size: var(--fs14, 14px);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.gyst-choice-panel.is-destructive .gyst-choice-panel-row { color: var(--danger); }
.gyst-choice-panel-row.is-cancel { color: var(--text-dim); font-weight: 500; }
.gyst-choice-panel-row:focus-visible { outline: 2px solid var(--accent-secondary, var(--accent)); outline-offset: -2px; }
/* A choice with a subtitle: label over a dim line (iOS Choice.subtitle). */
.gyst-choice-panel-text { display: flex; flex-direction: column; gap: 2px; padding: 8px 0; min-width: 0; }
.gyst-choice-panel-sub { font-size: var(--fs12, 12px); font-weight: 400; line-height: 1.35; color: var(--text-dim); }

/* ───────── GATE PANEL (iOS ElevationGatePanel) — promoted 2026-09-30 ─────────
   The verification panel every elevation gate renders — Money's step-up,
   app protection's unlock, Security's inline fresh-proof, and the delete /
   withdraw checks: a lock header, an inline failure line, the factor
   buttons, the code field and the quiet text links. It lived in
   financial.css as .money-gate-* and Security borrowed it (GystUI Lead
   7.24); it is shared now. JS: GystUI.gatePanel() / GystUI.gateHeader().

   The accent is the one thing callers vary, exactly like iOS
   ElevationGatePanel's `accent:` — theme accent by default (app unlock,
   Security), .is-danger for delete / withdraw, and Money's finance colour
   inherited from .money-section's --gyst-gate-accent (financial.css). The
   panel never sets the variable itself (that would block the inherited
   value); every use falls back to --accent. The lock tile
   is iOS's: a nested-radius square with a 12% fill and a 30% stroke, and
   both the box and the glyph ride the type ladder (design/00 pitfall #2). */
.gyst-gate {
  width: 100%;
  max-width: 360px;
  padding: 28px 24px 24px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 16px;
}
.gyst-gate.is-danger { --gyst-gate-accent: var(--danger); }
.gyst-gate-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  text-align: center;
}
.gyst-gate-wordmark {
  display: block;
  width: min(244px, 100%);
  height: auto;
  aspect-ratio: 320 / 108;
  object-fit: contain;
  user-select: none;
  -webkit-user-drag: none;
}
.gyst-gate-icon {
  font-size: var(--fs20, 20px);            /* the glyph: iOS scaled(20) */
  width: 2.4em;                            /* the box: iOS scaled(48) */
  height: 2.4em;
  box-sizing: border-box;
  border-radius: var(--gyst-nested-radius);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--gyst-gate-accent, var(--accent)) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--gyst-gate-accent, var(--accent)) 30%, transparent);
  color: var(--gyst-gate-accent, var(--accent));
}
.gyst-gate-icon svg,
.gyst-gate-icon i { width: 1em; height: 1em; }
.gyst-gate-title {
  font-family: var(--gyst-font-stack);
  font-size: var(--fs17, 17px);
  font-weight: 700;
  color: var(--text);
}
.gyst-gate-subtitle {
  font-family: var(--gyst-font-stack);
  font-size: var(--fs13, 13px);
  color: var(--text-dim);
  line-height: 1.4;
  max-width: 280px;
}
/* Inline error/info line — never a toast: the panel has no "content behind
   it" for a toast to interrupt. */
.gyst-gate-message {
  font-family: var(--gyst-font-stack);
  font-size: var(--fs12_5, 12.5px);
  color: var(--danger);
  text-align: center;
  line-height: 1.4;
}
.gyst-gate-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.gyst-gate-btn { width: 100%; }
.gyst-gate-otp-input {
  width: 100%;
  box-sizing: border-box;
  padding: 12px 14px;
  border-radius: var(--gyst-nested-radius);
  border: 1px solid var(--border);
  background: var(--bg-3);
  color: var(--text);
  font-family: var(--gyst-font-stack);
  font-size: var(--fs20, 20px);
  font-weight: 600;
  text-align: center;
  /* em, so the digit spacing scales with the tiered font-size (0.2em × 20px
     = 4px at tier 0). */
  letter-spacing: 0.2em;
  font-variant-numeric: tabular-nums;
  outline: none;
}
.gyst-gate-otp-input::placeholder {
  color: var(--text-dimmer);
  letter-spacing: 0;
  font-weight: 400;
  font-size: var(--fs14, 14px);
}
.gyst-gate-otp-input:focus { border-color: var(--gyst-gate-accent, var(--accent)); }
/* Quiet text links under the code view — "Use passkey instead" / "Resend
   code" / "Cancel" — as <button>s. */
.gyst-gate-links {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  margin-top: 2px;
}
.gyst-gate-textlink {
  background: transparent;
  border: none;
  padding: 0;
  font-family: var(--gyst-font-stack);
  font-size: var(--fs12_5, 12.5px);
  font-weight: 600;
  color: var(--gyst-gate-accent, var(--accent));
  cursor: pointer;
  font-variant-numeric: tabular-nums;
}
.gyst-gate-textlink:hover:not(:disabled) { text-decoration: underline; }
.gyst-gate-textlink:disabled { color: var(--text-dimmer); cursor: default; }
/* Reserve the hit area in vertical link stacks so neighboring actions do
   not intercept each other's taps. */
.gyst-gate-links > .gyst-tap-floor {
  min-width: var(--gyst-tap-floor);
  min-height: var(--gyst-tap-floor);
}

/* ───────── NOTICE ROW (iOS GystUI.NoticeRow) — 2026-09-29 ─────────
   A flush row inside a .gyst-panel: what's keeping a feature from working,
   plus the one action that fixes it. .is-attention (default) washes the row
   in the DangerRow tint — blocked-permission states are danger per the
   master rules; .is-info is the neutral variant. Nested inside a panel, so
   it never frosts itself (ambient nesting rule). Type + geometry come off
   the tier ladder; the action's visible height tracks the row height and its
   hit area is padded out to the 44px floor. */
.gyst-notice-row {
  display: flex;
  align-items: center;
  gap: var(--gyst-row-gap);
  min-height: var(--gyst-row-min-h);
  padding: var(--gyst-row-py) var(--gyst-row-px);
  box-sizing: border-box;
  position: relative;
  font-family: var(--font);
  color: var(--text);
  background: color-mix(in srgb, var(--danger) var(--gyst-danger-wash), transparent);
}
.gyst-notice-row.is-info { background: transparent; }
.gyst-notice-row__icon {
  flex: 0 0 auto;
  width: var(--gyst-row-icon-w, 20px);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--danger);
}
.gyst-notice-row.is-info .gyst-notice-row__icon { color: var(--accent-secondary, var(--accent)); }
.gyst-notice-row__icon svg,
.gyst-notice-row__icon i {
  width: var(--gyst-row-icon-size);
  height: var(--gyst-row-icon-size);
}
.gyst-notice-row__body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--gyst-row-subtitle-gap);
}
.gyst-notice-row__title {
  font-size: var(--fs15, 15px);
  font-weight: 600;
  color: var(--text);
  line-height: 1.25;
}
.gyst-notice-row__message {
  font-size: var(--fs12, 12px);
  color: var(--text-dim);
  line-height: 1.35;
}
.gyst-notice-row__action {
  flex: 0 0 auto;
  position: relative;
  min-height: var(--gyst-chip-h); /* iOS NoticeRow action: frame(height: chipHeight) */
  padding: 0 var(--gyst-row-gap); /* 12/15/18 — iOS chipPaddingH scaled */
  border: 0;
  border-radius: var(--gyst-nested-radius, 10px);
  background: color-mix(in srgb, var(--accent-secondary, var(--accent)) 12%, transparent);
  color: var(--accent-secondary, var(--accent));
  font-family: inherit;
  font-size: var(--fs13, 13px);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.12s;
}
.gyst-notice-row__action::after {
  /* A fixed 44px hit area centred on the chip-height button — the tap
     floor is ergonomics, never tier-scaled, so it can't fall under 44px
     at the compact tier (the button is 32px there) or grow with it. */
  content: "";
  position: absolute;
  top: 50%;
  height: 44px;
  transform: translateY(-50%);
  left: -4px;
  right: -4px;
}
.gyst-notice-row__action:hover {
  background: color-mix(in srgb, var(--accent-secondary, var(--accent)) 20%, transparent);
}
.gyst-notice-row__action:focus-visible {
  outline: 2px solid var(--accent-secondary, var(--accent));
  outline-offset: 2px;
}
.gyst-notice-row__action:disabled { opacity: 0.5; cursor: default; }
/* A notice slot directly under another row in the same panel (the habit /
   routine "Remind me at" row) gets a row divider above the notice — only
   while the notice shows. iOS: NotificationsOffNotice + .noticeLeadingDivider()
   → GystUI.RowDivider (1pt, inset rowPaddingH, chromeBorder at dividerOpacity). */
.gyst-notify-host.is-divided > .gyst-notify-slot:not(:empty)::before {
  content: "";
  display: block;
  height: 1px;
  margin-left: var(--gyst-row-px);
  background: var(--gyst-row-divider);
}
/* The editors' reminder lists (.reminder-list, modal.css) cap at 120px and
   scroll — a notice at the top of one would push the reminder it warns
   about out of view (and at tier 2 get cut off itself). While a notice is
   showing, the list takes its natural height. Progressive enhancement only
   (GystUI Lead, 2026-09-29): without :has() the list keeps modal.css's cap
   and overflow-y:auto, so it still scrolls to every reminder; and the
   selector stays scoped to .reminder-list — never body or anything global.
   test/gystui-followups.test.js pins both. */
.reminder-list:has(> .gyst-notify-slot .gyst-notice-row) { max-height: none; }

/* ── Tap floor (iOS GystUI.TapFloorShape / .gystTapFloor()) ─────────────
   The 44×44 hit-area floor (design/00: ergonomics, never tier-scaled) for a
   control that LOOKS smaller — a text button in an action row, a 26–32px
   icon button. The control keeps its box, its look and its place; an
   invisible ::after centred on it catches taps within 22px of its centre.
   A dimension already ≥44px keeps its size (max(100%, 44px)). The host
   becomes position: relative, so don't put it on a control that is itself
   absolutely positioned or already draws an ::after. */
.gyst-tap-floor { position: relative; }
.gyst-tap-floor::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: max(100%, 44px);
  height: max(100%, 44px);
  transform: translate(-50%, -50%);
}
