/* global variables: not updating on hot reload, need to refresh the browser with CTRL+F5 */
:root {
    /* Fonts */
    --font-1: Inter;
    --font-2: Outfit;
    /* ── Font weights — Inter-standard 5-step scale (Phase 0.4, 2026-05-25).
       Outfit ships only 400/500/600 — using --font-weight-light (300) or
       --font-weight-bold (700) on an Outfit site triggers browser font
       synthesis (visible quality regression). Document per site if needed. */
    --font-weight-light:    300;
    --font-weight-regular:  400;
    --font-weight-medium:   500;
    --font-weight-semibold: 600;
    --font-weight-bold:     700;
    /* Font Sizes — 9-step scale + 2 named exceptions. Phase 1C value revisions
       landed 2026-05-25: xs 0.7→0.75, sm 0.82→0.875, md 0.9→1.000, lg 1.2→1.125,
       xl 1.4→1.250. Retired --font-size-base (callsites now use --font-size-md)
       and --font-size-smd (now use --font-size-sm). --font-size-6xl / -7xl remain
       pending the global h1 rule decision (Phase 2). */
    --font-size-xs:               0.750rem; /* 12px  — metadata, badges, tooltips */
    --font-size-sm:               0.875rem; /* 14px  — secondary text, labels, footer */
    --font-size-md:               1.000rem; /* 16px  — body default */
    --font-size-lg:               1.125rem; /* 18px  — emphasized body, list-item primary */
    --font-size-xl:               1.250rem; /* 20px  — modal titles, card titles */
    --font-size-2xl:              1.500rem; /* 24px  — section headings */
    --font-size-3xl:              2.000rem; /* 32px  — page h1 */
    --font-size-4xl:              3.000rem; /* 48px  — display / hero secondary */
    --font-size-5xl:              5.000rem; /* 80px  — hero */
    --font-size-display:          6.000rem; /* 96px — home hero wordmark only (named exception; 2026-05-29 desktop-review pass dropped from 9.775rem/156.4px after side-by-side audit against ChatGPT/Vercel/Linear/Notion/Stripe split-layout auth pages — all reference wordmarks live in 24–40px range, our prior 156px read as splash-page / 2010s register; 96px is the confident-hero floor that still keeps the body content's hierarchy intact) */
    --font-size-loading-wordmark: 1.750rem; /* 28px  — first-load wordmark only (named exception) */
    --font-size-wordmark-mobile:  clamp(2.5rem, calc(1.75rem + 3.75vw), 4rem); /* 40–64px fluid — Home mobile wordmark @media (max-width: 960px), named exception (Phase 2, fluid scaling 2026-05-27 evening; 2026-05-28: re-anchored to reference-aligned Option B endpoints — 40px at 320 css px, 64px at 960 css px — after audit against ChatGPT/Vercel/Apple HIG/Linear refs showed prior 46–92px curve sat above all four reference ceilings) */
    --font-size-badge:            0.620rem; /* ~10px — ConnectionStatusPill notification badge, named exception (no token below xs=12) */
    /* ── Icon sizes — SPECIAL CASE, separate vocabulary from product type
       (Phase 2, 2026-05-25). Icons are positional, not contextual: they
       must NOT grow with body-text revisions. Used on icon-font glyphs
       (Bootstrap Icons / Tabler) via `font-size: var(--icon-size-*)`. */
    --icon-size-md: 40px; /* file row icon (ClipboardItem) */
    --icon-size-lg: 44px; /* empty-clipboard icon (ExportClipboardModal) */
    --icon-size-xl: 56px; /* large file icon wrapper, drop-zone overlay + sticker icons */
    /* ── Dev-tools font sizes — SPECIAL CASE, not product typography ──────────
       A separate, deliberately denser scale used ONLY by developer surfaces:
       the dev panel (DevConnectionPanel), the dev-bar (Layout .dev-bar*), and
       the structured log viewer (LogViewer). These are tiny info-display sizes
       for diagnostics — the dev-bar/panel default to OFF at launch (see
       DevToolsService), so end users normally never see them. Kept OUT of the
       product --font-size-* scale on purpose: dev-tool density must never
       constrain product typography, nor be constrained by it. If you are
       styling product UI, do NOT use these — use --font-size-* above.
       Literals were consolidated onto these steps 2026-05-21 (CSS audit
       finding #5, dev-tools portion). */
    --dev-font-3xs: 0.55rem;
    --dev-font-2xs: 0.62rem;
    --dev-font-xs: 0.66rem;
    --dev-font-sm: 0.7rem;
    --dev-font-md: 0.78rem;
    --dev-font-lg: 0.85rem;
    --dev-font-xl: 0.9rem;
    /* Dev-tools font weights — symmetric with --dev-font-* sizes. SPECIAL CASE,
       not product typography. Same numeric values as --font-weight-* but kept
       in a separate vocabulary so the closed-enum stylelint allowed-list (Phase
       1B.2) can fail accidental product use without blocking the dev-bar. */
    --dev-font-weight-light:    300;
    --dev-font-weight-regular:  400;
    --dev-font-weight-medium:   500;
    --dev-font-weight-semibold: 600;
    --dev-font-weight-bold:     700;
    /* ── Opacity — 5-step scale (Phase 3.1, 2026-05-25). Data-snapped from a
       47-callsite product inventory. --opacity-quiet (0.68) is the locked tier
       from the 2026-04-19 typography pass; the other four snap natural clusters
       with Δ ≤ 0.10 across all callsites. Endpoint values 0 and 1 (transitions
       and keyframe end-states) are not tokenised — they're permitted as raw
       literals by the stylelint closed-enum.

       ── Weight + opacity rhythm pattern ──────────────────────────────────────
       Apply per role (see monochrome-polish-plan.md § Weight + opacity rhythm
       pattern). The pair is what makes monochrome read as deliberate.

         Section headers, metadata, labels       regular 400  + quiet 0.68
         Body text                               regular 400  + full 1.0
         Emphasized body, button labels, primary medium 500   + full 1.0
         Quiet emphasis (sec. field labels)      medium 500   + muted/quiet 0.55–0.70
         Strong recession (instr/hero copy)      regular/medium + strong 0.78–0.85
         Modal titles, card titles, section heads semibold 600 + full 1.0
         Heavy CTA (primary action buttons)      semibold 600 + full 1.0
         Errors, required attention              bold 700     + full 1.0
         Brand wordmark (Outfit)                 medium 500   + full 1.0
         Hero display (Outfit, ≥ 48px)           regular 400  + strong 0.85
         Idle/disabled states (dots, queued)     —            + disabled 0.35
         Subtle secondary (trust signals)        regular 400  + muted 0.55 */
    --opacity-disabled: 0.35; /* idle status dots, queued/skipped rows, pulse-min for low-energy states */
    --opacity-muted:    0.55; /* tertiary text, trust signals, subtle metadata */
    --opacity-quiet:    0.68; /* labels, captions, recessed text — LOCKED 2026-04-19 */
    --opacity-strong:   0.85; /* hero display, modal subtitles, persistent icons */
    --opacity-full:     1.00; /* default; explicit token for keyframe pulse-max / hover restore */
    /* ── Border radius — 4-step numeric scale + 2 semantic contracts (Phase 3.2,
       2026-05-25). Data-snapped from a 59-callsite product inventory. 8px is the
       heaviest cluster (23 uses after snap) — buttons, list items, inline cards.
       The two contracts (--radius-pill, --radius-circle) are CSS shape contracts,
       not numeric radii: they express intent ("fully rounded ends" / "perfect
       circle") that survives any future element-size change. Raw 0 is permitted
       by the stylelint enum for explicit no-radius (corner overrides on already-
       rounded ancestors). Partial-radius values (e.g., '8px 8px 0 0' for top-only
       rounding) need a per-site stylelint-disable until a 3.2.x follow-up extends
       the enum with per-corner tokens if/when that pattern shows up at scale. */
    --radius-xs:      4px;   /* small chips, status dots, dividers, error blocks */
    --radius-sm:      8px;   /* buttons, list items, inline cards (default control radius) */
    --radius-md:     14px;   /* large interactive surfaces — context menus, drop sticker, file row inner */
    --radius-lg:     20px;   /* outer card frames — ClipboardItem */
    --radius-pill: 999px;    /* fully-rounded ends — pill inputs, transfer strips, popovers */
    --radius-circle: 50%;    /* perfect circle — status dots, avatars, loading spinner */
    /* ── Line-height — 5-step scale (Phase 3.4, 2026-05-25). Data-snapped from a
       50-callsite product inventory. --line-height-tight (1) is the heaviest
       cluster by far (22 uses, ~44% of all callsites — single-line UI text,
       icon/button vertical centering). The body-rhythm range covers 1.3-1.65
       in 4 tiers with max Δ 0.1 per snap. Special cases stay raw: the
       ConnectionStatusPill badge uses absolute 14px to match its host height,
       and `inherit`/`normal` keywords are permitted by the stylelint enum. */
    --line-height-tight:   1;     /* icon/button text, badges, single-line UI */
    --line-height-snug:    1.3;   /* tight body, popover labels, headings */
    --line-height-normal:  1.4;   /* default body text — modal copy, banners */
    --line-height-relaxed: 1.5;   /* comfortable body — ConsentBanner, Home subtitles */
    --line-height-loose:   1.65;  /* long-form reading — legal pages, hero subtitle */
    /* ── Letter-spacing — 5-step scale (Phase 3.5, 2026-05-25). Data-snapped
       from a 13-callsite product inventory — smallest of the Phase 3 series.
       Two distinct unit families intentionally coexist: em-based tracking for
       most surfaces (scales with font-size) and a single px-based subtle token
       for pill-shaped interactive elements tuned at a specific size. Display
       tightening is its own token because hero text is sensitive to small
       tracking changes that body text would absorb invisibly. */
    --letter-spacing-display: -0.03em;  /* hero wordmark display tightening (1 use) */
    --letter-spacing-tight:   -0.01em;  /* hero h2, sidebar list-header — slight negative tracking */
    --letter-spacing-subtle:   0.2px;   /* pill inputs, CTA, drop sticker — absolute px tuning, mixed-unit by design */
    --letter-spacing-snug:     0.02em;  /* slight positive tracking — button labels, loading wordmark, section titles */
    --letter-spacing-wide:     0.06em;  /* wide tracking — uppercase labels (Settings dev label, AdModal demo label) */
    /* Spacing & Layout — 8-step t-shirt scale (Phase 3.3 scope B, 2026-05-25;
       extended in Phase 4.1, 2026-05-25 with --spacing-smd per Decision 2 = A).
       Extends the existing --spacing-sm (8) / --spacing-md (12) without changing
       either, so the --default-padding/gap/inner-spacing aliases continue to
       resolve to the same values. --spacing-smd (10) was added to tokenise the
       ~25-site 10px cluster originally deferred from 3.3; "smd" reads as
       small-medium and slots between sm and md without disturbing the scale.
       Stylelint enum closure for padding/margin/gap is the closing milestone
       of Phase 4 (after all 8 surface audits ship). */
    --spacing-2xs: 4px;
    --spacing-xs:  6px;
    --spacing-sm:  8px;
    --spacing-smd: 10px;
    --spacing-md: 12px;
    --spacing-lg: 16px;
    --spacing-xl: 20px;
    --spacing-2xl: 24px;
    /* Phase 4.2 extension (2026-05-28, Home-driven): 3xl (28) absorbs the
       FA-icon-button + narrow-viewport horizontal padding cluster called out
       in pre-release #47; 4xl (48) absorbs the home action-panel + control-
       height cluster. Activated on Home for now; other surfaces opt in
       during the post-release #47 project-wide sweep. */
    --spacing-3xl: 28px;
    --spacing-4xl: 48px;
    /* Single-callsite named exception (parallels --font-size-display) for the
       deliberate 77px bottom padding on .home-action-panel @ ≤960px — a
       one-off tune that does not belong on the t-shirt scale. */
    --spacing-home-action-panel-bottom: 77px;
    --top-row-height: 72px;
    --sidebar-sticky-row-height: 40px;
    --default-padding: var(--spacing-sm);
    --default-gap: var(--spacing-sm);
    --default-inner-spacing: var(--spacing-md);
    --scrollbar-size: 48px;
    /* Hover/focus states */
    --focus-ring: 0 0 0 3px rgba(0, 0, 0, 0.13);
    /* Transparent background */
    --color-transparent: transparent;
    /* Transfer-direction accents (theme-neutral) */
    --color-upload: #007bff;
    --color-download: #28a745;
    /* Legacy single-theme tokens — kept ONLY for the few elements that are not
       a clean light/dark pair, so the theme-aware tokens below cannot express
       them: asymmetric surfaces (Sidebar, sticky list header, dev-bar ad
       dropdown), Footer's deliberately fixed dark-styled hover, the theme
       <select> option list, and the pre-SPA loading screen. Everything else
       was migrated to the theme-aware tokens — see docs/CHANGELOG.md, CSS
       audit finding #7. */
    --color-primary-bg-light: #ffffff;  /* also in App.razor critical CSS — keep in sync */
    --color-primary-bg-dark: #171717;   /* also in App.razor critical CSS — keep in sync */
    --color-secondary-bg-light: #f6f6f6;
    --color-secondary-bg-dark: #212121;
    --color-primary-text-dark: #ffffff;
    --color-secondary-text-dark: #aaa;
    --color-hover-dark: #444;
    --color-hover-select-dark: #595959;
    /* ─── Theme-aware surface / text / border tokens ──────────────────────────
       One token per role; the value flips under [data-bs-theme="dark"] below,
       so a component writes var(--color-primary-bg) once instead of a rule plus
       a [data-bs-theme="dark"] duplicate. */
    --color-primary-text: #000000;
    --color-secondary-text: #2f2f2f;
    --color-primary-bg: #ffffff;
    --color-secondary-bg: #f6f6f6;
    --color-input-bg: #ffffff;
    --color-modal-bg: #ffffff;
    /* Toast surface. Light: white body matches modal — differentiation comes
       from a darker perimeter (#cfcfcf vs --color-primary-border #dedede) plus
       the slightly stronger shadow on .clipcroft-toast. Dark: see the
       [data-bs-theme="dark"] block — toast bg sits at the midpoint between
       page (--color-primary-bg #171717) and modal (#2f2f2f), so it reads
       distinctly over either context. */
    --color-toast-bg: #ffffff;
    --color-toast-border: #cfcfcf;
    --color-hover: #f0f0f0;
    --color-hover-select: #e6e6e6;
    /* Border vocabulary (extended from 3 → 4 levels 2026-05-27, reopening the
       Phase 4 lock — see CHANGELOG + ux-roadmap). The Phase 4 series locked
       at primary-border / btn-border / btn-border-hover, which left a gap
       between "almost invisible" (primary-border, ~13 % luminance contrast)
       and "almost primary" (btn-border-hover, 100 %). --color-secondary-border
       fills that gap (~28 % luminance contrast against the panel) and is the
       canonical "outlined secondary button" border. Used by .home-open-btn. */
    --color-primary-border: #dedede;
    --color-secondary-border: #b8b8b8;
    --color-btn-bg: #080808;
    --color-btn-border: #080808;
    --color-btn-text: #ffffff;
    --color-btn-hover-bg: #ffffff;
    --color-btn-text-hover: #1a1a1a;
    --color-btn-border-hover: #000000;
    --color-top-row-button: #444444;
    --color-item-bg: #f4f4f4;
    --color-item-bg-2: #dbdbdb;
    --color-item-border: #595959;
    /* ─── Semantic status colors ──────────────────────────────────────────────
       Theme-aware: each flips to a lighter value under [data-bs-theme="dark"]
       (see the dark block below) so it stays legible on the dark background.
       Use for danger / success / warning / info text, icons, borders, and —
       via color-mix() — tinted surfaces. These replace the old Material-palette
       accent vars (--red-color etc.) that components had drifted away from. */
    --color-danger: #dc3545;
    --color-success: #16a34a;
    --color-warning: #b45309;
    --color-info: #2563eb;
    /* Connection-status traffic-light dots (ConnectionStatus.razor only) — a
       fixed traffic-light metaphor, not semantic status text; kept distinct so
       the "connecting" dot stays yellow rather than going amber. */
    --yellow-color: #FFEB3B;
    --gray-color: #9E9E9E;
    /* ─── Z-index scale ──────────────────────────────────────────────────────
       Single overview of every z-index that participates in the app-wide
       layer cake. Values jump in big steps so a new layer can slot between
       two existing ones without renumbering.

       Local stacking-context tweaks (z-index: 1/2/3/9/10 inside a single
       card, list item, sidebar, drop-zone inner, or modal body) are
       intentionally NOT lifted here — they only coordinate with siblings
       in their own stacking context, and globalizing them would suggest a
       relationship that doesn't exist.

       ⚠ Two loading-related rules duplicate their values as literals:
       - App.razor's inline critical CSS (`#loading-screen`, z-index: 9999)
         must paint before any external CSS loads, so a var lookup would
         resolve to unset.
       - first-load-screen.css mirrors the same values as literals to keep
         the loading-overlay rule self-contained against any future CSS
         load-order surprise.
       If you change `--z-loading-screen` or `--z-nav-progress` here, update
       both files in the same commit. */
    --z-dev-bar: 99;            /* dev-bar shell (Layout)                      */
    --z-dev-bar-dropdown: 100;  /* dropdowns hanging off the dev-bar           */
    --z-overlay: 1000;          /* contextual menu, drop-zone shroud, error-ui */
    --z-consent: 1050;          /* GDPR consent banner                         */
    --z-ad-modal: 1060;         /* AdModal — between consent and standard modal*/
    --z-modal-overlay: 2000;    /* BaseModal backdrop                          */
    --z-modal-panel: 2001;      /* modal container — one layer above backdrop  */
    --z-modal-priority: 3000;   /* system/confirm modals above feature modals  */
    --z-tooltip: 4000;          /* hover/focus tooltip bubble                  */
    --z-dev-panel: 9000;        /* DevConnectionPanel (full-screen dev surface)*/
    --z-loading-screen: 9999;   /* first-paint loading overlay — also literal  */
    --z-nav-progress: 10000;    /* in-app nav progress bar (above loading)     */
}

html, body {
    font-family: var(--font-1);
    color: var(--color-primary-text);
    padding-top: env(safe-area-inset-top);
    /*padding-bottom: env(safe-area-inset-bottom);
    padding-left: env(safe-area-inset-left);
    padding-right: env(safe-area-inset-right);*/
    overscroll-behavior: none; /* Prevents bouncing and pull-to-refresh */
    touch-action: pan-x pan-y; /* Prevents unintended gestures */
    background-color: var(--color-primary-bg);
}

/* Dark theme — flips the theme-aware token values; the safe-area background
   is then handled automatically by the theme-aware html/body rule above. */
[data-bs-theme="dark"] {
    --focus-ring: 0 0 0 3px rgba(255, 255, 255, 0.18);
    /* Lighter status colors — legible on the dark background. */
    --color-danger: #f87171;
    --color-success: #4ade80;
    --color-warning: #fbbf24;
    --color-info: #87bcff;
    /* Dark values for the theme-aware surface / text / border tokens. */
    --color-primary-text: #ffffff;
    --color-secondary-text: #aaa;
    --color-primary-bg: #171717;
    --color-secondary-bg: #212121;
    --color-input-bg: #212121;
    --color-modal-bg: #2f2f2f;
    /* Dark: toast bg = midpoint between page (#171717) and modal (#2f2f2f),
       so a toast reads distinctly over EITHER (darker than modal, lighter
       than page). #232323 is the true (R/G/B average) midpoint; close to
       --color-secondary-bg #212121 but kept as a dedicated token so toasts
       can evolve independently of the secondary surface. */
    --color-toast-bg: #232323;
    --color-toast-border: #3a3a3a;
    --color-hover: #444;
    --color-hover-select: #595959;
    --color-primary-border: #393939;
    --color-secondary-border: #5a5a5a;
    --color-btn-bg: #ffffff;
    --color-btn-border: #ffffff;
    --color-btn-text: #000000;
    --color-btn-hover-bg: #000000;
    --color-btn-text-hover: #f0f0f0;
    --color-btn-border-hover: #ffffff;
    --color-top-row-button: #cccccc;
    --color-item-bg: #2f2f2f;
    --color-item-bg-2: #434343;
    --color-item-border: #bfbfbf;
    background-color: var(--color-primary-bg);
    color: var(--color-primary-text);
}

/* The only h1 in the app (Home.razor:41 "Clipcroft") carries its own scoped
   font-size + font-family at Home.razor.css:134-142 (--font-size-display).
   The global h1 font-size rule + min-width:768px @media override were dead
   code; removed 2026-05-25 (Phase 2) along with --font-size-6xl/7xl. The
   focus-outline rule stays for any future h1. */
h1:focus {
    outline: none;
}

h2 {
    font-size: var(--font-size-2xl); /* no matching --font-size-* step; left as-is */
}

h3 {
    font-size: var(--font-size-xl);
}


/* Token values are theme-aware, so one rule set covers light and dark. */
.btn-primary {
    color: var(--color-btn-text);
    background-color: var(--color-btn-bg);
    border-color: var(--color-btn-border);
    /* Override Bootstrap's per-variant disabled-state CSS variables. Without
       these, .btn:disabled (specificity 0,2,0 — pseudo-class counts as class,
       so it beats .btn-primary's 0,1,0) falls back to Bootstrap's default
       --bs-btn-disabled-bg: #0d6efd (blue), making the disabled "Create
       protected clipboard" button render bright blue against our otherwise
       monochrome design. The tokens flip with the theme, covering both modes. */
    --bs-btn-disabled-color: var(--color-btn-text);
    --bs-btn-disabled-bg: var(--color-btn-bg);
    --bs-btn-disabled-border-color: var(--color-btn-border);
    border-radius: var(--radius-pill);
    padding-left: var(--spacing-xl);
    padding-right: var(--spacing-xl);
    gap: var(--default-gap);
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-subtle);
}

    .btn-primary:hover {
        background-color: var(--color-btn-hover-bg);
        color: var(--color-btn-text-hover);
        border-color: var(--color-btn-border-hover);
    }

    .btn-primary:active {
        background-color: var(--color-btn-hover-bg) !important;
        color: var(--color-btn-text-hover) !important;
        border-color: var(--color-btn-border-hover) !important;
        transform: scale(0.96); /* Optional: add a slight press effect */
    }

.btn-secondary {
    border-radius: var(--radius-pill);
    padding-left: var(--spacing-xl);
    padding-right: var(--spacing-xl);
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-subtle);
}

/* Button styling */
.btn-export {
    /* stylelint-disable-next-line declaration-property-value-allowed-list -- Phase 4 deliberate tail spacing — surface audit exception, see CHANGELOG 2026-05-25 */
    padding: 7px 11px; /* var(--default-inner-spacing); */
    border-radius: var(--radius-sm);
    font-size: var(--font-size-sm);
    display: flex;
    align-items: center;    
}

.input-button-container {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-smd);
    width: 100%;
}

    .input-button-container button {
        white-space: nowrap;
    }

.input-button-container--fit {
    max-width: fit-content;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid var(--color-success);
}

.invalid {
    outline: 1px solid var(--color-danger);
}

.validation-message {
    color: var(--color-danger);
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45OTMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: black;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.top-row-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--color-transparent);
    color: var(--color-top-row-button);
    border: none;
    cursor: pointer;
    font-size: var(--font-size-2xl);
    line-height: var(--line-height-tight);
    margin: 0;
    padding: var(--spacing-xs) var(--default-padding);
    border-radius: var(--radius-sm);
}

    .top-row-button:hover {
        background-color: var(--color-hover);
    }

.top-row-button.hidden {
    display: none;
}

.icon-notification-dot {
    position: absolute;
    top: 0.2rem;
    right: 0.2rem;
    width: 0.6rem;
    height: 0.6rem;
    background-color: var(--color-danger);
    border-radius: var(--radius-circle);
    z-index: 2;
}

.menu-icon {
    font-size: var(--font-size-lg);
    flex-shrink: 0;
    display: flex;
    align-items: center;
}

/* Each rule below uses fully-qualified selectors on both sides of the comma.
   Earlier wording dropped the [data-bs-theme="dark"] prefix and the
   ::placeholder / :focus pseudo on the second branch, which made the dark-
   mode declarations leak onto every <input type="password"> regardless of
   theme (and vice versa for placeholder / focus declarations on text inputs).
   Visible symptom: password modal inputs always rendered with the dark
   background even in light mode. */

input[type="text"],
input[type="password"] {
    background-color: var(--color-input-bg);
    color: var(--color-primary-text);
    border: 1px solid var(--color-item-border);
    padding: 0.5rem 1rem;
    border-radius: var(--radius-sm);
    font-size: var(--font-size-md);
    transition: all 0.2s ease;
    width: 100%;
    outline: none;
}

    input[type="text"]::placeholder,
    input[type="password"]::placeholder {
        color: var(--color-top-row-button);
        opacity: var(--opacity-quiet);
    }

    input[type="text"]:focus,
    input[type="password"]:focus {
        border-color: var(--color-btn-border-hover);
        box-shadow: var(--focus-ring);
    }

/* ── App text-input look ─────────────────────────────────────────────────────
   The canonical single-line text-field appearance for inputs that sit in an
   .input-button-container next to an action button. Builds on the global
   input[type="text"] rule above (bg / border / radius / focus ring) and adds
   the row sizing the design uses. Apply to ANY future text input that should
   match the clipboard-name fields. Carries NO clipboard-specific behaviour —
   for that, layer .clipboard-id-input on top.
   Selector is qualified with input[type="text"] so it out-specifies the bare
   element rule above (which would otherwise win the `padding` declaration).
   ─────────────────────────────────────────────────────────────────────────── */
input[type="text"].app-text-input {
    flex: 1;
    min-width: 0;
    height: 48px;
    padding: 0 var(--spacing-lg);
}

/* ── Clipboard-ID behaviour modifier ─────────────────────────────────────────
   Layered on top of .app-text-input for clipboard-NAME entry fields only
   (Home "open existing" + ChangeClipboardModal). Renders the field lowercase as
   the user types so what they see matches the unconditionally-lowercased value
   used for routing — see docs/clipboard-id-design.md §5. Deliberately separate
   from the generic look: a non-clipboard input gets .app-text-input alone.
   ─────────────────────────────────────────────────────────────────────────── */
.clipboard-id-input {
    text-transform: lowercase;
}

/* ─── Scrollbars ─────────────────────────────────────────────────────────────
   Single source of truth. Was previously split between this block and a
   separate css/scrollbars.css whose rules (loaded after app.css) silently
   overrode most of these. That file is removed; the values below are exactly
   what was rendering live — the 48px WebKit scrollbar from scrollbars.css plus
   the Firefox `scrollbar-width: thin` that survived the old cascade.
   ─────────────────────────────────────────────────────────────────────────── */
/* WebKit-based browsers (Chrome, Edge, Safari, Opera) */
::-webkit-scrollbar {
    width: var(--scrollbar-size);
    height: var(--scrollbar-size);
}

::-webkit-scrollbar-track {
    background: rgba(0, 0, 0, 0.1);
}

    ::-webkit-scrollbar-track:hover {
        background: rgba(0, 0, 0, 0.2);
    }

::-webkit-scrollbar-thumb {
    background-color: rgba(100, 100, 100, 0.7);
    border-radius: var(--radius-sm);
    border: 4px solid transparent; /* padding around the thumb */
    background-clip: content-box;  /* keep the border from widening the thumb */
}

    ::-webkit-scrollbar-thumb:hover {
        background-color: rgba(100, 100, 100, 0.9);
    }

/* Transparent up/down arrow buttons */
::-webkit-scrollbar-button {
    background: transparent;
    height: var(--scrollbar-size);
    width: var(--scrollbar-size);
    display: block;
}

/* Firefox — honors only scrollbar-width / scrollbar-color; WebKit ignores both. */
* {
    scrollbar-width: thin;
    scrollbar-color: rgba(100, 100, 100, 0.7) transparent;
}

.transfer-spinner {
    width: 16px;
    height: 16px;
    display: inline-block;
    flex-shrink: 0;
    border: 2px solid var(--color-primary-border);
    border-top-color: var(--color-top-row-button);
    border-radius: var(--radius-circle);
    animation: transfer-spinner-rotate 0.8s linear infinite;
}

[data-bs-theme="dark"] .transfer-spinner {
    border-color: rgba(255, 255, 255, 0.18);
    border-top-color: rgba(255, 255, 255, 0.7);
}

@keyframes transfer-spinner-rotate {
    to {
        transform: rotate(360deg);
    }
}

/* Error boundary fallback — shown when an unhandled exception reaches the root */
.error-boundary {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100dvh;
    padding: 2rem;
}

.error-boundary__box {
    max-width: 420px;
    text-align: center;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

/* Legal pages (Privacy, Terms) */
.legal-page {
    min-height: 100dvh;
    padding: 2rem 1rem 4rem;
    display: flex;
    justify-content: center;
}

.legal-page__inner {
    max-width: 760px;
    width: 100%;
}

.legal-page__back {
    padding: 0;
    margin-bottom: 2rem;
    font-size: var(--font-size-sm);
    text-decoration: none;
}

.legal-page__date {
    font-size: var(--font-size-sm);
    opacity: var(--opacity-quiet);
    margin-bottom: 2rem;
}

.legal-page h1 {
    font-size: var(--font-size-3xl);
    font-weight: var(--font-weight-semibold);
    margin-bottom: 0.25rem;
}

.legal-page h2 {
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-semibold);
    margin-top: 2rem;
    margin-bottom: 0.5rem;
}

.legal-page p,
.legal-page li {
    font-size: var(--font-size-md);
    line-height: var(--line-height-loose);
}

.legal-page ul {
    padding-left: 1.5rem;
    margin-bottom: 1rem;
}


/* ─── Utility classes ────────────────────────────────────────────────────────
   Generic helpers for use anywhere in the app.
   Add the class to an element in markup; no component CSS needed.
   ────────────────────────────────────────────────────────────────────────── */

/* Availability state — used on file rows that are not on this device */
.file-row-unavailable {
    opacity: var(--opacity-muted);
}

/* Strikethrough — used on filenames that are unavailable */
.file-name-strikethrough {
    text-decoration: line-through;
}

/* Flex centering — horizontal + vertical */
.flex-center {
    display: flex;
    justify-content: center;
    align-items: center;
}

/* Single-line text truncation with ellipsis */
.text-ellipsis {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 2-line clamp with trailing ellipsis */
.line-clamp-2 {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
}

/* Bare button reset — removes all browser button chrome */
.btn-reset {
    background: none;
    border: none;
    padding: 0;
    margin: 0;
    cursor: pointer;
}

/* ─── Modal action button — shared base ──────────────────────────────────────
   Used by ExportClipboardModal and ThreeDotsMenuModal.
   Each component overrides only `width` and adds its unique sections.
   ────────────────────────────────────────────────────────────────────────── */
.modal-action-btn {
    display: flex;
    align-items: center;
    gap: var(--default-gap);
    justify-content: flex-start;
    padding: var(--spacing-md) var(--spacing-lg);
    background-color: var(--color-secondary-bg);
    border: 1px solid var(--color-primary-border);
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: background-color 0.2s ease, transform 0.1s ease;
    font-size: var(--font-size-md);
    color: var(--color-primary-text);
}

.modal-action-btn:disabled {
    cursor: default;
    opacity: var(--opacity-quiet); /* :disabled deliberately recedes further than 0.78 → context warrants */
    transform: none;
}

.modal-action-btn:hover {
    background-color: var(--color-primary-bg);
    transform: scale(1.01);
}

.modal-action-btn i {
    font-size: var(--font-size-lg);
    min-width: 24px;
    text-align: center;
}

.modal-action-btn span:last-child {
    text-align: left;
}

/* --color-danger is theme-aware, so no separate dark-mode rule is needed. */
.modal-action-btn--danger,
.modal-action-btn--danger .menu-icon {
    color: var(--color-danger);
}

.modal-action-btn--danger:hover {
    background-color: color-mix(in srgb, var(--color-danger) 8%, transparent);
    color: var(--color-danger);
}

/* ── Shared settings-style modal primitives ─────────────────────────────────
   Used by SettingsModal.razor and DeveloperModal.razor. Kept global (not
   scoped) so any future "settings-shaped" modal renders with the same section
   dividers, title weight, and description color without duplicating CSS.
   Settings-specific rules (clear-confirm, wakelock-status colors, storage
   metrics, etc.) stay in SettingsModal.razor.css.
   ─────────────────────────────────────────────────────────────────────────── */
.settings-section {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-md);
}

.settings-section + .settings-section {
    margin-top: var(--spacing-xl);
    padding-top: var(--spacing-xl);
    border-top: 1px solid var(--color-primary-border);
}

.settings-action-title {
    font-weight: var(--font-weight-medium);
}

/* Section group title — sits at the top of each .settings-section above the
   rows. Visually subordinate to .settings-action-title (row labels) by design:
   uppercase + muted opacity + small caps-style tracking marks the title as a
   *grouping device*, while the larger sentence-case row labels carry the
   primary informational weight. Mirrors the .settings-dev-label rule body
   (dev-only context, dashed border) but kept as a distinct semantic class
   because the dev label may be repositioned or removed at launch when the
   dev panel flips OFF by default. */
.settings-group-title {
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    letter-spacing: var(--letter-spacing-wide);
    text-transform: uppercase;
    opacity: var(--opacity-muted);
}

.settings-action-description {
    color: var(--color-secondary-text);
    font-size: var(--font-size-sm);
    line-height: var(--line-height-normal);
}

.settings-wakelock-row {
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--spacing-lg);
}

.settings-wakelock-text {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-2xs);
    flex: 1;
}

.settings-wakelock-toggle {
    transform: scale(1.4);
    /* stylelint-disable-next-line declaration-property-value-allowed-list -- Phase 4 deliberate tail spacing — surface audit exception, see CHANGELOG 2026-05-25 */
    margin-top: 2px;
    flex-shrink: 0;
}

.settings-ads-counters {
    display: flex;
    flex-direction: column;
    /* stylelint-disable-next-line declaration-property-value-allowed-list -- Phase 4 deliberate tail spacing — surface audit exception, see CHANGELOG 2026-05-25 */
    gap: 2px;
    margin-top: var(--spacing-xs);
    font-size: var(--font-size-sm);
    color: var(--color-secondary-text);
}

/* Standard action row inside any BaseModal body — Cancel/primary-or-danger pair,
   right-aligned, comfortable gap, sits below the body copy. Used by every
   IsHighPriority confirm modal in Clipboard.razor and SettingsModal's
   "Clear saved data?" confirm. Was scoped to Clipboard.razor.css; promoted
   2026-05-17 so confirm modals living in other components share the look. */
.modal-action-row {
    display: flex;
    gap: 0.75rem;
    justify-content: flex-end;
    margin-top: 1.5rem;
}

/* ── Password modal primitives ──────────────────────────────────────────────
   Shared by PasswordPromptModal and PasswordSetupModal — both render the same
   password-input layout: input + show/hide eye toggle, caps-lock hint, error
   line, Cancel/primary action row. Was duplicated rule-for-rule across the two
   components' scoped CSS; promoted here 2026-05-21 (CSS audit finding #3).
   The inputs additionally inherit the global input[type="password"] rule above;
   component-specific bits (the intro paragraph, the setup confirm-fields wrap,
   the setup mismatch hint) stay in each component's .razor.css.
   ─────────────────────────────────────────────────────────────────────────── */
.password-modal-body {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-lg);
}

.password-modal-input-wrap {
    position: relative;
}

.password-modal-input {
    height: 44px;
    width: 100%;
    box-sizing: border-box;
    /* stylelint-disable-next-line declaration-property-value-allowed-list -- Phase 4 deliberate tail spacing — surface audit exception, see CHANGELOG 2026-05-25 */
    padding-right: 42px; /* room for the show/hide eye toggle */
}

/* Edge / IE inject a native password-reveal eye via ::-ms-reveal on every
   <input type="password">. We provide our own toggle, so hide the native one
   to avoid the duplicate-eye look on Edge. ::-ms-clear is suppressed too. */
.password-modal-input::-ms-reveal,
.password-modal-input::-ms-clear {
    display: none;
    width: 0;
    height: 0;
}

/* Bare icon-button parked at the right edge of the input. Toggles the input's
   type between "password" and "text" so users can verify what they typed. */
.password-modal-eye {
    position: absolute;
    right: 6px;
    top: 50%;
    transform: translateY(-50%);
    width: 32px;
    height: 32px;
    padding: 0;
    background: transparent;
    border: none;
    border-radius: var(--radius-sm);
    color: inherit;
    cursor: pointer;
    opacity: var(--opacity-quiet);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.password-modal-eye:hover:not(:disabled),
.password-modal-eye:focus-visible {
    opacity: 1;
    background-color: rgba(0, 0, 0, 0.06);
}

[data-bs-theme="dark"] .password-modal-eye:hover:not(:disabled),
[data-bs-theme="dark"] .password-modal-eye:focus-visible {
    background-color: rgba(255, 255, 255, 0.08);
}

/* Disabled only by PasswordPromptModal, during its 30s lockout countdown. */
.password-modal-eye:disabled {
    cursor: not-allowed;
    opacity: var(--opacity-disabled);
}

.password-modal-eye i {
    font-size: var(--font-size-md);
    line-height: var(--line-height-tight);
}

/* Inline hint row — caps-lock indicator (both modals) and the setup modal's
   live password-mismatch line. */
.password-modal-hint {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    font-size: var(--font-size-sm);
    line-height: var(--line-height-snug);
}

.password-modal-hint i {
    font-size: var(--font-size-sm);
    line-height: var(--line-height-tight);
}

.password-modal-hint-warn {
    color: var(--color-warning);
}

.password-modal-error {
    margin: 0;
    font-size: var(--font-size-sm);
    line-height: var(--line-height-snug);
    color: var(--color-danger);
}

/* Cancel (left) + primary action (right) paired in a single bottom row. The
   8px margin-top adds to the body gap so the action zone reads as its own. */
.password-modal-actions {
    display: flex;
    justify-content: space-between;
    gap: var(--spacing-sm);
    margin-top: var(--spacing-sm);
}

.password-modal-submit {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
}

.password-modal-submit i {
    font-size: inherit;
    line-height: var(--line-height-tight);
}

/* ── Toasts ─────────────────────────────────────────────────────────────────
   Slide in from the left, appearing just below the header.
   margin-top: Toastify stacks from top:15px; 57px brings the first toast to
   72px (--top-row-height). The added --vv-offset-top (set by toastifyHelper.js
   from window.visualViewport.offsetTop) shifts the toast down by the on-screen
   keyboard pan on iOS Safari, so toasts stay inside the visible viewport when
   the keyboard is open. Defaults to 0px (desktop, Android, no-keyboard, or no
   visualViewport API) — no change to existing positioning. Stacking of
   multiple toasts remains correct because the value is shared.
   transition override: suppress the default top-slide; keep opacity for exit.
   animation: transform-only slide-in; opacity is handled by Toastify's
   .on class (enter stays at 1, exit transitions to 0 via the override).
   ─────────────────────────────────────────────────────────────────────────── */
.toastify.clipcroft-toast {
    /* stylelint-disable-next-line declaration-property-value-allowed-list -- Phase 4 deliberate tail spacing — surface audit exception, see CHANGELOG 2026-05-25 */
    margin-top: calc(57px + var(--vv-offset-top, 0px));
    /* stylelint-disable-next-line declaration-property-value-allowed-list -- Phase 4 deliberate tail spacing — surface audit exception, see CHANGELOG 2026-05-25 */
    padding: var(--spacing-smd) 14px;
    min-width: 220px;
    max-width: 380px;
    background: var(--color-toast-bg);
    color: var(--color-primary-text);
    border: 1px solid var(--color-toast-border);
    border-left: 6px solid var(--color-toast-border);
    border-radius: var(--radius-sm);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
    font-family: var(--font-1), system-ui, sans-serif;
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-medium);
    transition: opacity 0.4s ease !important;
    animation: clipcroft-toast-enter 0.35s cubic-bezier(0.215, 0.61, 0.355, 1) forwards;
}

.toastify.clipcroft-toast--success { border-left-color: var(--color-success); }
.toastify.clipcroft-toast--error   { border-left-color: var(--color-danger);  }
.toastify.clipcroft-toast--info    { border-left-color: var(--color-info);    }
.toastify.clipcroft-toast--warning { border-left-color: var(--color-warning); }

.toastify.clipcroft-toast--success i { color: var(--color-success); }
.toastify.clipcroft-toast--error   i { color: var(--color-danger);  }
.toastify.clipcroft-toast--info    i { color: var(--color-info);    }
.toastify.clipcroft-toast--warning i { color: var(--color-warning); }

/* Slightly larger than the body text so the outline glyphs read clearly
   without feeling like an icon-column. flex-shrink:0 keeps the icon at
   full size when the text wraps to two lines. line-height:24px is the
   explicit px-match for the text's computed line-height (font-size-md
   16px × 1.5 = 24px). Using a unitless 1.5 here would give the icon a
   27px box (font-size-lg 18px × 1.5), leaving a 3px box-height mismatch
   that pushed the icon's glyph-center ~1.5px below the text's on the
   first line. The px-match collapses both boxes to 24px, so the icon
   glyph centers exactly on the first text line — combined with the
   wrapper's align-items:flex-start (toastifyHelper.js), this anchors the
   icon to the first line when the message wraps to 2+ lines and keeps
   it pixel-tight on the single-line case. */
.toastify.clipcroft-toast i {
    font-size: var(--font-size-lg);
    flex-shrink: 0;
    line-height: 24px;
}

@keyframes clipcroft-toast-enter {
    from { transform: translateX(calc(100% + 30px)); }
    to   { transform: translateX(0); }
}

@media (max-width: 768px) {
    .toastify.clipcroft-toast {
        max-width: calc(75vw - 20px);
    }
}

/* ── Custom tooltip ─────────────────────────────────────────────────────────
   Replaces the default browser `title` bubble with a styled element that
   shares the app's modal/border tokens — coherent in both themes. Driven by
   wwwroot/js/tooltip.js, which migrates [title] → [data-tt] on a single
   shared element appended to <body>. Z-index sits above modals (3000) but
   below the first-paint loading screen (9999). Touch devices skip this and
   keep the OS long-press affordance.
   ─────────────────────────────────────────────────────────────────────────── */
.app-tooltip {
    position: fixed;
    top: -9999px;
    left: 0;
    z-index: var(--z-tooltip);
    max-width: 260px;
    padding: var(--spacing-xs) var(--spacing-smd);
    background-color: var(--color-modal-bg);
    color: var(--color-primary-text);
    border: 1px solid var(--color-primary-border);
    border-radius: var(--radius-sm);
    font-family: var(--font-1);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-regular);
    line-height: var(--line-height-snug);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
    pointer-events: none;
    opacity: 0;
    transform: translateY(2px);
    transition: opacity 120ms ease, transform 120ms ease;
    white-space: normal;
    overflow-wrap: anywhere;
}

.app-tooltip.is-visible {
    opacity: 1;
    transform: translateY(0);
}

[data-bs-theme="dark"] .app-tooltip {
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);
}

/* ── Explorer / list-modal shared layer ────────────────────────────────────
   Shared primitives for ExportClipboardExplorerModal and ClipboardManagerModal.
   Any future wide/fullscreen modal that shows a scrollable list + toolbar row
   can use these without re-implementing them. Component-specific grid layouts,
   filter tabs, checkboxes, and column definitions stay in each component's
   own .razor.css.
   ─────────────────────────────────────────────────────────────────────────── */

.explorer-body {
    display: flex;
    flex-direction: column;
    width: 100%;
    /* Default: fills the fullscreen modal's available viewport height.
       Override with a local { height: auto } rule for smaller modals. */
    height: calc(90dvh - 130px);
    overflow: hidden;
}

.explorer-top {
    flex-shrink: 0;
}

.explorer-empty p {
    font-size: var(--font-size-md);
    opacity: var(--opacity-quiet);
    text-align: center;
    margin: 0;
    padding: var(--spacing-2xl) 0;
}

.explorer-statusbar {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    padding-bottom: var(--spacing-md);
    min-height: 32px;
    flex-wrap: wrap;
}

.explorer-item-count {
    font-size: var(--font-size-sm);
    opacity: var(--opacity-muted);
}

.explorer-sel-btn {
    display: flex;
    align-items: center;
    gap: var(--spacing-2xs);
    padding: 0 var(--spacing-smd);
    height: 28px;
    box-sizing: border-box;
    border: 1px solid var(--color-primary-border);
    border-radius: var(--radius-sm);
    background: var(--color-secondary-bg);
    font-size: var(--font-size-sm);
    line-height: var(--line-height-tight);
    cursor: pointer;
    color: var(--color-primary-text);
    transition: background 0.12s;
}

.explorer-sel-btn:hover {
    background: var(--color-hover);
}

.explorer-sel-btn--danger {
    color: var(--color-danger);
    border-color: color-mix(in srgb, var(--color-danger) 30%, transparent);
}

.explorer-sel-btn--danger:hover {
    background: color-mix(in srgb, var(--color-danger) 8%, transparent);
}

.explorer-sel-btn--ghost {
    border-color: transparent;
    background: transparent;
}

.explorer-list {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

.explorer-row {
    border-radius: var(--radius-sm);
    padding-top: var(--spacing-2xs);
    padding-bottom: var(--spacing-2xs);
    transition: background 0.1s;
    cursor: pointer;
    -webkit-user-select: none;
    user-select: none;
}

.explorer-row:hover {
    background: var(--color-hover);
}

.explorer-row--selected {
    background: var(--color-hover-select) !important;
}

.explorer-action-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border: none;
    border-radius: var(--radius-xs);
    background: transparent;
    cursor: pointer;
    color: var(--color-primary-text);
    font-size: var(--font-size-md);
    transition: background 0.1s;
}

.explorer-action-btn:hover {
    background: var(--color-hover);
}

.explorer-action-btn--danger {
    color: var(--color-danger);
}

.explorer-action-btn--danger:hover {
    background: color-mix(in srgb, var(--color-danger) 8%, transparent);
}
