/* Project Panel v0.1 — Doppler-tier polish */

:root {
    /* The large-preview fade needs literal channel values (CEP's Chromium
       predates color-mix), so each theme defines its own fade triplet. */
    --fade-full: rgb(30, 30, 30);
    --fade-70: rgba(30, 30, 30, 0.7);
    --fade-0: rgba(30, 30, 30, 0);
    /* Placeholder checker/stripe pattern colours (theme-tunable). */
    --checker-a: var(--bg-input);
    --checker-b: var(--bg-zebra);
    --bg: #1e1e1e;
    --bg-elev: #262626;
    --bg-hover: #2c2c2c;
    --bg-active: #3a3a3a;
    --bg-input: #161616;
    --bg-zebra: #262626;
    --border: #333;
    --border-soft: #2a2a2a;
    --text: #e6e6e6;
    --text-dim: #999;
    --text-faint: #666;
    --accent: #5b8def;
    --accent-soft: rgba(91, 141, 239, 0.15);
    --danger: #e06c6c;
    --missing: #d97757;

    /* AE label colors (matches stock AE label palette) */
    --label-0: transparent;
    /* Fallbacks matching DEFAULT_LABEL_COLORS in panel.js, which overwrites
       these at startup (and on palette edits). Keeping them identical means
       no flash-of-wrong-colour before init and one palette in the codebase. */
    --label-1: #D86C6C;
    --label-2: #E6B485;
    --label-3: #A9C7A4;
    --label-4: #E5A9C4;
    --label-5: #A9B4D8;
    --label-6: #D8C4A0;
    --label-7: #A0C7C0;
    --label-8: #7E8FC4;
    --label-9: #7BA86F;
    --label-10: #9B7BB8;
    --label-11: #D89A6C;
    --label-12: #A87B5C;
    --label-13: #C77BA8;
    --label-14: #7BB8B8;
    --label-15: #C4B49C;
    --label-16: #5C8C5C;

    /* Motion system — shared easing + duration tokens so every interaction
       feels like one coherent system rather than a grab-bag of timings.
       --ease-out: standard UI deceleration for hovers, fades, small state
       changes. --ease-spring: a gentle overshoot for larger, more expressive
       moves (panels, reveals). Durations kept short; a tool you use all day
       should feel instant, with motion as a subtle confirmation, not a wait. */
    --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
    --ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);
    --dur-fast: 0.12s;
    --dur-med: 0.2s;
}

* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

html, body {
    height: 100%;
    overflow: hidden;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", system-ui, sans-serif;
    font-size: 12px;
    color: var(--text);
    background: var(--bg);
    user-select: none;
    -webkit-user-select: none;
}

.app {
    display: flex;
    flex-direction: column;
    height: 100vh;
}

.hidden { display: none !important; }

/* ----- top bar ----- */

.topbar {
    flex-shrink: 0;
    padding: 3px 10px 12px;
    border-bottom: 1px solid var(--border-soft);
    background: var(--bg);
}

.search-wrap {
    position: relative;
    display: flex;
    align-items: center;
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 0 8px;
    transition: border-color var(--dur-fast) var(--ease-out);
}

.search-wrap:focus-within {
    border-color: var(--accent);
}

.search-icon {
    color: var(--text-faint);
    flex-shrink: 0;
}

#search {
    flex: 1;
    background: none;
    border: none;
    outline: none;
    color: var(--text);
    font-size: 12px;
    padding: 7px 6px;
    font-family: inherit;
}

#search::placeholder {
    color: var(--text-faint);
    opacity: 1;
}

/* Rotating search hints render in this overlay (not the native
   placeholder) so each one can animate in with the SAME word cascade as a
   rename. Positioned over the input's text area; ignores pointer events so
   the field stays clickable. Hidden while the field has focus or text. */
.search-hint {
    position: absolute;
    left: 32px;
    right: 56px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--text-faint);
    font-size: 12px;
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    pointer-events: none;
    z-index: 1;
}

.search-hint.hint-out {
    opacity: 0;
    transition: opacity 0.34s cubic-bezier(0.05, 0.8, 0.2, 1);
}

/* Each hint word rises in like a rename (word-in, matching duration and
   stagger to the rename cascade). */
.search-hint .pw {
    display: inline-block;
    animation: word-in 0.9s cubic-bezier(0.05, 0.8, 0.2, 1) backwards;
}

.clear-btn {
    background: none;
    border: none;
    color: var(--text-faint);
    cursor: pointer;
    font-size: 16px;
    line-height: 1;
    padding: 2px 4px;
    border-radius: 3px;
    display: none;
}

.clear-btn:hover { color: var(--text); background: var(--bg-hover); }

.search-wrap.has-text .clear-btn { display: block; }

/* Search help: a small ? button at the right of the search field, and a
   popover listing the keyword vocabulary on hover. */
.search-help-btn {
    background: none;
    border: 1px solid var(--border);
    color: var(--text-faint);
    cursor: help;
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    margin-left: 2px;
    transition: color var(--dur-fast), border-color var(--dur-fast) var(--ease-out);
}

.search-help-btn:hover {
    color: var(--text);
    border-color: var(--text-dim);
}

.search-help-popover {
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: 6px;
    z-index: 200;
    width: 260px;
    max-width: calc(100vw - 16px);
    background: var(--bg-elev);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 11px 12px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

.search-help-popover.hidden { display: none; }

.search-help-title {
    font-size: 12px;
    font-weight: 600;
    color: var(--text);
    margin-bottom: 9px;
    padding-bottom: 7px;
    border-bottom: 1px solid var(--border-soft);
}

/* Each group is a row: left-aligned category label in a fixed-width
   column, keywords flowing to its right. Top-aligned so multi-line
   keyword wraps don't push the label off-centre. */
.search-help-group {
    display: grid;
    grid-template-columns: 66px 1fr;
    align-items: start;
    gap: 10px;
    margin-bottom: 6px;
}

.search-help-group:last-child {
    margin-bottom: 0;
}

.search-help-group-title {
    font-size: 11px;
    font-weight: 600;
    color: var(--text-dim);
    text-align: left;
    padding-top: 2px;
    line-height: 1.4;
}

.search-help-words {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

/* Keywords: high-contrast monospace chips. Light text on a subtle filled
   chip — readable, not the low-contrast blue-on-blue from before. */
.search-help-word {
    font-family: var(--mono, ui-monospace, monospace);
    font-size: 10.5px;
    color: var(--text);
    background: var(--bg-hover);
    border: 1px solid var(--border-soft);
    border-radius: 4px;
    padding: 1px 6px;
    white-space: nowrap;
    line-height: 1.5;
}

/* filter chips */

.filters {
    display: flex;
    gap: 4px;
    margin-top: 11px;
    overflow-x: auto;
    /* Reserve the pinned + button's footprint when scrolling chips into
       view, so the active chip parks just LEFT of it, never beneath it. */
    scroll-padding-right: 36px;
    scrollbar-width: none;
}

.filters::-webkit-scrollbar { display: none; }

.chip {
    background: var(--bg-elev);
    color: var(--text-dim);
    border: 1px solid transparent;
    border-radius: 4px;
    padding: 3px 9px;
    font-size: 11px;
    cursor: pointer;
    white-space: nowrap;
    font-family: inherit;
    transition: all var(--dur-fast) var(--ease-out);
}

.chip:hover {
    color: var(--text);
    background: var(--bg-hover);
}

.chip.active {
    background: var(--accent-soft);
    color: var(--accent);
    border-color: var(--accent);
}

/* Smart folder chips: visually similar to built-in chips but carry the
   smart folder's accent colour via a small dot. Active state uses the
   folder's colour instead of the default accent. */
.chip-smart {
    display: inline-flex;
    align-items: center;
    gap: 5px;
}

.chip-smart-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    flex-shrink: 0;
    opacity: 0.85;
}

.chip-smart.active {
    /* Tint background and border using the folder's colour rather than the
       default --accent. --smart-color is set inline on the chip element. */
    background: color-mix(in srgb, var(--smart-color) 18%, transparent);
    border-color: var(--smart-color);
    color: var(--text);
}

.chip-smart.active .chip-smart-dot {
    opacity: 1;
}

/* User-created custom chips use a small diamond marker instead of the round
   dot smart-folder chips use, so the two kinds of query chip are visually
   distinguishable at a glance. */
.chip-custom-dot {
    border-radius: 1px;
    width: 6px;
    height: 6px;
    transform: rotate(45deg);
}

/* ----- list ----- */

.list-wrap {
    flex: 1;
    overflow: hidden;
    position: relative;
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* Header lives above the scroll container. Hides its own horizontal overflow;
   we sync its scrollLeft to the list's scrollLeft via JS so the columns stay
   aligned when the list scrolls sideways. */
.list-header-wrap {
    flex-shrink: 0;
    overflow: hidden;
    background: var(--bg-elev);
    border-bottom: 1px solid var(--border);
    position: relative;
}

/* Separator overlay inside the header — same shape as the list overlay but
   sized to the header height. */
.col-separators.in-header {
    z-index: 0;
}

.list-scroll {
    flex: 1;
    overflow-x: auto;
    overflow-y: scroll;
    position: relative;
    /* `overflow-y: scroll` reserves the scrollbar gutter so content width
       doesn't shift when the scrollable height changes (e.g. on folder
       toggle). Horizontal stays auto since we don't always need it. */
}

/* Full-height dotted column separators. Drawn ON TOP of the rows (z-index
   above .list) so the line is continuous over item rows and empty space
   alike; pointer-events: none means it never intercepts clicks or hover.
   Each child div is one separator line at a specific x offset. */
.col-separators {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: max-content;
    min-width: 100%;
    pointer-events: none;
    z-index: 2;
}

.col-separator {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 0;
    border-left: 1px dashed var(--border-soft);
}

.list {
    position: relative;
    z-index: 1;
    outline: none;
    padding: 0;
    min-width: max-content;
}

.list-header {
    display: flex;
    align-items: stretch;
    min-width: max-content;
    user-select: none;
    position: relative;
    z-index: 1;
}

.list-header.is-empty {
    display: none;
}

.list-header-cell {
    padding: 6px 8px;
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-dim);
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex-shrink: 0;
    box-sizing: border-box;
    position: relative;
}

.list-header-cell.is-last-col {
    /* Rightmost data column flex-grows to fill remaining space, then
       horizontal scroll kicks in if total exceeds panel width. */
    flex: 1 0 auto;
}

/* Header label is now a button so the user can click to sort. Buttons get
   browser defaults we don't want, so we strip them and recreate a flat
   text-like appearance. */
.list-header-cell .header-label {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    background: none;
    border: none;
    padding: 0;
    margin: 0;
    cursor: pointer;
    font: inherit;
    color: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
    text-align: left;
    overflow: hidden;
    max-width: 100%;
}

.list-header-cell .header-label .header-label-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.list-header-cell .header-label:hover .header-label-text {
    color: var(--text);
}

/* Active sorted column: brighter text plus the visible arrow. */
.list-header-cell.is-sorted .header-label-text {
    color: var(--text);
}

.list-header-cell .col-sort-arrow {
    display: inline-flex;
    align-items: center;
    color: var(--accent);
    flex-shrink: 0;
}

/* Resize handle: a wide 18px invisible click target straddling the column
   border, with a visible affordance shown on hover. Wider than visually
   needed so the user can grab it without precision. */
.list-header-cell .col-resize-handle {
    position: absolute;
    top: 0;
    right: -9px;
    width: 18px;
    height: 100%;
    cursor: col-resize;
    z-index: 3;
}

/* The visible indicator inside the handle — a vertical bar centered on
   the column boundary, brightened to accent on hover. Always present but
   nearly invisible by default so you can see it's draggable. */
.list-header-cell .col-resize-handle::before {
    content: '';
    position: absolute;
    top: 4px;
    bottom: 4px;
    left: 50%;
    width: 2px;
    transform: translateX(-50%);
    background: var(--text-faint);
    opacity: 0;
    border-radius: 1px;
    transition: opacity var(--dur-fast), background var(--dur-fast) var(--ease-out);
}

.list-header-cell .col-resize-handle:hover::before {
    opacity: 1;
    background: var(--accent);
}

/* Two tiny grip marks centered horizontally on the bar, hinting at
   draggability. Drawn via box-shadow on the same pseudo-element. */
.list-header-cell .col-resize-handle:hover::before {
    box-shadow:
        0 -3px 0 -1px var(--accent),
        0 3px 0 -1px var(--accent);
}

body.resizing-column {
    cursor: col-resize !important;
    user-select: none;
}

body.resizing-column * {
    cursor: col-resize !important;
}

/* While actively resizing, all handles show their indicator so the dragged
   one stays visually anchored even when the cursor moves off it. */
body.resizing-column .col-resize-handle::before {
    opacity: 0.4;
}

.list-header-cell:last-child {
    border-right: none;
}

.list-header-name {
    /* Width is set inline by JS, same as other columns. No flex-grow. */
}

.list-scroll::-webkit-scrollbar { width: 8px; height: 8px; }
.list-scroll::-webkit-scrollbar-track { background: transparent; }
.list-scroll::-webkit-scrollbar-corner { background: transparent; }
.list-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
.list-scroll::-webkit-scrollbar-thumb:hover { background: #444; }
.icon-grid-scroll::-webkit-scrollbar { width: 8px; height: 8px; }
.icon-grid-scroll::-webkit-scrollbar-track { background: transparent; }
.icon-grid-scroll::-webkit-scrollbar-corner { background: transparent; }
.icon-grid-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
.icon-grid-scroll::-webkit-scrollbar-thumb:hover { background: #444; }

.row {
    display: flex;
    align-items: center;
    cursor: default;
    border-left: 2px solid transparent;
    position: relative;
    min-width: max-content;
    /* Smooth the hover/selection state changes. Background and the left
       accent bar ease in rather than snapping, which reads as more refined
       on a surface you interact with constantly. Kept fast so it never
       feels laggy. */
    transition: background var(--dur-fast) var(--ease-out),
                border-left-color var(--dur-fast) var(--ease-out);
}

/* When the list is in virtualized mode, each row is positioned absolutely
   by index, so the parent .list reserves the full scroll height via its
   own min-height and rows transform into place. translateY uses the GPU,
   avoiding reflow on scroll re-renders. */
.list.is-virtualized .row {
    /* Full width of the list content, never narrower than the panel: an
       absolutely positioned row otherwise shrinks to its columns' width,
       leaving the zebra striping short of the right edge (invisible at
       default contrast, revealed by the Lighter theme). min-width keeps
       horizontal scroll working when columns exceed the panel. */
    width: 100%;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    min-width: max-content;
    will-change: transform;
}

.row.is-folder {
    cursor: pointer;
}

/* Subtle separator above each root-level item (depth 0), except the first.
   When folders open, their children sit between the separators, anchoring
   the root structure visually. */
.row.root-divider {
    border-top: 1px solid var(--border-soft);
}

/* Zebra stripes: every other row gets a slightly raised background so the
   eye can track horizontally across the data columns without losing its
   place. Both states are set explicitly so the alternation reads reliably
   (a transparent base row could otherwise blend with whatever is behind the
   list, making the stripe invisible). Kept subtle — texture, not gridding. */
.row {
    background: var(--bg);
}
.row.row-zebra {
    background: var(--bg-zebra);
}

/* Zebra depth, modelled the way it reads: the DARKER rows cast shadows
   onto the lighter ones, as if the light rows slide just underneath.
   Drop shadows project outward from the dark rows' top and bottom edges,
   and the dark rows sit one z-layer above their light neighbours so the
   cast paints over them. Contact at the junction is guaranteed by
   construction — the shadow originates exactly at the dark row's edge.
   Selected rows are excluded (the accent fill shouldn't cast or be
   dirtied); the drop-target rule's z-index: 2 keeps the drag highlight
   above this layering. */
.row:not(.row-zebra):not(.selected):not(.drop-target) {
    box-shadow: 0 5px 12px -3px rgba(0, 0, 0, 0.12),
                0 -5px 12px -3px rgba(0, 0, 0, 0.12);
    z-index: 1;
}

/* Cells inside a row. Border-rights are NOT drawn here; full-height dotted
   separators live in .col-separators (overlay inside .list-scroll) so the
   lines extend below the last row to the bottom of the scroll area. */
.row-cell {
    padding: 5px 8px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 11.5px;
    color: var(--text-faint);
    flex-shrink: 0;
    box-sizing: border-box;
    line-height: 1.4;
}

.row-cell em {
    font-style: italic;
}

/* The name cell holds the icon, chevron, depth indent and item name. Now
   has a fixed width matching the header's name column, set inline by JS. */
.row-name-cell {
    display: flex;
    align-items: center;
    gap: 7px;
    padding-right: 8px;
    color: var(--text);
}

/* Rightmost data column flex-grows to fill remaining horizontal space. */
.row-cell.is-last-col {
    flex: 1 0 auto;
}

.chevron {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 12px;
    height: 12px;
    color: var(--text-faint);
    flex-shrink: 0;
    transition: transform var(--dur-fast) ease;
}

.chevron.open {
    transform: rotate(90deg);
}

.chevron-spacer {
    display: inline-block;
    width: 12px;
    flex-shrink: 0;
}

.row.is-folder:hover .chevron {
    color: var(--text);
}

.row:hover,
.row.row-zebra:hover {
    background: var(--bg-hover);
    /* A whisper of the accent on the left edge on hover — a quiet preview of
       what selection looks like, so hovering feels connected to selecting
       rather than unrelated. Much fainter than the full selection bar. */
    border-left-color: color-mix(in srgb, var(--accent) 35%, transparent);
    /* Instant hover-IN: zero the transition while hovered so the highlight
       snaps on the moment the cursor arrives. The base .row keeps its
       transition, so moving AWAY still fades out smoothly. */
    transition: none;
}
.row.selected,
.row.row-zebra.selected {
    background: var(--accent-soft);
    border-left-color: var(--accent);
}
.row.selected:hover,
.row.row-zebra.selected:hover { background: var(--accent-soft); }
.row.missing .name { color: var(--missing); }

/* Icon container — neutral by default. Label colour painted directly onto
   the glyph via `color` (the SVGs use currentColor for their strokes/fills). */
.row-icon {
    flex-shrink: 0;
    /* Icon slot + inline-thumb box. Driven by --row-icon-size so it tracks
       the active row density (set per density on body, below). */
    width: var(--row-icon-size, 22px);
    height: var(--row-icon-size, 22px);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-dim);
}

/* Type glyphs scale with density via --row-glyph-size. CSS width/height
   override the SVG's own width/height attributes. The inline-thumb box also
   carries .row-icon and its image fills the slot, so thumbnails follow too. */
.row-icon svg { display: block; width: var(--row-glyph-size, 15px); height: var(--row-glyph-size, 15px); }

.row .name {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 12px;
    font-weight: 500;
    color: var(--text);
}

.row .inline-rename {
    /* Lives inside .row-name-cell, which has an explicit pixel width. The
       input must consume the cell's REMAINING space (after chevron/icon)
       and no more — flex:1 with min/max-width:0 lets it shrink to fit the
       fixed cell instead of inflating the row's max-content width, which
       was pushing the horizontal scrollbar and flinging the field left.
       width:0 is the key: it gives the flex item a zero base so it can
       never exceed its share of the fixed-width parent. */
    flex: 1 1 0;
    width: 0;
    min-width: 0;
    max-width: 100%;
    background: var(--bg-input);
    border: 1px solid var(--accent);
    color: var(--text);
    font-family: inherit;
    font-size: 12px;
    font-weight: 500;
    padding: 2px 5px;
    margin: -2px -5px;
    border-radius: 3px;
    outline: none;
}

/* Label colour painted onto the icon glyph itself. Uses the same --label-N
   custom properties as the Set Label swatches and the label editor, so the
   colour on the item exactly matches what you picked — single source of
   truth. (Previously these were a separate hardcoded, more-muted set, which
   made folders look duller than their swatch.) */
.row.label-1  .row-icon { color: var(--label-1); }
.row.label-2  .row-icon { color: var(--label-2); }
.row.label-3  .row-icon { color: var(--label-3); }
.row.label-4  .row-icon { color: var(--label-4); }
.row.label-5  .row-icon { color: var(--label-5); }
.row.label-6  .row-icon { color: var(--label-6); }
.row.label-7  .row-icon { color: var(--label-7); }
.row.label-8  .row-icon { color: var(--label-8); }
.row.label-9  .row-icon { color: var(--label-9); }
.row.label-10 .row-icon { color: var(--label-10); }
.row.label-11 .row-icon { color: var(--label-11); }
.row.label-12 .row-icon { color: var(--label-12); }
.row.label-13 .row-icon { color: var(--label-13); }
.row.label-14 .row-icon { color: var(--label-14); }
.row.label-15 .row-icon { color: var(--label-15); }
.row.label-16 .row-icon { color: var(--label-16); }

/* Subtle whole-row tint in the label colour, for ITEMS only (not folders).
   Implemented as a translucent overlay (::before) that sits ON TOP of the
   row's normal background rather than replacing it — so the zebra striping,
   hover and selection backgrounds all remain untouched underneath. Each
   labelled item exposes its colour as --row-label; the overlay paints that
   colour at low opacity. Folders never set --row-label, so they get no tint
   (they keep their coloured icon only). The overlay is non-interactive and
   sits behind the row's text/cells (z-index), so readability is preserved. */
.row:not(.is-folder).label-1  { --row-label: var(--label-1); }
.row:not(.is-folder).label-2  { --row-label: var(--label-2); }
.row:not(.is-folder).label-3  { --row-label: var(--label-3); }
.row:not(.is-folder).label-4  { --row-label: var(--label-4); }
.row:not(.is-folder).label-5  { --row-label: var(--label-5); }
.row:not(.is-folder).label-6  { --row-label: var(--label-6); }
.row:not(.is-folder).label-7  { --row-label: var(--label-7); }
.row:not(.is-folder).label-8  { --row-label: var(--label-8); }
.row:not(.is-folder).label-9  { --row-label: var(--label-9); }
.row:not(.is-folder).label-10 { --row-label: var(--label-10); }
.row:not(.is-folder).label-11 { --row-label: var(--label-11); }
.row:not(.is-folder).label-12 { --row-label: var(--label-12); }
.row:not(.is-folder).label-13 { --row-label: var(--label-13); }
.row:not(.is-folder).label-14 { --row-label: var(--label-14); }
.row:not(.is-folder).label-15 { --row-label: var(--label-15); }
.row:not(.is-folder).label-16 { --row-label: var(--label-16); }

/* The tint overlay. Rendered only for non-folder rows carrying a real label
   (label-1..16), and only when the "label tints" setting is on (the body lacks
   .label-tints-off). Unlabelled rows (label-0) and folders are not matched, so
   no overlay is created for them. Pointer-events none so it never intercepts
   clicks; low opacity so it reads as a hint over whatever background — base,
   zebra, hover, selection — sits beneath it. */
body:not(.label-tints-off) .row:not(.is-folder).label-1::before,
body:not(.label-tints-off) .row:not(.is-folder).label-2::before,
body:not(.label-tints-off) .row:not(.is-folder).label-3::before,
body:not(.label-tints-off) .row:not(.is-folder).label-4::before,
body:not(.label-tints-off) .row:not(.is-folder).label-5::before,
body:not(.label-tints-off) .row:not(.is-folder).label-6::before,
body:not(.label-tints-off) .row:not(.is-folder).label-7::before,
body:not(.label-tints-off) .row:not(.is-folder).label-8::before,
body:not(.label-tints-off) .row:not(.is-folder).label-9::before,
body:not(.label-tints-off) .row:not(.is-folder).label-10::before,
body:not(.label-tints-off) .row:not(.is-folder).label-11::before,
body:not(.label-tints-off) .row:not(.is-folder).label-12::before,
body:not(.label-tints-off) .row:not(.is-folder).label-13::before,
body:not(.label-tints-off) .row:not(.is-folder).label-14::before,
body:not(.label-tints-off) .row:not(.is-folder).label-15::before,
body:not(.label-tints-off) .row:not(.is-folder).label-16::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--row-label);
    opacity: 0.12;
    pointer-events: none;
    z-index: 0;
}

/* Keep the row's content above the tint overlay. */
.row > * {
    position: relative;
    z-index: 1;
}

/* New-item dots off (Settings > Appearance). Present-when-off class on body,
   mirroring label-tints-off; hides dots already mounted in the virtualized
   list without a row rebuild. */
body.new-dots-off .row-new-dot,
body.new-dots-off .icon-new-dot {
    display: none;
}

/* Smart folders colour their icon from their own --smart-color (the colour
   chosen in the smart-folder editor), not the AE label. This rule comes after
   the label rules so it wins for smart folders regardless of their underlying
   AE label value. */
/* Smart folder icons keep the default icon colour — the sparkle glyph is
   the smart cue; tinting the icon read as the label changing to blue. */

/* drag state */

.row.dragging {
    opacity: 0.5;
}

.row.drop-target {
    background: var(--accent-soft) !important;
    box-shadow: inset 0 0 0 1px var(--accent);
    /* Rows with .root-divider carry a 1px border-top, making them 1px
       taller than the measured row height — so the following row overlaps
       this row's bottom edge and was painting over the highlight's bottom
       line. Lifting the highlighted row keeps the full outline visible. */
    z-index: 2;
}

.row.drop-target .row-icon,
.row.drop-target .chevron {
    color: var(--accent);
}

/* custom drag cue — a soft glow that tracks the cursor inside the panel.
   Centred on the cursor with translate(-50%, -50%) so the cursor sits
   exactly in the glow's bright centre. Naturally clips at the panel edge,
   which gives a tasteful fade as you cross out toward AE. */

.drag-glow {
    position: fixed;
    width: 56px;
    height: 56px;
    pointer-events: none;
    z-index: 9999;
    transform: translate(-50%, -50%);
    background: radial-gradient(
        circle at center,
        rgba(91, 141, 239, 0.55) 0%,
        rgba(91, 141, 239, 0.25) 30%,
        rgba(91, 141, 239, 0.08) 60%,
        rgba(91, 141, 239, 0) 100%
    );
    border-radius: 50%;
    animation: drag-glow-pulse 1.4s ease-in-out infinite;
}

/* Alt held during a drag = replace mode. The glow shifts to a warm orange
   (the --missing hue) so it's visibly different from the add-to-comp blue
   before the cursor ever crosses into AE. */
.drag-glow.drag-replace {
    background: radial-gradient(
        circle at center,
        rgba(217, 119, 87, 0.55) 0%,
        rgba(217, 119, 87, 0.25) 30%,
        rgba(217, 119, 87, 0.08) 60%,
        rgba(217, 119, 87, 0) 100%
    );
}

@keyframes drag-glow-pulse {
    0%, 100% { opacity: 0.85; transform: translate(-50%, -50%) scale(1); }
    50%      { opacity: 1;    transform: translate(-50%, -50%) scale(1.08); }
}

body.dragging-active {
    user-select: none;
    -webkit-user-select: none;
    cursor: grabbing;
}

body.dragging-active * {
    cursor: grabbing !important;
}

/* ----- empty state ----- */

.empty {
    position: absolute;
    inset: 0;
    /* Purely informational — must NEVER intercept clicks. Spanning inset:0
       it sits over the grid's breadcrumb bar, and with default pointer
       events it was eating the back button whenever shown. */
    pointer-events: none;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    color: var(--text-faint);
    padding: 28px;
    text-align: center;
    animation: empty-rise 0.4s var(--ease-out);
}

/* Emblem: a large folder with a magnifying glass that slowly orbits it.
   The folder is the still anchor; the magnifier drifts on an organic path
   formed by two independent sine motions (X and Y with different periods
   and phases), so it never traces a plain circle. */
.empty-emblem {
    position: relative;
    width: 140px;
    height: 91px;
    margin-bottom: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: visible;
}

.empty-folder {
    color: var(--text-faint);
    opacity: 0.5;
    width: 105px;
    height: 88px;
}

/* The magnifier drift is split across two elements so the two transforms
   compose (a single element can't run two independent transform animations
   — the second would override the first). The outer orbit element handles
   the horizontal sine, the inner svg handles the vertical sine. Different
   durations (9s vs 7s) keep them out of phase for an organic, non-circular
   path; different amplitudes (X wider) give an elongated orbit. */
.empty-magnifier-orbit {
    position: absolute;
    left: 50%;
    top: 50%;
    margin-left: -14px;
    margin-top: -14px;
    animation: mag-drift-x 9s ease-in-out infinite;
}

.empty-magnifier {
    display: block;
    color: var(--text-dim);
    opacity: 0.9;
    width: 28px;
    height: 28px;
    animation: mag-drift-y 7s ease-in-out infinite;
}

/* Tilt rotor (the <g> inside the svg). Tilt follows VELOCITY, not position:
   the X drift is at its fastest mid-travel and momentarily still at the
   extremes, so the glass leans hardest through the middle and stands
   upright at each turn-around — synced to the same 9s cycle as the drift.
   Per-segment ease-in-out gives the tilt the same smooth sine-like shape
   as the motion. transform-origin is the lens centre in viewBox units, so
   the handle swings while the glass stays planted. */
.mag-rotor {
    transform-origin: 13px 13px;
    animation: mag-tilt 9s ease-in-out infinite;
}

@keyframes mag-tilt {
    0%   { transform: rotate(0deg); }
    25%  { transform: rotate(15deg); }
    50%  { transform: rotate(0deg); }
    75%  { transform: rotate(-15deg); }
    100% { transform: rotate(0deg); }
}

@keyframes mag-drift-x {
    0%   { transform: translateX(-32px); }
    50%  { transform: translateX(36px); }
    100% { transform: translateX(-32px); }
}

@keyframes mag-drift-y {
    0%   { transform: translateY(-20px); }
    50%  { transform: translateY(24px); }
    100% { transform: translateY(-20px); }
}

.empty-title {
    font-size: 16px;
    font-weight: 600;
    color: var(--text);
    margin-top: 16px;
    margin-bottom: 6px;
    letter-spacing: 0.01em;
}

.empty-sub {
    /* 12px against the 16px title is a 0.75 ratio — a clean, conventional
       primary/secondary step. Combined with the lighter weight and dimmer
       colour, the subtext reads as clearly subordinate to the heading. */
    font-size: 12px;
    color: var(--text-faint);
    line-height: 1.5;
}

@keyframes empty-rise {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ----- startup error screen ----- */

.startup-error {
    position: fixed;
    inset: 0;
    z-index: 12000;
    display: none;
    align-items: center;
    justify-content: center;
    background: var(--bg);
    padding: 28px;
}

.startup-error.visible {
    display: flex;
    animation: empty-rise 0.3s var(--ease-out);
}

.startup-error-inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    max-width: 280px;
}

.startup-error-icon {
    color: var(--missing);
    opacity: 0.9;
    margin-bottom: 16px;
}

.startup-error-title {
    font-size: 15px;
    font-weight: 600;
    color: var(--text);
    margin-bottom: 8px;
}

.startup-error-sub {
    font-size: 12px;
    color: var(--text-faint);
    line-height: 1.5;
    margin-bottom: 14px;
}

.startup-error-code {
    font-family: 'SFMono-Regular', Menlo, Consolas, monospace;
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.04em;
    color: var(--text);
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 6px 12px;
    margin-bottom: 10px;
}

.startup-error-detail {
    font-size: 10.5px;
    color: var(--text-faint);
    font-family: 'SFMono-Regular', Menlo, Consolas, monospace;
    line-height: 1.4;
    max-width: 260px;
    word-break: break-word;
    margin-bottom: 16px;
    opacity: 0.7;
}

.startup-error-retry {
    background: var(--bg-elev);
    border: 1px solid var(--border);
    color: var(--text);
    font-size: 12px;
    padding: 7px 16px;
    border-radius: 6px;
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.startup-error-retry:hover {
    background: var(--bg-hover);
    border-color: var(--text-dim);
}

/* ----- statusbar ----- */

.statusbar {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: nowrap;
    padding: 6px 10px;
    border-top: 1px solid var(--border-soft);
    background: var(--bg);
    font-size: 11px;
    color: var(--text-faint);
    /* Fixed height, and never wrap. When the panel is narrow the two groups
       must shrink/clip rather than wrap onto a second line — wrapping is what
       made the bar double in height and shove the buttons up. height (not just
       min-height) plus nowrap keeps it rigidly one row in every scenario. */
    height: 34px;
    min-height: 34px;
    box-sizing: border-box;
    overflow: hidden;
}

.actions {
    display: flex;
    gap: 2px;
    flex-shrink: 0;
}

.icon-btn {
    background: none;
    border: none;
    color: var(--text-dim);
    cursor: pointer;
    padding: 4px 6px;
    border-radius: 3px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.icon-btn:hover {
    background: var(--bg-hover);
    color: var(--text);
}

.icon-btn:active {
    background: var(--bg-active);
}

/* Shy toggle in its active (shy-on) state: accent-tinted so it's clearly
   "engaged", like AE's shy/solo switches. */
.shy-toggle.is-active {
    background: color-mix(in srgb, var(--accent) 22%, transparent);
    color: var(--accent);
}
.shy-toggle.is-active:hover {
    background: color-mix(in srgb, var(--accent) 30%, transparent);
    color: var(--accent);
}

/* ----- context menu ----- */

.context-menu {
    position: fixed;
    background: var(--bg-elev);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 4px 0;
    min-width: 180px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
    z-index: 1000;
    font-size: 12px;
}

.ctx-item {
    padding: 6px 14px;
    cursor: default;
    display: flex;
    align-items: center;
    justify-content: space-between;
    color: var(--text);
}

.ctx-item:hover {
    background: var(--accent);
    color: white;
}

/* Delete / destructive entries. Red text matching the confirm-modal delete
   button (--danger), with a red-tinted hover instead of the blue accent so
   it reads clearly as a destructive action. */
.ctx-item.danger {
    color: var(--danger);
}

.ctx-item.danger:hover {
    background: rgba(224, 108, 108, 0.15);
    color: var(--danger);
}

.ctx-item.disabled {
    color: var(--text-faint);
    pointer-events: none;
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding-top: 4px;
    padding-bottom: 4px;
}

.ctx-shortcut {
    font-size: 10.5px;
    color: var(--text-faint);
    margin-left: 24px;
}

.ctx-item:hover .ctx-shortcut { color: rgba(255,255,255,0.7); }

.ctx-sep {
    height: 1px;
    background: var(--border);
    margin: 4px 0;
}

.ctx-submenu {
    position: relative;
}

.ctx-submenu::after {
    content: '▾';
    color: var(--text-faint);
    font-size: 10px;
}

/* Submenus (label picker + where-used) now drop DOWN over the menu items
   below them rather than flying out to the side — that kept going off the
   edge of a narrow panel. They sit slightly right and slightly down from
   the parent row, fade in, and float above a dimmed backdrop that darkens
   the rest of the menu (same idea as the settings backdrop). */
.ctx-labels,
.ctx-where-used-list,
.ctx-children {
    /* Instant pop on/off via visibility. The flyout OVERLAPS the parent
       entry by 2px rather than floating below it: with no gap there is no
       hover dead zone, so however slowly the cursor crosses from the entry
       into the flyout, :hover never drops. (Earlier attempts bridged the
       gap with pseudo-elements; that failed because the flyout can open
       upward, and overflow on the Where Used list clips its pseudo-elements
       anyway. No gap means nothing to bridge.) */
    visibility: hidden;
    position: absolute;
    left: 14px;
    top: calc(100% - 2px);
    z-index: 3;
    background: var(--bg-elev);
    border: 1px solid var(--border);
    border-radius: 7px;
    box-shadow: 0 10px 28px rgba(0,0,0,0.5);
}

.ctx-labels {
    padding: 7px;
}

/* Generic submenu flyout: plain clickable entries (Create Proxy, Set Proxy). */
.ctx-children {
    min-width: 110px;
    padding: 4px;
}

/* Entries inside the flyout match the Where Used styling (compact, rounded,
   soft-accent hover) rather than the full-width top-level menu rows. */
.ctx-children .ctx-item {
    padding: 6px 9px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 12px;
}

.ctx-children .ctx-item:hover {
    background: var(--accent-soft);
    color: var(--accent);
}

.ctx-children .ctx-item.danger:hover {
    background: rgba(224, 108, 108, 0.12);
    color: var(--danger);
}

.ctx-label-grid {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 5px;
}

.ctx-where-used-list {
    min-width: 170px;
    max-width: 240px;
    max-height: 300px;
    overflow-y: auto;
    padding: 4px;
}

.ctx-where-used-list::-webkit-scrollbar { width: 8px; height: 8px; }
.ctx-where-used-list::-webkit-scrollbar-track { background: transparent; }
.ctx-where-used-list::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
.ctx-where-used-list::-webkit-scrollbar-thumb:hover { background: #444; }

/* On hover, reveal and animate into place. pointer-events re-enabled so
   the items are clickable. The fade is opacity-ONLY and in-only: the
   flyout's box appears at full size instantly (visibility), so the 2px
   overlap anti-dead-zone fix above keeps working exactly as designed, and
   there is no fade-out — a lingering semi-visible flyout would sit over
   the menu items it overlaps and intercept their clicks. */
.ctx-submenu:hover .ctx-labels,
.ctx-submenu:hover .ctx-where-used-list,
.ctx-submenu:hover .ctx-children {
    visibility: visible;
    animation: ctx-flyout-in 0.09s ease;
}
@keyframes ctx-flyout-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* Dim backdrop over the whole menu while a submenu is open. Toggled by JS
   adding .submenu-open to the menu. Sits below the active submenu (z 3)
   but above the other items, darkening them. */
.context-menu .ctx-dim {
    display: none;
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
    border-radius: inherit;
    z-index: 2;
    pointer-events: none;
}
.context-menu.submenu-open .ctx-dim {
    display: block;
}
/* The hovered submenu's parent row stays above the dim so it reads as the
   active item. */
.context-menu.submenu-open .ctx-submenu:hover {
    z-index: 3;
    position: relative;
}

.ctx-label-swatch {
    width: 20px;
    height: 20px;
    border-radius: 4px;
    cursor: pointer;
    border: 1px solid rgba(255,255,255,0.1);
}

.ctx-label-swatch:hover { transform: scale(1.1); }
.ctx-label-swatch.none {
    background: repeating-linear-gradient(45deg, #333 0, #333 3px, #222 3px, #222 6px);
}

/* Settings cog tile — same footprint as a swatch, sits as the last cell
   of the grid. Opens AE's label preferences. */
.ctx-label-cog {
    width: 20px;
    height: 20px;
    border-radius: 4px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-dim);
    border: 1px solid var(--border-soft);
    transition: background var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast) var(--ease-out);
}

.ctx-label-cog:hover {
    background: var(--bg-hover);
    color: var(--text);
    border-color: var(--border);
}

.ctx-wu-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 6px 9px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 12px;
    color: var(--text);
}

.ctx-wu-item:hover {
    background: var(--accent-soft);
    color: var(--accent);
}

.ctx-wu-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ctx-wu-count {
    color: var(--text-faint);
    font-size: 11px;
    flex-shrink: 0;
}

.ctx-wu-loading,
.ctx-wu-empty {
    padding: 8px 9px;
    font-size: 11.5px;
    color: var(--text-faint);
    white-space: nowrap;
}

/* ----- in-panel confirmation modal ----- */
.modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 10000;
    animation: modal-fade-in 0.12s ease-out;
}

@keyframes modal-fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

.modal {
    background: var(--bg-elev);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 18px 18px 14px;
    min-width: 280px;
    max-width: 90%;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
}

/* Label colour editor modal: a list of the 16 labels with a colour block,
   name, and hex. Clicking a row opens AE's picker. */
.label-editor-modal {
    width: 320px;
    max-width: 92%;
    padding: 0;
    display: flex;
    flex-direction: column;
    max-height: 80vh;
}

.label-editor-header {
    padding: 15px 16px 12px;
    border-bottom: 1px solid var(--border-soft);
}

.label-editor-title {
    font-size: 15px;
    font-weight: 600;
    color: var(--text);
}

.label-editor-body {
    padding: 8px;
    overflow-y: auto;
    flex: 1;
}

.label-editor-body::-webkit-scrollbar { width: 8px; height: 8px; }
.label-editor-body::-webkit-scrollbar-track { background: transparent; }
.label-editor-body::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
.label-editor-body::-webkit-scrollbar-thumb:hover { background: #444; }

.label-editor-row {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 7px 9px;
    border-radius: 6px;
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-out);
}

.label-editor-row:hover {
    background: var(--bg-hover);
}

.label-editor-swatch {
    width: 22px;
    height: 22px;
    border-radius: 5px;
    flex-shrink: 0;
    border: 1px solid rgba(255, 255, 255, 0.12);
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);
}

.label-editor-name {
    flex: 1;
    font-size: 12.5px;
    color: var(--text);
}

.label-editor-hex {
    font-family: var(--mono, ui-monospace, monospace);
    font-size: 11px;
    color: var(--text-dim);
    letter-spacing: 0.02em;
}

.label-editor-footer {
    display: flex;
    border-top: 1px solid var(--border);
}

/* Full-width buttons with dividers between, matching the app's confirm
   dialog and smart-folder modal footers. */
.label-editor-footer .confirm-btn {
    flex: 1;
    background: none;
    border: none;
    color: var(--text);
    padding: 11px 0;
    font-size: 12.5px;
    font-family: inherit;
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-out);
}

.label-editor-footer .confirm-btn + .confirm-btn {
    border-left: 1px solid var(--border);
}

.modal-title {
    color: var(--text);
    font-size: 13px;
    font-weight: 600;
    margin-bottom: 8px;
    word-break: break-word;
}

.modal-message {
    color: var(--text);
    font-size: 12.5px;
    line-height: 1.5;
    margin-bottom: 12px;
    word-break: break-word;
}

.modal-detail {
    color: var(--text-dim);
    font-size: 11.5px;
    line-height: 1.45;
    margin-bottom: 16px;
    word-break: break-word;
    padding: 8px 10px;
    background: var(--bg);
    border-radius: 4px;
    border-left: 2px solid var(--border);
}

.modal-input {
    width: 100%;
    background: var(--bg-input);
    border: 1px solid var(--border);
    color: var(--text);
    font-size: 13px;
    font-family: inherit;
    padding: 8px 10px;
    border-radius: 5px;
    outline: none;
    margin-bottom: 16px;
    box-sizing: border-box;
}

.modal-input:focus {
    border-color: var(--accent);
}

.modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}

.modal-btn {
    background: var(--bg);
    border: 1px solid var(--border);
    color: var(--text);
    padding: 6px 14px;
    border-radius: 5px;
    font-size: 12px;
    font-family: inherit;
    cursor: pointer;
    transition: background var(--dur-fast), border-color var(--dur-fast) var(--ease-out);
}

.modal-btn:hover {
    background: var(--bg-hover);
}

.modal-btn:focus {
    outline: none;
    border-color: var(--accent);
}

.modal-confirm {
    background: var(--danger);
    border-color: var(--danger);
    color: white;
}

.modal-confirm:hover {
    background: #c45757;
    border-color: #c45757;
}

.modal-confirm:focus {
    border-color: white;
    box-shadow: 0 0 0 1.5px var(--danger);
}

.modal-primary {
    background: var(--accent);
    border-color: var(--accent);
    color: white;
}

.modal-primary:hover {
    background: #4a7ad9;
    border-color: #4a7ad9;
}

.modal-primary:focus {
    border-color: white;
    box-shadow: 0 0 0 1.5px var(--accent);
}


/* ----- confirm modal (delete-style with strong hierarchy) ----- */

.confirm-modal {
    /* Override base .modal padding — we want zero padding so the button row
       can sit edge-to-edge against the modal border. */
    padding: 0;
    width: 320px;
    min-width: 280px;
    overflow: hidden;
}

.confirm-body {
    padding: 20px 22px 18px;
}

.confirm-title {
    color: var(--text);
    font-size: 15px;
    font-weight: 600;
    line-height: 1.3;
    margin-bottom: 6px;
    word-break: break-word;
    /* Truncate very long names to two lines. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.confirm-question {
    color: var(--text-dim);
    font-size: 12.5px;
    line-height: 1.4;
    margin-bottom: 12px;
}

.confirm-detail {
    color: var(--text-faint);
    font-size: 11.5px;
    line-height: 1.5;
    word-break: break-word;
    /* Path lists pass newline-separated lines; render them as lines. */
    white-space: pre-line;
}

.confirm-buttons {
    display: flex;
    border-top: 1px solid var(--border);
}

.confirm-btn {
    flex: 1;
    background: none;
    border: none;
    color: var(--text);
    padding: 11px 0;
    font-size: 12.5px;
    font-family: inherit;
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-out);
}

.confirm-btn + .confirm-btn {
    border-left: 1px solid var(--border);
}

.confirm-cancel:hover {
    background: var(--bg-hover);
}

.confirm-danger {
    color: var(--danger);
    font-weight: 500;
}

.confirm-danger:hover {
    background: rgba(224, 108, 108, 0.12);
}

.confirm-danger:focus {
    background: rgba(224, 108, 108, 0.18);
    outline: none;
}

.confirm-primary {
    color: var(--accent);
    font-weight: 500;
}

.confirm-primary:hover {
    background: rgba(91, 141, 239, 0.12);
}

.confirm-primary:focus {
    background: rgba(91, 141, 239, 0.18);
    outline: none;
}

/* Default-action emphasis. The dialog keeps focus on the key trap (so AE
   delivers Enter/Escape), so the primary button can't carry a :focus ring.
   is-default reproduces that visual cue: this is the button Enter triggers. */
.confirm-danger.is-default { background: rgba(224, 108, 108, 0.18); }
.confirm-primary.is-default { background: rgba(91, 141, 239, 0.18); }
.rl-go.is-default { background: rgba(91, 141, 239, 0.18); }

/* ----- relink confirmation modal ----- */

.rl-modal {
    padding: 0;
    width: 480px;
    min-width: 400px;
    max-width: 90vw;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    max-height: 72vh;
}

.rl-head {
    padding: 18px 22px 14px;
    border-bottom: 1px solid var(--border);
}

.rl-title {
    color: var(--text-dim);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin-bottom: 8px;
}

.rl-headline {
    color: var(--text);
    font-size: 15px;
    font-weight: 600;
    line-height: 1.3;
    margin-bottom: 6px;
}

.rl-loc {
    color: var(--text-faint);
    font-size: 11px;
    word-break: break-all;
    line-height: 1.4;
}

.rl-list {
    overflow-y: auto;
    flex: 1;
    padding: 0;
}

.rl-list::-webkit-scrollbar { width: 8px; height: 8px; }
.rl-list::-webkit-scrollbar-track { background: transparent; }
.rl-list::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
.rl-list::-webkit-scrollbar-thumb:hover { background: #444; }

.rl-section {
    padding-bottom: 4px;
}

.rl-section-head {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 9px 22px 7px;
    background: var(--bg);
    font-size: 10.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.rl-section-count {
    font-variant-numeric: tabular-nums;
    background: var(--bg-active);
    color: var(--text-dim);
    border-radius: 9px;
    padding: 1px 8px;
    font-size: 10px;
    font-weight: 600;
}

.rl-head-ok .rl-section-label { color: #91c094; }
.rl-head-bad .rl-section-label { color: var(--danger); }

.rl-row {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 7px 22px;
}

.rl-dot {
    flex: 0 0 auto;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    margin-top: 5px;
}

.rl-dot-ok { background: #91c094; }
.rl-dot-bad { background: var(--danger); }

.rl-row-text {
    flex: 1;
    min-width: 0;
}

.rl-row-name {
    color: var(--text);
    font-size: 12.5px;
    font-weight: 500;
    line-height: 1.3;
    word-break: break-all;
}

.rl-row-path {
    color: var(--text-faint);
    font-size: 10.5px;
    line-height: 1.35;
    margin-top: 1px;
    word-break: break-all;
}

.rl-row-note {
    color: var(--danger);
    opacity: 0.75;
    font-style: italic;
}

.rl-foot {
    display: flex;
    border-top: 1px solid var(--border);
}

.rl-btn {
    flex: 1;
    background: none;
    border: none;
    padding: 12px 0;
    font-size: 12.5px;
    font-family: inherit;
    font-weight: 500;
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-out);
}

.rl-btn + .rl-btn {
    border-left: 1px solid var(--border);
}

.rl-cancel {
    color: var(--danger);
}

.rl-cancel:hover {
    background: rgba(224, 108, 108, 0.12);
}

.rl-cancel:focus {
    background: rgba(224, 108, 108, 0.18);
    outline: none;
}

.rl-go {
    color: var(--accent);
}

.rl-go:hover {
    background: rgba(91, 141, 239, 0.12);
}

.rl-go:focus {
    background: rgba(91, 141, 239, 0.18);
    outline: none;
}

.rl-go:disabled {
    color: var(--text-faint);
    cursor: default;
    opacity: 0.6;
}

.rl-go:disabled:hover {
    background: none;
}

/* ----- smart folder modal ----- */

.smart-modal {
    /* Edge-to-edge layout like confirm-modal so the header gradient runs
       full-width. The inner sections handle their own padding. Wide enough
       that the value column stays readable beside the two fixed dropdown
       columns; clamped so narrow panels still fit. */
    padding: 0;
    width: 340px;
    max-width: calc(100vw - 24px);
    max-width: 92%;
    max-height: 80vh;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.smart-modal-header {
    /* Compact header — the conditions below are the focus, so the header
       stays low-key. Background gradient set inline from the picked colour.
       No divider line: it made the gap above the conditions card read
       heavier than the gap below. The header padding + body top padding
       are tuned so the visible gap above the card matches the 14px gap
       below it (the card's margin-bottom). */
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 16px 18px 0;
}

.smart-modal-body {
    flex: 1;
    overflow-y: auto;
    padding: 9px 16px 16px;
}

.smart-modal-icon {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
}

.smart-modal-icon svg {
    width: 26px;
    height: 26px;
}

.smart-modal-name {
    flex: 1;
    background: transparent;
    border: 1px solid transparent;
    color: var(--text);
    font-size: 20px;
    font-weight: 600;
    padding: 5px 8px;
    border-radius: 5px;
    outline: none;
    min-width: 0;
}

.smart-modal-name:hover {
    border-color: var(--border-soft);
}

.smart-modal-name:focus {
    border-color: var(--border);
    background: rgba(0, 0, 0, 0.25);
}

/* CONDITIONS — the primary block. Bordered card to give it weight and set
   it apart from the secondary appearance rows below. */
.smart-modal-conditions {
    background: var(--bg);
    border: 1px solid var(--border-soft);
    border-radius: 7px;
    padding: 12px;
    margin-bottom: 14px;
}

.smart-modal-conditions-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 10px;
}

.smart-modal-conditions-title {
    font-size: 13px;
    font-weight: 600;
    color: var(--text);
}

/* Inline match-mode toggle: "Match [all] [any]" sitting on the conditions
   header line. Small, doesn't compete with the title. */
.smart-modal-matchmode {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

.matchmode-prefix {
    font-size: 11px;
    color: var(--text-dim);
    margin-right: 2px;
}

.matchmode-btn {
    background: transparent;
    border: 1px solid var(--border-soft);
    color: var(--text-dim);
    font-size: 11px;
    padding: 2px 9px;
    border-radius: 3px;
    cursor: pointer;
    transition: background var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast) var(--ease-out);
}

.matchmode-btn:hover {
    color: var(--text);
}

.matchmode-btn.is-on {
    background: var(--accent-soft);
    color: var(--accent);
    border-color: var(--accent);
}

/* Secondary appearance rows (colour, chip toggle). Quieter than conditions:
   no card, just a small label + control on a row. */
.smart-modal-appearance {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 4px 2px;
    margin-bottom: 6px;
}

.smart-modal-mini-label {
    font-size: 11.5px;
    color: var(--text-dim);
    min-width: 52px;
}

/* Rules list */
.rules-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-bottom: 8px;
}

.rule-row {
    /* Wide: one line — field | operator | value | remove.
       Narrow (see media query): two lines — the value gets the WHOLE second
       line, because docked project panels are often too narrow for three
       readable columns side by side. */
    display: grid;
    grid-template-columns: 116px 104px minmax(0, 1fr) 22px;
    align-items: center;
    gap: 6px;
}

.rule-value-wrap {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

.rule-value-wrap input.rule-value { flex: 1; min-width: 0; width: 100%; }

@media (max-width: 470px) {
    .rule-row {
        grid-template-columns: 1fr 1fr 22px;
        row-gap: 5px;
    }
    .rule-row .rule-dd.rule-field,
    .rule-row .rule-dd.rule-op { width: auto; }
    .rule-row .rule-dd.rule-value-dd,
    .rule-row > input.rule-value,
    .rule-row .rule-value-wrap,
    .rule-row .rule-value-spacer {
        grid-column: 1 / -1;
        grid-row: 2;
    }
}

.rule-row select,
.rule-row input[type="text"],
.rule-row input[type="number"] {
    background: var(--bg-elev);
    border: 1px solid var(--border-soft);
    color: var(--text);
    font-size: 12px;
    padding: 6px 8px;
    border-radius: 4px;
    outline: none;
    min-width: 0;
}

.rule-row select {
    /* Make selects fit their content but not overflow the row. */
    flex: 0 1 auto;
    cursor: pointer;
    max-width: 130px;
}

.rule-row .rule-field { flex: 1 1 auto; min-width: 100px; }
.rule-row .rule-op    { flex: 0 1 auto; min-width: 80px;  }
.rule-row .rule-value { flex: 1 1 auto; min-width: 60px; max-width: none; }

.rule-row select:hover,
.rule-row input:hover {
    border-color: var(--border);
}

.rule-row select:focus,
.rule-row input:focus {
    border-color: var(--accent);
}

.rule-unit {
    font-size: 10.5px;
    color: var(--text-dim);
    margin-left: -2px;
}

.rule-value-spacer {
    flex: 1 1 auto;
}

.rule-remove {
    background: transparent;
    border: none;
    color: var(--text-dim);
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
    padding: 4px 7px;
    border-radius: 3px;
    flex-shrink: 0;
    transition: background var(--dur-fast), color var(--dur-fast) var(--ease-out);
}

.rule-remove:hover:not([disabled]) {
    background: rgba(239, 91, 91, 0.12);
    color: rgb(239, 91, 91);
}

.rule-remove[disabled] {
    opacity: 0.3;
    cursor: not-allowed;
}

.rule-add {
    background: transparent;
    border: 1px dashed var(--border-soft);
    color: var(--text-dim);
    font-size: 11.5px;
    padding: 6px 10px;
    border-radius: 4px;
    cursor: pointer;
    width: 100%;
    text-align: center;
    transition: border-color var(--dur-fast), color var(--dur-fast) var(--ease-out);
}

.rule-add:hover {
    border-color: var(--border);
    color: var(--text);
}

/* Colour swatches */
.smart-modal-colours {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

.colour-dot {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 2px solid transparent;
    cursor: pointer;
    padding: 0;
    transition: transform var(--dur-fast), border-color var(--dur-fast) var(--ease-out);
}

.colour-dot:hover {
    transform: scale(1.12);
}

.colour-dot.is-on {
    border-color: var(--text);
}

/* Toggle row in the modal (used for "Show as filter chip"). Compact
   checkbox + label with a hint line below. */
.smart-modal-toggle {
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    user-select: none;
}

.smart-modal-toggle input[type="checkbox"] {
    width: 14px;
    height: 14px;
    accent-color: var(--accent);
    cursor: pointer;
    margin: 0;
}

.smart-modal-toggle-label {
    font-size: 12px;
    color: var(--text);
}

/* Footer */
.smart-modal-footer {
    display: flex;
    border-top: 1px solid var(--border);
}

/* Buttons span full width with dividers between, matching the app's
   confirm dialog. Delete (when present) sits on the far left. */
.smart-modal-footer .confirm-btn {
    flex: 1;
    background: none;
    border: none;
    color: var(--text);
    padding: 11px 0;
    font-size: 12.5px;
    font-family: inherit;
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-out);
}

.smart-modal-footer .confirm-btn + .confirm-btn,
.smart-modal-footer-right .confirm-btn + .confirm-btn {
    border-left: 1px solid var(--border);
}

.smart-modal-footer-right {
    display: flex;
    flex: 2;
}

.smart-modal-footer-right .confirm-btn {
    flex: 1;
}

/* The right group needs a divider before it when Delete is present. */
.smart-modal-footer #sm-delete + .smart-modal-footer-right {
    border-left: 1px solid var(--border);
}

.smart-modal-footer .confirm-btn:hover {
    background: var(--bg-hover);
}

.smart-modal-footer .confirm-danger {
    color: var(--danger);
    font-weight: 500;
    flex: 1;
}

.smart-modal-footer .confirm-danger:hover {
    background: rgba(224, 108, 108, 0.12);
}

.smart-modal-footer .confirm-primary {
    color: var(--accent);
    font-weight: 500;
}

.smart-modal-footer .confirm-primary:hover {
    background: rgba(91, 141, 239, 0.12);
}

/* ----- smart folder row treatment ----- */

/* The folder itself: subtle accent border on the left side using the
   smart folder's assigned colour, plus a soft glow on hover and selection.
   The colour comes through as the CSS variable --smart-color set inline
   by buildRow. */
.row.smart-folder {
    /* Transparent fallback: with no colour chosen (the default) every
       --smart-color decoration disappears entirely; a picked colour is set
       inline by buildRow and lights them up. */
    --smart-color: transparent;
    position: relative;
    /* Faint colour wash across the row so smart folders read as distinct
       from regular folders. Applied as background-IMAGE (not the background
       shorthand) so it layers OVER the row's zebra background-color rather
       than replacing it — otherwise smart-folder rows lost their zebra
       stripe. */
    background-image: linear-gradient(
        to right,
        color-mix(in srgb, var(--smart-color) 9%, transparent) 0%,
        color-mix(in srgb, var(--smart-color) 3%, transparent) 40%,
        transparent 75%
    );
}

.row.smart-folder:hover {
    /* Strengthen the wash and add a soft inner glow on hover. */
    background-image: linear-gradient(
        to right,
        color-mix(in srgb, var(--smart-color) 15%, transparent) 0%,
        color-mix(in srgb, var(--smart-color) 6%, transparent) 45%,
        transparent 80%
    );
    box-shadow: inset 0 0 12px color-mix(in srgb, var(--smart-color) 12%, transparent);
}

.row.smart-folder.selected {
    /* Keep the standard selection background, layered with the smart glow. */
    background-color: var(--accent-soft);
    background-image: none;
    box-shadow: inset 0 0 14px color-mix(in srgb, var(--smart-color) 16%, transparent);
}

.row.smart-folder::before {
    /* Left-edge colour accent that runs the full height of the row.
       Subtle by default, more visible on hover and selection. */
    content: '';
    position: absolute;
    left: 0;
    top: 3px;
    bottom: 3px;
    width: 2px;
    background: var(--smart-color);
    border-radius: 1px;
    opacity: 0.65;
    /* Soft glow emanating from the accent bar. */
    box-shadow: 0 0 6px color-mix(in srgb, var(--smart-color) 55%, transparent);
    transition: opacity var(--dur-fast), width var(--dur-fast), box-shadow var(--dur-fast) var(--ease-out);
}

.row.smart-folder:hover::before {
    opacity: 0.95;
    width: 3px;
    box-shadow: 0 0 9px color-mix(in srgb, var(--smart-color) 75%, transparent);
}

.row.smart-folder.selected::before {
    opacity: 1;
    width: 3px;
    box-shadow: 0 0 10px color-mix(in srgb, var(--smart-color) 85%, transparent);
}

/* The smart folder icon picks up the accent colour and a faint glow. */
/* (Icon keeps the default colour — the cog badge is the smart cue. A
   second tint rule lived here and survived the first removal pass.) */

/* Items appearing INSIDE a smart folder's expanded query results get a
   subtle left-padding accent so they read as "in the smart folder context."
   Quieter than the folder itself — the folder is the headline; these are
   the contents. */
.row.smart-folder-child {
    --smart-color: #5b8def;
}

.row.smart-folder-child::before {
    content: '';
    position: absolute;
    left: 0;
    top: 6px;
    bottom: 6px;
    width: 1px;
    background: var(--smart-color);
    opacity: 0.3;
    border-radius: 1px;
}

/* ----- topbar layout updates ----- */

.topbar-row {
    display: flex;
    align-items: center;
    gap: 6px;
}

.topbar-row .search-wrap {
    flex: 1;
    min-width: 0;
}

.cog-btn {
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    padding: 0;
    background: none;
    border: none;
    color: var(--text-dim);
    cursor: pointer;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background var(--dur-fast), color var(--dur-fast) var(--ease-out);
}

.cog-btn:hover {
    background: var(--bg-hover);
    color: var(--text);
}

.cog-btn:active {
    background: var(--bg-active);
}

/* ----- bpc button in statusbar ----- */

.status-left {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;        /* allow shrinking below content size */
    overflow: hidden;    /* clip rather than push the action buttons */
}

/* The item count can truncate when space is tight. */
#status-count {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.bpc-btn {
    background: var(--bg-elev);
    border: 1px solid var(--border);
    color: var(--text-dim);
    font-family: inherit;
    font-size: 10.5px;
    padding: 2px 7px;
    border-radius: 3px;
    cursor: pointer;
    transition: background var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast) var(--ease-out);
    line-height: 1.3;
    white-space: nowrap;
    flex-shrink: 0;
}

.bpc-btn:hover {
    background: var(--bg-hover);
    color: var(--text);
    border-color: var(--border);
}

/* ----- settings slide-in panel ----- */

.settings-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    z-index: 9998;
    animation: settings-backdrop-in 0.36s cubic-bezier(0.65, 0, 0.15, 1);
}

.settings-backdrop.is-closing {
    animation: settings-backdrop-out 0.36s cubic-bezier(0.65, 0, 0.15, 1) forwards;
}

@keyframes settings-backdrop-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

@keyframes settings-backdrop-out {
    from { opacity: 1; }
    to { opacity: 0; }
}

.settings-panel {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: 92%;
    max-width: 460px;
    background: var(--bg);
    border-left: 1px solid var(--border);
    z-index: 9999;
    display: flex;
    flex-direction: column;
    /* S-curve with extended deceleration tail: gentle accel, sharper decel,
       long settle at the end. */
    animation: settings-panel-in 0.36s cubic-bezier(0.65, 0, 0.15, 1);
}

.settings-panel.is-closing {
    animation: settings-panel-out 0.36s cubic-bezier(0.65, 0, 0.15, 1) forwards;
}

@keyframes settings-panel-in {
    from { transform: translateX(100%); }
    to { transform: translateX(0); }
}

@keyframes settings-panel-out {
    from { transform: translateX(0); }
    to { transform: translateX(100%); }
}

.settings-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 14px;
    border-bottom: 1px solid var(--border-soft);
    flex-shrink: 0;
}

.settings-header h2 {
    font-size: 13px;
    font-weight: 600;
    color: var(--text);
    margin: 0;
}

.settings-close {
    background: none;
    border: none;
    color: var(--text-dim);
    cursor: pointer;
    padding: 5px;
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.settings-close:hover {
    background: var(--bg-hover);
    color: var(--text);
}

.settings-body {
    flex: 1;
    overflow-y: auto;
    padding: 14px;
}

.settings-body::-webkit-scrollbar { width: 8px; height: 8px; }
.settings-body::-webkit-scrollbar-track { background: transparent; }
.settings-body::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
.settings-body::-webkit-scrollbar-thumb:hover { background: #444; }

.settings-section {
    margin-bottom: 22px;
}

.settings-section-title {
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-faint);
    font-weight: 600;
    margin: 0 0 10px 4px;
}

.settings-row {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    padding: 12px 14px;
    background: var(--bg-elev);
    border: 1px solid var(--border-soft);
    border-radius: 6px;
}

.settings-row-text {
    flex: 1;
    min-width: 0;
}

.settings-row-title {
    color: var(--text);
    font-size: 12.5px;
    font-weight: 500;
    margin-bottom: 4px;
}

/* Hoverable setting title — a subtle dotted underline hints that hovering
   reveals an explanation. Kept understated so it doesn't clutter the row. */
.settings-title-help {
    border-bottom: 1px dotted var(--border);
    cursor: help;
}

.settings-title-help:hover {
    border-bottom-color: var(--text-dim);
}

/* Hover popover with the setting's explanation. Appended to document.body
   and positioned with fixed coordinates (so the settings panel can't clip
   it or trap it underneath). Styled like the search help popover. */
.settings-help-popover {
    position: fixed;
    z-index: 10001;
    width: 220px;
    max-width: calc(100vw - 12px);
    background: var(--bg-elev);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 10px 11px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
    font-size: 11.5px;
    font-weight: 400;
    line-height: 1.45;
    color: var(--text-dim);
    pointer-events: none;
}

.settings-row-desc {
    color: var(--text-faint);
    font-size: 11.5px;
    line-height: 1.45;
}

/* Settings action buttons — the same quiet-well language as the panel's
   inputs and chips: sunken surface, soft border, dim label that wakes on
   hover, gentle press. No hard theme-agnostic hexes. */
.settings-btn {
    flex-shrink: 0;
    background: var(--bg-input);
    border: 1px solid var(--border-soft);
    color: var(--text-dim);
    padding: 5px 13px;
    border-radius: 6px;
    font-family: inherit;
    font-size: 11px;
    font-weight: 500;
    letter-spacing: 0.01em;
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out),
                transform var(--dur-fast) var(--ease-out);
    align-self: center;
}

.settings-btn:hover {
    background: var(--bg-hover);
    border-color: var(--border);
    color: var(--text);
}

.settings-btn:active {
    transform: scale(0.97);
}

.settings-btn[disabled] {
    opacity: 0.4;
    cursor: default;
    pointer-events: none;
}

.settings-btn.installed {
    background: rgba(91, 141, 239, 0.12);
    border-color: rgba(91, 141, 239, 0.3);
    color: var(--accent);
}

/* Installed state — green outline on the row, button shows confirmation. */
.settings-row.is-installed {
    border-color: rgba(120, 192, 120, 0.45);
    background: rgba(120, 192, 120, 0.06);
}

.settings-row.is-installed .settings-row-desc {
    color: rgba(160, 210, 160, 0.85);
}

.settings-btn.is-success {
    background: rgba(120, 192, 120, 0.14);
    border-color: rgba(120, 192, 120, 0.4);
    color: #9dd29d;
    cursor: default;
}

.settings-btn.is-success:hover {
    background: rgba(120, 192, 120, 0.14);
    border-color: rgba(120, 192, 120, 0.4);
}

/* Back header for the secondary slide-in. */
.settings-header .back-btn {
    display: flex;
    align-items: center;
    gap: 6px;
    background: none;
    border: none;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 12.5px;
    font-weight: 500;
    cursor: pointer;
    padding: 4px 6px 4px 0;
    border-radius: 4px;
}

.settings-header .back-btn:hover {
    color: var(--text);
}

/* Hidden input that captures keystrokes. AE only delivers keys to CEP
   panels when a text input has focus, so we keep focus on this invisible
   element when the user is navigating the list. */
.key-trap {
    position: absolute;
    top: 0;
    left: 0;
    width: 1px;
    height: 1px;
    opacity: 0;
    border: none;
    padding: 0;
    margin: 0;
    background: transparent;
    pointer-events: none;
    caret-color: transparent;
    /* Critically: NOT display:none or visibility:hidden — those prevent
       focus from working at all. */
}

/* ----- preview area (above the search bar) ----- */

.preview {
    position: relative;
    height: 110px;
    flex-shrink: 0;
    padding: 10px;
    background: var(--bg);
    box-sizing: border-box;
    overflow: visible;
}

.preview-stack {
    position: relative;
    width: 100%;
    height: 100%;
}

/* The front card holds the actual content. Back cards are decorative
   when multi-selection is shown. */
.preview-card {
    position: absolute;
    inset: 0;
    background: var(--bg-elev);
    /* The card is the one persistent surface across the size toggle — its
       colour morphs (lighter grey <-> the large mode's alpha-dark) instead
       of popping with the layout swap. */
    transition: background-color 0.45s cubic-bezier(0.65, 0, 0.15, 1);
    border: 1px solid var(--border-soft);
    border-radius: 6px;
    box-sizing: border-box;
    transition: transform 0.18s cubic-bezier(0.65, 0, 0.15, 1),
                opacity 0.18s ease;
}

.preview-card-front {
    display: flex;
    gap: 14px;
    align-items: stretch;
    padding: 10px;
    z-index: 3;
}

.preview-card-back-1 {
    z-index: 2;
    transform: translate(6px, 4px) rotate(2deg) scale(0.97);
    opacity: 0;
    pointer-events: none;
}

.preview-card-back-2 {
    z-index: 1;
    transform: translate(-5px, 6px) rotate(-2.5deg) scale(0.95);
    opacity: 0;
    pointer-events: none;
}

.preview.is-multi .preview-card-back-1 { opacity: 1; }
.preview.is-multi .preview-card-back-2 { opacity: 0.85; }

.preview-thumb {
    flex-shrink: 0;
    width: 130px;
    height: 100%;
    /* Transparent, not a darker well: letterbox bars and image alpha show
       the panel background itself, so transparent images sit seamlessly.
       The large preview keeps its own background (declared below). */
    background: transparent;
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    position: relative;
}

.preview-thumb img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

.preview-thumb-placeholder {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-faint);
}

.preview-thumb-placeholder svg { display: block; }

.preview-info {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    /* Top-align rather than center: when the name wraps to two lines the
       stack (name + meta + used-in) is taller than the pane, and centering
       pushed the used-in line off the bottom edge where it was clipped.
       Centering vertically within the row is handled by the flex parent. */
    justify-content: center;
    gap: 5px;
    padding: 2px 2px 2px 0;
    max-height: 100%;
    overflow: visible;
}

/* When the name is two lines, the info block is at its tallest — anchor the
   stack to the top so nothing spills past the pane's bottom. */
.preview-info.tall {
    justify-content: flex-start;
}

.preview-name {
    color: var(--text);
    font-size: 14px;
    font-weight: 600;
    line-height: 1.25;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    word-break: break-word;
}

/* When the explicit two-line split is mounted, the -webkit-box clamp's
   overflow:hidden clips the lower line's descenders (g, y, p). The
   .two-line class (added by JS — CEP 11 has no :has()) switches this
   container to a plain block with visible vertical overflow so tails
   show; horizontal clipping still happens per .pn-line. */
.preview-name.two-line {
    display: block;
    overflow: visible;
}

.preview-meta {
    color: var(--text-faint);
    font-size: 11px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Last-known path line shown for missing footage (native-AE behaviour).
   Wraps fully so the whole path is always readable — break-all because
   paths have no natural break points. The reload-failure popup carries the
   full path too, but the pane shouldn't truncate. */
.preview-missing-path {
    color: var(--missing);
    font-size: 9.5px;
    line-height: 1.35;
    margin-top: 2px;
    white-space: normal;
    word-break: break-all;
}

/* Multi-selection badge in the corner of the front card. */
.preview-multi-badge {
    position: absolute;
    top: 8px;
    right: 8px;
    background: var(--accent);
    color: white;
    font-size: 10.5px;
    font-weight: 600;
    padding: 2px 7px;
    border-radius: 10px;
    z-index: 10;
    pointer-events: none;
    opacity: 0;
    transform: scale(0.85);
    transition: opacity 0.15s ease, transform 0.15s cubic-bezier(0.4, 0, 0.2, 1.4);
}

.preview.is-multi .preview-multi-badge {
    opacity: 1;
    transform: scale(1);
}

/* Empty state — project-info display when nothing has ever been previewed. */
/* The Project default state sits flush with the panel: a transparent
   thumb well (the old var(--bg) fill read as a grey block against the
   preview's own surface) and full-strength text — the dimmed treatment
   read as disabled rather than calm. */
.preview.preview-empty .preview-thumb {
    background: transparent;
}

/* Solid colour block in preview thumbnail. */
.preview-solid {
    width: 100%;
    height: 100%;
    border-radius: 3px;
}

/* ----- large preview mode -----
   Triggered by .preview-large on the preview section. The preview height
   becomes content-driven (auto), capped at 50vh so on small panels it
   doesn't dominate. The card-front becomes a normal flow element so it
   contributes to the preview's height — otherwise the absolute positioning
   from compact mode would let the search bar and list slide underneath.

   Layout inside large mode:
     - .preview-thumb spans full width, 16:9 aspect (capped by max-height).
     - .preview-info absolutely positions over the bottom-right of the thumb,
       with a vignette gradient backdrop for readability.
*/
.preview.preview-large {
    height: auto;
}

.preview.preview-large .preview-stack {
    height: auto;
}

.preview.preview-large .preview-card-front {
    position: relative;
    inset: auto;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
    /* The card chrome (background + border) reads as a visible box around
       the centered thumb when the panel is wider than the thumb's capped
       width. Drop it in large mode so only the panel background shows in
       the surrounding space. */
    background: transparent;
    border: none;
}

.preview.preview-large .preview-thumb {
    width: 100%;
    height: auto;
    aspect-ratio: 16 / 9;
    max-height: 45vh;
    border-radius: 6px;
    /* Transparent like the compact thumb, so image alpha and letterbox
       bars show the SAME panel tone in both preview sizes. */
    background: transparent;
    position: relative;
    /* When the panel is wider than max-height × 16/9 the browser caps the
       height and shrinks the width to maintain aspect-ratio. The element
       would otherwise sit on the left of the card-front. margin: 0 auto
       centers it within the available horizontal space. */
    margin-left: auto;
    margin-right: auto;
}

.preview.preview-large .preview-thumb img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

/* Placeholder icons (folder, comp, etc. when no thumbnail) scale up in
   large mode so they don't look tiny in the wider thumb area. */
.preview.preview-large .preview-thumb-placeholder {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
}

.preview.preview-large .preview-thumb-placeholder svg {
    width: 96px;
    height: 96px;
}

/* Info overlay: sits bottom-right of the thumb, with a vignette gradient
   running along the bottom so the text reads cleanly against any image. */
.preview.preview-large .preview-info {
    /* Anchor to the bottom of the card-front, but constrain width to match
       the centered thumb above. When the thumb shrinks to maintain aspect
       ratio against max-height, the info follows so the gradient sits on
       the image, not in empty space. */
    position: absolute;
    bottom: 0;
    /* Match the thumb's max width: aspect-ratio 16:9 × max-height 45vh */
    width: calc(45vh * 16 / 9);
    max-width: 100%;
    left: 50%;
    transform: translateX(-50%);
    padding: 22px 14px 10px;
    /* Panel-background tone (--bg = #1e1e1e; CEP's Chromium predates
       color-mix so the rgb triplet is hardcoded to match). Fully opaque at
       the bottom edge so the strip melts into the pane below, fading up. */
    background: linear-gradient(
        to top,
        var(--fade-full) 0%,
        var(--fade-full) 22%,
        var(--fade-70) 55%,
        var(--fade-0) 100%
    );
    text-align: right;
    pointer-events: none;
    flex: 0 0 auto;
    justify-content: flex-end;
    gap: 2px;
    box-sizing: border-box;
}

.preview.preview-large .preview-name {
    font-size: 14px;
    font-weight: 600;
    color: #fff;
    -webkit-line-clamp: 1;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: block;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

.preview.preview-large .preview-meta {
    font-size: 11px;
    color: rgba(255, 255, 255, 0.82);
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

/* Decorative back cards (multi-select effect) hidden in large mode —
   they were absolutely positioned for the fixed 110px height, don't make
   sense in a content-sized large layout. */
.preview.preview-large .preview-card-back-1,
.preview.preview-large .preview-card-back-2 {
    display: none;
}

/* The toggle button sits in the top-right corner of the preview area,
   shows the icon corresponding to the OTHER mode (i.e. what it will switch
   to on click). */
.preview-size-toggle {
    position: absolute;
    top: 6px;
    right: 6px;
    z-index: 20;
    width: 22px;
    height: 22px;
    border-radius: 4px;
    background: rgba(20, 20, 20, 0.7);
    border: 1px solid var(--border-soft);
    color: var(--text-dim);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    opacity: 0;
    transition: opacity var(--dur-fast), color var(--dur-fast), background var(--dur-fast) var(--ease-out);
    padding: 0;
}

.preview:hover .preview-size-toggle {
    opacity: 1;
}

.preview-size-toggle:hover {
    color: var(--text);
    background: rgba(40, 40, 40, 0.9);
}

/* Show only the icon for the OTHER mode. */
.preview-size-toggle .ico-compact,
.preview-size-toggle .ico-large {
    display: none;
}

.preview.preview-compact .preview-size-toggle .ico-large { display: block; }
.preview.preview-large .preview-size-toggle .ico-compact { display: block; }

/* Hide toggle entirely if preview is hidden. */
.preview.preview-hidden .preview-size-toggle {
    display: none;
}

/* ----- density modes (applied to body via settings) ----- */

/* Default = comfortable (--row-icon-size / --row-glyph-size fall back to
   22px / 15px in the .row-icon rule above). Compact and spacious shift both
   the icon-slot box and the glyph, so icons and inline thumbnails scale with
   the chosen density rather than just the row padding. */

body.density-compact {
    --row-icon-size: 19px;
    --row-glyph-size: 13px;
}
body.density-compact .row-cell {
    padding-top: 3px;
    padding-bottom: 3px;
    font-size: 11px;
}
body.density-compact .row .name {
    font-size: 11.5px;
}

body.density-spacious {
    --row-icon-size: 27px;
    --row-glyph-size: 18px;
}
body.density-spacious .row-cell {
    padding-top: 8px;
    padding-bottom: 8px;
    font-size: 12px;
}
body.density-spacious .row .name {
    font-size: 12.5px;
}

/* Preview area hidden — slide it out entirely so the list reclaims space. */
/* Show/hide is a breathing collapse, not a display snap. max-height caps
   sit just above each mode's real height so the motion tracks the visible
   collapse closely; visibility flips after the transition so the hidden
   pane can't intercept clicks. */
/* Caps sit just above each mode's real height (compact 110px, large
   ~45vh of thumb + chrome), so the max-height travel tracks the visible
   growth closely — minimal dead zone, maximal smoothness. */
.preview {
    max-height: 112px;
    overflow: hidden;
    transition: max-height 0.45s cubic-bezier(0.65, 0, 0.15, 1),
                opacity 0.25s cubic-bezier(0.65, 0, 0.15, 1),
                visibility 0s 0s;
}

.preview.preview-large {
    max-height: 48vh;
}

.preview.preview-hidden {
    max-height: 0;
    opacity: 0;
    visibility: hidden;
    transition: max-height 0.45s cubic-bezier(0.65, 0, 0.15, 1),
                opacity 0.25s cubic-bezier(0.65, 0, 0.15, 1),
                visibility 0s 0.45s;
}

/* ----- settings UI patterns (segmented, toggle, action row) ----- */

.settings-row-compact {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    padding: 10px 14px;
    background: var(--bg-elev);
    border: 1px solid var(--border-soft);
    border-radius: 6px;
}

.settings-row-compact + .settings-row-compact {
    margin-top: 8px;
}

.settings-row-compact .settings-row-text {
    flex: 1;
    min-width: 0;
}

.settings-row-compact .settings-row-title {
    color: var(--text);
    font-size: 12.5px;
    font-weight: 500;
    margin-bottom: 2px;
}

.settings-row-compact .settings-row-desc {
    color: var(--text-faint);
    font-size: 11px;
    line-height: 1.4;
}

/* Segmented control. */
.seg {
    display: inline-flex;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 2px;
    flex-shrink: 0;
}

.seg-btn {
    background: none;
    border: none;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 11.5px;
    font-weight: 500;
    padding: 4px 10px;
    border-radius: 4px;
    cursor: pointer;
    transition: background var(--dur-fast), color var(--dur-fast) var(--ease-out);
}

.seg-btn:hover {
    color: var(--text);
}

.seg-btn.is-active {
    background: var(--bg-elev);
    color: var(--text);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}

/* Toggle switch. */
.toggle {
    position: relative;
    width: 32px;
    height: 18px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 10px;
    cursor: pointer;
    flex-shrink: 0;
    transition: background 0.15s, border-color 0.15s var(--ease-out);
}

.toggle::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--text-dim);
    transition: transform 0.15s cubic-bezier(0.4, 0, 0.2, 1),
                background 0.15s;
}

.toggle.is-on {
    background: var(--accent);
    border-color: var(--accent);
}

.toggle.is-on::after {
    transform: translateX(14px);
    background: white;
}

/* Bottom-of-panel "Advanced settings" button. */
.settings-footer {
    margin-top: 18px;
    padding-top: 6px;
    border-top: 1px solid var(--border-soft);
}

.settings-advanced-btn {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    background: none;
    border: none;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 12.5px;
    font-weight: 500;
    padding: 12px 14px;
    cursor: pointer;
    border-radius: 6px;
    transition: background var(--dur-fast), color var(--dur-fast) var(--ease-out);
}

.settings-advanced-btn:hover {
    background: var(--bg-hover);
    color: var(--text);
}

.settings-advanced-btn .chev {
    color: var(--text-faint);
}

.settings-advanced-btn:hover .chev {
    color: var(--text);
}

/* ----- inline row meta (column system) ----- */

.row .row-meta {
    font-size: 10.5px;
    color: var(--text-faint);
    flex-shrink: 1;
    min-width: 0;
    margin-left: auto;
    padding-left: 8px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: flex;
    align-items: center;
    gap: 0;
}

.row .row-meta-sep {
    display: inline-block;
    padding: 0 5px;
    opacity: 0.5;
}

.row .row-meta em {
    font-style: italic;
    opacity: 0.85;
}

/* Tiny label colour swatch when the Label column is enabled in the row. */
.row .col-label-swatch {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 2px;
    vertical-align: middle;
    flex-shrink: 0;
}
/* Column swatches read the same --label-N variables as the right-click
   picker, row tints, and icon colouring — one palette everywhere. These were
   previously a separate hard-coded set that had drifted completely out of
   sync with the picker. */
.row .col-label-swatch.label-1  { background: var(--label-1); }
.row .col-label-swatch.label-2  { background: var(--label-2); }
.row .col-label-swatch.label-3  { background: var(--label-3); }
.row .col-label-swatch.label-4  { background: var(--label-4); }
.row .col-label-swatch.label-5  { background: var(--label-5); }
.row .col-label-swatch.label-6  { background: var(--label-6); }
.row .col-label-swatch.label-7  { background: var(--label-7); }
.row .col-label-swatch.label-8  { background: var(--label-8); }
.row .col-label-swatch.label-9  { background: var(--label-9); }
.row .col-label-swatch.label-10 { background: var(--label-10); }
.row .col-label-swatch.label-11 { background: var(--label-11); }
.row .col-label-swatch.label-12 { background: var(--label-12); }
.row .col-label-swatch.label-13 { background: var(--label-13); }
.row .col-label-swatch.label-14 { background: var(--label-14); }
.row .col-label-swatch.label-15 { background: var(--label-15); }
.row .col-label-swatch.label-16 { background: var(--label-16); }

/* ----- columns reorder UI (Advanced > Columns shown) ----- */

.settings-explain {
    color: var(--text-faint);
    font-size: 11px;
    margin-bottom: 10px;
    line-height: 1.5;
}

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

.col-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 12px;
    background: var(--bg-elev);
    border: 1px solid var(--border-soft);
    border-radius: 5px;
    cursor: default;
    transition: opacity var(--dur-fast), transform 0.06s var(--ease-out);
}

.col-row.is-off {
    opacity: 0.55;
}

.col-row.is-off .col-label {
    color: var(--text-dim);
}

.col-row.is-dragging,
.custom-chip-row.is-dragging {
    /* The row stays visible and moves into its new position live. A subtle
       lift (elevated background, accent border, soft shadow) marks it as the
       one being dragged. */
    background: var(--bg-elev);
    border-color: var(--accent);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
    position: relative;
    z-index: 2;
}

.col-handle {
    cursor: grab;
    color: var(--text-faint);
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
    padding: 2px;
    border-radius: 3px;
    transition: color var(--dur-fast), background var(--dur-fast) var(--ease-out);
}
.col-handle:hover {
    color: var(--text);
    background: var(--bg-hover);
}
.col-handle:active {
    cursor: grabbing;
}

/* While a reorder drag is in progress, suppress the hover glow on every
   handle so passing the cursor near other rows doesn't make them flicker
   white. The dragged row's own handle is kept lit via the rule below. */
.col-list.is-reordering .col-handle:hover,
.custom-chip-list.is-reordering .col-handle:hover {
    color: var(--text-faint);
    background: transparent;
}
.col-list.is-reordering .col-row.is-dragging .col-handle,
.custom-chip-list.is-reordering .custom-chip-row.is-dragging .col-handle {
    color: var(--text);
    background: var(--bg-hover);
}

.col-toggle {
    flex-shrink: 0;
}

.col-label {
    color: var(--text);
    font-size: 12.5px;
    font-weight: 500;
}

/* ----- custom filter chips list (in Settings → Filter Chips) ----- */

.custom-chip-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 8px;
}

.custom-chip-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px 8px 12px;
    background: var(--bg-elev);
    border: 1px solid var(--border-soft);
    border-radius: 5px;
    cursor: pointer;
    transition: border-color var(--dur-fast), background var(--dur-fast) var(--ease-out);
}

.custom-chip-row:hover {
    border-color: var(--border);
    background: var(--bg-hover);
}

.custom-chip-dot {
    width: 8px;
    height: 8px;
    border-radius: 1px;
    transform: rotate(45deg);
    flex-shrink: 0;
}

.custom-chip-name {
    flex: 1;
    color: var(--text);
    font-size: 12.5px;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Edit + delete buttons share a base style. Delete sits on the far right
   per the spec; edit hovers next to it for direct access. Both are quiet
   by default and colour up on hover. */
.custom-chip-edit,
.custom-chip-delete {
    background: transparent;
    border: none;
    padding: 4px 6px;
    border-radius: 3px;
    cursor: pointer;
    color: var(--text-dim);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background var(--dur-fast), color var(--dur-fast) var(--ease-out);
    flex-shrink: 0;
}

.custom-chip-edit:hover {
    color: var(--text);
    background: var(--bg-hover);
}

.custom-chip-delete:hover {
    color: rgb(239, 91, 91);
    background: rgba(239, 91, 91, 0.12);
}

.custom-chip-empty {
    padding: 14px 12px;
    text-align: center;
    color: var(--text-faint);
    font-size: 11.5px;
    background: var(--bg-elev);
    border: 1px dashed var(--border-soft);
    border-radius: 5px;
    margin-bottom: 8px;
}

.settings-add-btn {
    width: 100%;
    background: transparent;
    border: 1px dashed var(--border-soft);
    color: var(--text-dim);
    font-size: 12px;
    padding: 8px 12px;
    border-radius: 5px;
    cursor: pointer;
    font-family: inherit;
    transition: border-color var(--dur-fast), color var(--dur-fast) var(--ease-out);
}

.settings-add-btn:hover {
    border-color: var(--border);
    color: var(--text);
}

.settings-section-title {
    font-size: 10.5px;
    font-weight: 600;
    color: var(--text-dim);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-bottom: 8px;
}

/* ----- inline row thumbnails ----- */

/* When a row has a thumbnail, the icon slot becomes a fixed-aspect box
   containing the image. Same outer dimensions as the icon. */
.row-icon-thumb {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg);
    border-radius: 2px;
    overflow: hidden;
    box-sizing: border-box;
    border: 1px solid var(--border-soft);
}

.row-icon-thumb .row-thumb-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.row-icon-thumb .row-thumb-solid {
    display: block;
    width: 100%;
    height: 100%;
}

/* Pending state: render the icon glyph at half opacity so it doesn't look
   like a finished thumbnail. */
.row-icon-thumb.is-pending {
    opacity: 0.55;
    border-color: transparent;
    background: transparent;
}

/* ----- inline thumb settings grid ----- */

/* Stacked variant of the compact row — title at top, content below. Useful
   when the right-side control is wider than a single toggle. */
/* Sub-row inside the inline-thumbnails stack ("Copy to Grid view") — same
   single box as the kind toggles, separated by an inner hairline. */
.thumb-follow-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--border-soft);
}

.settings-row-stack {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 12px 14px;
    background: var(--bg-elev);
    border: 1px solid var(--border-soft);
    border-radius: 6px;
}

.settings-row-stack + .settings-row-compact,
.settings-row-compact + .settings-row-stack,
.settings-row-stack + .settings-row-stack {
    margin-top: 8px;
}

.thumb-toggle-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px 16px;
}

.thumb-toggle-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    cursor: pointer;
    padding: 4px 0;
}

.thumb-toggle-label {
    color: var(--text);
    font-size: 12px;
    font-weight: 500;
}

/* ----- new item indicator ----- */

/* Bright blue dot to the left of the icon, marking an item as new since
   last interaction. Sized to read clearly at a glance. When the dot is
   removed from the DOM, the icon shifts left to fill its space — no extra
   layout handling needed since flex collapses around the absent element. */
.row-new-dot {
    display: inline-block;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: #3b9eff;
    box-shadow: 0 0 6px rgba(59, 158, 255, 0.6);
    flex-shrink: 0;
    /* Absolutely positioned at an explicit coordinate (left is set inline by
       buildRow from the row's depth) so it takes no flex space — thumbnails
       stay column-aligned — and sits directly under the parent folder's icon
       column. The name cell is the positioning context (.row > * sets
       position: relative). Vertical centring via top + margin rather than a
       transform, so the is-fresh scale animation doesn't fight it. */
    position: absolute;
    top: 50%;
    margin: -4.5px 0 0 0;
}

/* Signature delight: when a genuinely new item first appears, its dot pops
   in with a damped spring — a big initial overshoot, then progressively
   smaller wobbles that ease smoothly to a stop — plus a one-shot halo bloom.
   The 'is-fresh' class is added by JS only on first appearance, and the JS
   also sets an animation-delay so a batch of new items cascades top-to-bottom
   rather than all firing at once. */
.row-new-dot.is-fresh {
    animation: new-dot-pop 0.9s var(--ease-out) both;
}

@keyframes new-dot-pop {
    0% {
        transform: scale(0);
        box-shadow: 0 0 0 0 rgba(59, 158, 255, 0.55);
    }
    /* Big first overshoot + halo bloom. */
    32% {
        transform: scale(1.32);
        box-shadow: 0 0 0 5px rgba(59, 158, 255, 0);
    }
    /* First settle below rest. */
    52% { transform: scale(0.9); }
    /* Smaller second wobble. */
    70% { transform: scale(1.08); }
    82% { transform: scale(0.97); }
    /* Tiny third wobble. */
    92% { transform: scale(1.02); }
    100% {
        transform: scale(1);
        box-shadow: 0 0 6px rgba(59, 158, 255, 0.6);
    }
}

/* In selected rows the dot retains its colour but gets a stronger halo
   so it pops against the selection background. */
.row.selected .row-new-dot {
    box-shadow: 0 0 8px rgba(59, 158, 255, 0.9);
}

/* ===== icon (grid) view ===== */

/* View-mode toggle in the statusbar. */
.view-toggle {
    display: inline-flex;
    margin-left: 8px;
    border: 1px solid var(--border);
    border-radius: 5px;
    overflow: hidden;
}
.view-toggle-btn {
    background: transparent;
    border: none;
    color: var(--text-faint);
    padding: 3px 7px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    transition: color var(--dur-fast) var(--ease-out),
                background var(--dur-fast) var(--ease-out);
}
.view-toggle-btn:hover { color: var(--text-dim); background: var(--bg-hover); }
.view-toggle-btn.is-active { color: var(--text); background: var(--bg-active); }

/* Zoom slider, shown only in icon mode. */
.icon-zoom {
    width: 84px;
    height: 16px;
    accent-color: var(--accent);
    cursor: pointer;
    vertical-align: middle;
}
.icon-zoom.hidden { display: none; }

/* The icon view fills the same area the list occupies. */
.icon-view {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
}
.icon-view.hidden { display: none; }

/* Breadcrumb / back bar. */
.icon-crumb {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    border-bottom: 1px solid var(--border-soft);
    background: var(--bg);
}
.icon-back {
    background: var(--bg-elev);
    border: 1px solid var(--border);
    color: var(--text-dim);
    border-radius: 5px;
    width: 24px;
    height: 22px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    flex-shrink: 0;
    transition: background var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}
.icon-back:hover:not(:disabled) {
    background: var(--bg-hover);
    color: var(--text);
    border-color: var(--text-dim);
}
.icon-back:disabled { opacity: 0.35; cursor: default; }
.icon-crumb-path {
    font-size: 11.5px;
    color: var(--text-dim);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Scroll area + grid. */
.icon-grid-scroll {
    flex: 1;
    overflow-y: auto;
    overflow-x: hidden;
    outline: none;
}
.icon-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    padding: 12px;
    align-content: flex-start;
}
.icon-grid-empty {
    color: var(--text-faint);
    font-size: 12px;
    padding: 28px;
    width: 100%;
    text-align: center;
}

/* Individual grid cell. Width is set inline (zoom slider); height follows. */
.icon-cell {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 6px;
    border-radius: 7px;
    border: 1px solid transparent;
    cursor: default;
    user-select: none;
    -webkit-user-select: none;
    -webkit-user-drag: none;
    transition: background var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}
.icon-thumb-img, .icon-cell img {
    -webkit-user-drag: none;
    user-select: none;
}
.icon-cell:hover { background: var(--bg-hover); transition: none; }
.icon-cell.selected {
    background: var(--accent-soft);
    border-color: var(--accent);
}

/* Thumbnail area within a cell. */
.icon-thumb {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    /* The thumb tile sits on the zebra light grey — background AND border
       the same tone, so the frame melts into the fill and image alpha /
       letterboxing read against that single soft tone. */
    background: var(--bg-zebra);
    border: 1px solid var(--bg-zebra);
    border-radius: 5px;
    overflow: hidden;
    margin-bottom: 6px;
}
.icon-thumb-img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}
.icon-thumb-placeholder {
    width: 100%;
    height: 100%;
    background: repeating-linear-gradient(
        45deg,
        var(--checker-a),
        var(--checker-a) 6px,
        var(--checker-b) 6px,
        var(--checker-b) 12px
    );
    opacity: 0.5;
}
/* Typed placeholder: a centred, enlarged file-type glyph instead of the
   diagonal-stripe pattern. Used for audio/vector/data footage in grid view,
   and as the brief pre-thumbnail state for image/video. */
.icon-thumb-typed {
    background: var(--bg-input);
    opacity: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-dim);
}
.icon-thumb-typed svg {
    width: 40%;
    height: 40%;
    min-width: 22px;
    min-height: 22px;
}
.icon-thumb-solid { width: 100%; height: 100%; }
.icon-folder-glyph { color: var(--label-6); }
.icon-cell.is-folder .icon-thumb {
    background: transparent;
    border-color: transparent;
}

/* Per-label tint of the folder glyph, mirroring the list's label colours. */
.icon-cell.label-1 .icon-folder-glyph { color: var(--label-1); }
.icon-cell.label-2 .icon-folder-glyph { color: var(--label-2); }
.icon-cell.label-3 .icon-folder-glyph { color: var(--label-3); }
.icon-cell.label-4 .icon-folder-glyph { color: var(--label-4); }
.icon-cell.label-5 .icon-folder-glyph { color: var(--label-5); }
.icon-cell.label-6 .icon-folder-glyph { color: var(--label-6); }
.icon-cell.label-7 .icon-folder-glyph { color: var(--label-7); }
.icon-cell.label-8 .icon-folder-glyph { color: var(--label-8); }
.icon-cell.label-9 .icon-folder-glyph { color: var(--label-9); }
.icon-cell.label-10 .icon-folder-glyph { color: var(--label-10); }
.icon-cell.label-11 .icon-folder-glyph { color: var(--label-11); }
.icon-cell.label-12 .icon-folder-glyph { color: var(--label-12); }
.icon-cell.label-13 .icon-folder-glyph { color: var(--label-13); }
.icon-cell.label-14 .icon-folder-glyph { color: var(--label-14); }
.icon-cell.label-15 .icon-folder-glyph { color: var(--label-15); }
.icon-cell.label-16 .icon-folder-glyph { color: var(--label-16); }
/* Grid smart folders also keep the default glyph colour (see list note). */

/* Cell label (item name). */
.icon-label {
    font-size: 11px;
    color: var(--text-dim);
    text-align: center;
    line-height: 1.3;
    width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    word-break: break-word;
}
.icon-cell.selected .icon-label { color: var(--text); }

/* Inline rename input inside an icon cell — sized to the label area. */
.inline-rename-icon {
    width: 100%;
    box-sizing: border-box;
    font-size: 11px;
    text-align: center;
    background: var(--bg-input);
    border: 1px solid var(--accent);
    border-radius: 4px;
    color: var(--text);
    padding: 1px 3px;
    outline: none;
}

/* New-item dot on a grid cell. */
.icon-new-dot {
    position: absolute;
    top: 8px;
    right: 8px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #3b9eff;
    box-shadow: 0 0 6px rgba(59, 158, 255, 0.6);
    z-index: 2;
}

/* A shy-marked folder shown while the shy toggle is OFF: subtly dimmed and
   italicised so you can see at a glance which folders are set to hide. When
   the toggle is ON these rows aren't rendered at all. */
.row.shy-folder .name {
    opacity: 0.55;
    font-style: italic;
}

/* ===== marquee (rubber-band) selection box ===== */
.marquee-box {
    position: fixed;
    z-index: 12000;
    background: rgba(91, 141, 239, 0.18);
    border: 1px solid rgba(91, 141, 239, 0.9);
    border-radius: 2px;
    pointer-events: none;
    box-shadow: 0 0 0 1px rgba(91, 141, 239, 0.25);
}

/* ===== micro-motion =====
   Small entrance/arrival animations extending the existing motion language
   (--dur-fast / --ease-out / --ease-spring). Everything here is transform/
   opacity only — compositor-friendly, no layout cost — and nothing attaches
   to row MOUNTING, because the virtualized list recycles rows on scroll and
   a mount animation would re-fire constantly mid-scroll. */

/* Context menu entrance: quick scale-from-cursor. The .menu-in class is
   added by showContextMenu AFTER the viewport clamp has measured the menu,
   so the edge-flip math always measures the untransformed box; the class is
   also removed/re-added per open so reopening at a new spot re-pops.
   transform-origin is set inline by the same code to match the flip corner,
   so the menu always grows out of the click point. */
.context-menu.menu-in {
    animation: ctx-menu-in 0.1s var(--ease-out);
}
@keyframes ctx-menu-in {
    from { opacity: 0; transform: scale(0.97); }
    to   { opacity: 1; transform: scale(1); }
}

/* Inline thumbnail fade-on. Driven by image LOAD, not element mount: the
   previous mount-time keyframe played out on an empty transparent box —
   <img> pixels only arrive at decode, after the animation had already
   finished, so thumbs visually popped with no fade. Thumbs start
   transparent and a capture-phase 'load' listener (panel.js) adds
   .thumb-loaded the moment pixels exist, so the fade is always seen —
   fresh generations, cached thumbs, scroll-ins, and rebuilds alike.
   A failed load never gets the class; the existing error handler swaps
   the img out, so nothing is left invisibly mounted. */
.row-thumb-img,
.icon-thumb-img {
    opacity: 0;
    transition: opacity 0.16s var(--ease-out);
    /* Own compositor layer: the opacity transition then runs on the
       compositor thread and keeps animating smoothly even while the main
       thread is busy with background work (decode callbacks, JSX bridge
       responses, render passes) — precisely the "fade gets lost in dropped
       frames" failure. Thumb textures are tiny, so the layer cost is
       negligible even with a full window of them. */
    will-change: opacity;
}
.row-thumb-img.thumb-loaded,
.icon-thumb-img.thumb-loaded {
    opacity: 1;
}

/* Search-help popover: the modals' entrance (fade + small drop), matching
   modal-fade-in's feel. Restarts naturally on each display:none -> block. */
.search-help-popover:not(.hidden) {
    animation: popover-in 0.12s var(--ease-out);
}
@keyframes popover-in {
    from { opacity: 0; transform: translateY(-3px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* Multi-select badge count pulse. The badge's appear/disappear is already
   handled by its base opacity/transform transition; this keyframe fires
   only when the COUNT changes while the badge is already visible (class
   retriggered by renderPreview), giving a small spring emphasis. Ends at
   scale(1), the same resting transform as .is-multi, so the running
   transition is never fought. */
.preview-multi-badge.badge-pulse {
    animation: badge-pulse 0.26s var(--ease-spring);
}
@keyframes badge-pulse {
    0%   { transform: scale(1); }
    40%  { transform: scale(1.16); }
    100% { transform: scale(1); }
}

/* ===== reduced motion =====
   Collapse every animation/transition to a single instant frame for users
   who ask the OS for reduced motion. Durations go to ~0 rather than `none`
   so `forwards` fills and end-states still land correctly (settings panel
   stays where it slid to, fades end fully opaque). */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

/* ===== missing media tint =====
   Missing items keep their TYPE icon (video/image/etc — still scannable at
   a glance) but tinted the missing red, in both views. Placed here, after
   the per-label tint rules, so it wins the equal-specificity cascade. */
.row.missing .row-icon {
    color: var(--missing);
}
.icon-thumb-typed.is-missing {
    color: var(--missing);
}

/* ===== generating glow =====
   Blobby accent glow on a pending icon slot while its thumbnail is being
   generated. Three overlapping blurred circles pulse organically (staggered
   phases); the whole cluster blobs IN when generation dispatches and blobs
   OUT (forwards, then inert) when the thumb lands or generation fails —
   both on the panel's signature S-curve easing. At most two of these exist
   at once (generation concurrency cap), so the blur cost is negligible. */
.row-icon,
.icon-thumb-placeholder {
    position: relative;
}

/* Icon -> thumbnail conversion crossfade. The retiring icon goes absolute
   (so the incoming img takes the layout) and fades on the S-curve; the
   incoming img carries .thumb-convert, which slows its fade-in to match.
   Scroll-recycled cached thumbs do NOT carry the class and keep the quick
   0.16s fade, so scrolling never feels syrupy. */
.row-icon svg.icon-fade-out,
.icon-thumb-placeholder svg.icon-fade-out {
    position: absolute;
    inset: 0;
    margin: auto;
    pointer-events: none;
    animation: icon-fade-out 0.45s cubic-bezier(0.65, 0, 0.15, 1) forwards;
}

@keyframes icon-fade-out {
    to { opacity: 0; }
}

.row-thumb-img.thumb-convert,
.icon-thumb-img.thumb-convert {
    transition-duration: 0.22s;
    transition-timing-function: cubic-bezier(0.65, 0, 0.15, 1);
}

.thumb-glow {
    position: absolute;
    inset: 0;
    border-radius: 3px;
    overflow: hidden;
    pointer-events: none;
    animation: blob-in 0.38s cubic-bezier(0.65, 0, 0.15, 1);
}

.thumb-glow i {
    position: absolute;
    width: 62%;
    height: 62%;
    border-radius: 50%;
    background: var(--accent);
    opacity: 0.45;
    filter: blur(3px);
    animation: blob-pulse 1.7s ease-in-out infinite;
}

/* Matte mode (glyph-masked): the visible area is tiny, so the blobs run
   brighter and tighter to read as light moving through the strokes. */
.thumb-glow.is-matte i {
    opacity: 0.85;
    filter: blur(2px);
}

.thumb-glow i:nth-child(1) { left: -8%; top: 6%; }
.thumb-glow i:nth-child(2) { left: 34%; top: -10%; animation-delay: -0.55s; }
.thumb-glow i:nth-child(3) { left: 22%; top: 38%; animation-delay: -1.1s; }

@keyframes blob-in {
    from { transform: scale(0.2); opacity: 0; }
    to   { transform: scale(1); opacity: 1; }
}

@keyframes blob-pulse {
    0%, 100% { transform: scale(0.85) translateY(0); }
    50%      { transform: scale(1.18) translateY(8%); }
}

.thumb-glow.blob-out {
    animation: blob-out 0.34s cubic-bezier(0.65, 0, 0.15, 1) forwards;
}

@keyframes blob-out {
    from { transform: scale(1); opacity: 1; }
    to   { transform: scale(0.2); opacity: 0; }
}

/* ===== new-dot farewell =====
   The arrival pop's matching exit: the dot blobs away when its item is
   marked seen. forwards keeps it gone until the rebuild removes the node. */
.row-new-dot.dot-out,
.icon-new-dot.dot-out {
    animation: dot-out 0.38s cubic-bezier(0.65, 0, 0.15, 1) forwards;
}
@keyframes dot-out {
    /* Secondary-motion exit mirroring the pop-in's overshoot character:
       a brief grow past rest (outward anticipation, cousin of the pop's
       1.32 bloom) before collapsing to nothing. Per-segment S-curve
       easing keeps the peak smooth. */
    0%   { transform: scale(1); opacity: 1; }
    38%  { transform: scale(1.38); opacity: 1; }
    100% { transform: scale(0); opacity: 0; }
}

/* The row tint is painted by a ::before overlay, which `.label-anim *`
   can never match (pseudo-elements aren't element children) — it needs its
   own transition for the morph. */
.row.label-anim::before {
    transition: background-color 0.35s cubic-bezier(0.65, 0, 0.15, 1);
}

/* ===== optimistic label-colour morph =====
   Scoped by the temporary .label-anim class (added when a label swatch is
   picked, removed ~450ms later), so ordinary rebuilds and selection changes
   never animate colour. */
.label-anim,
.label-anim * {
    transition: color 0.35s cubic-bezier(0.65, 0, 0.15, 1),
                background-color 0.35s cubic-bezier(0.65, 0, 0.15, 1);
}

/* ===== large-preview word cascade =====
   The pane grows over the words; .words-in (added by the size toggle just
   before full size, and on every preview change while large) reveals the
   info and starts the word animations — they're scoped to .words-in so
   they begin at the reveal, not at span insertion. Fast launch, long
   settle: high outgoing influence, small incoming. */
.preview.preview-large .preview-info {
    opacity: 0;
    /* The dark text-backing gradient fades on with the reveal rather than
       popping with the layout. */
    transition: opacity 0.3s cubic-bezier(0.65, 0, 0.15, 1);
}

.preview.preview-large.words-in .preview-info {
    opacity: 1;
}

.preview.preview-large.words-in .preview-info .pw {
    display: inline-block;
    animation: word-in 0.32s cubic-bezier(0.05, 0.8, 0.2, 1) backwards;
}

/* Compact cascade: spans animate the moment they're inserted (no gating
   class) — fires on item changes and on size-toggle renders alike, since
   spans are only built when a cascade is intended (identity change or
   forced by the toggle). Large mode stays gated by .words-in above so its
   reveal can wait a beat after the layout pop. */
.preview:not(.preview-large) .preview-info .pw {
    display: inline-block;
    animation: word-in 0.32s cubic-bezier(0.05, 0.8, 0.2, 1) backwards;
}

/* Size-toggle snap guard: the pane's max-height transition exists for
   show/hide — during a size swap it would animate the layout change, and
   the size change is meant to POP (only the text animates). Added for the
   swap frame, removed two rAFs later. */
.preview.preview-snap,
.preview.preview-snap * {
    transition: none !important;
}

@keyframes word-in {
    from { opacity: 0; transform: translateY(5px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ===== rename letter cascade =====
   The first rebuild after a rename fades the new name in letter by
   letter, left to right — the dot stagger at type scale. */
/* Rename reveal everywhere (list rows, grid cells, tabs): the preview's
   word-in cascade, so a rename matches clicking a new item in the panel. */
.row .name .pw,
.icon-label .pw,
.tab-name .pw {
    display: inline-block;
    animation: word-in 0.9s cubic-bezier(0.05, 0.8, 0.2, 1) backwards;
}

/* Lift vertical clipping only while a name cascades, so the per-character
   rise-in isn't eaten by the name box's overflow:hidden (CEP has no
   :has(), so the class is applied by JS). Horizontal ellipsis resumes
   once the cascade markup is replaced on the next render. */
.row .name.cascading {
    overflow: visible;
}

.icon-label.cascading {
    overflow: visible;
}

/* Proxy-in-use badge: small white dot with a dark ring (visible on any
   thumb or icon), top-right of the media slot in both views. Shown only
   when the proxy is ACTIVE — mirrors AE's filled-square semantics (the
   host's proxy flag is hasProxyFor: proxy in USE, not merely assigned). */
.proxy-dot {
    position: absolute;
    top: 0;
    right: 0;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 0 0 1.5px rgba(0, 0, 0, 0.55);
    pointer-events: none;
    z-index: 2;
}

.proxy-dot-grid {
    top: 4px;
    right: 4px;
    width: 7px;
    height: 7px;
}

/* The grid tile needs to anchor its corner dot. */
.icon-thumb {
    position: relative;
}

/* Two-line long-name treatment (compact preview): explicit start/end
   fragments replace the clamp's end-truncation so the suffix survives.
   Specificity beats the .pw inline-block rule so each fragment stays on
   its own line while still riding the word-cascade animation. */
.preview .preview-info .preview-name .pn-line {
    display: block;
    width: 100%;
    white-space: nowrap;
    /* Clip horizontally only. overflow:hidden (both axes) cut the bottom
       off descenders (g, y, p) on the second line, since the line box is
       sized to line-height and the tail falls just outside it. Vertical
       visible + a slightly taller line box gives tails room; the meta gap
       below is widened so the descender never touches the sub-info. */
    overflow-x: hidden;
    overflow-y: visible;
    line-height: 1.4;
}

/* Missing-footage chip — bottom right of the panel, replaces the old
   refresh button (refresh lives in the empty-space context menu). */
.missing-chip {
    -webkit-appearance: none;
    appearance: none;
    font-family: inherit;
    border: 1px solid rgba(229, 72, 77, 0.45);
    background: rgba(229, 72, 77, 0.13);
    color: #e5484d;
    font-size: 10.5px;
    font-weight: 600;
    padding: 2px 9px;
    border-radius: 99px;
    line-height: 1.5;
    cursor: pointer;
}

.missing-chip:hover {
    background: rgba(229, 72, 77, 0.24);
}

/* Duration on grid tiles (comps, video, audio): no box — the numerals
   float free over a soft corner vignette (radial dark falloff anchored
   bottom-right, clipped by the tile's own rounded overflow). Type matches
   the preview pane's title weight, a touch larger than the old chip. */
.dur-chip {
    position: absolute;
    right: 6px;
    bottom: 4px;
    color: #fff;
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.2px;
    line-height: 1.4;
    pointer-events: none;
    z-index: 2;
}

.dur-chip::before {
    content: '';
    position: absolute;
    right: -16px;
    bottom: -34px;
    width: 104px;
    height: 62px;
    /* Sunk further down so the 15deg tilt doesn't lift the ellipse's
       lower edge above the tile's bottom (the corner that was sticking
       up). Origin pulled left so the rotation swings the protruding side
       downward, keeping the falloff flush with the bottom edge. */
    transform: rotate(15deg);
    transform-origin: 60% 100%;
    background: radial-gradient(ellipse at bottom right,
        rgba(0, 0, 0, 0.62),
        rgba(0, 0, 0, 0.38) 38%,
        rgba(0, 0, 0, 0) 72%);
    z-index: -1;
    pointer-events: none;
}

/* Inline search match count — sits just after the typed text, in a tone
   quieter than the input's own. */
.search-wrap {
    position: relative;
}

.search-count {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    color: var(--text-faint, var(--text-dim));
    font-size: 12px;
    pointer-events: none;
    white-space: nowrap;
}

/* Used-in lives on its own second line beneath the core meta — high
   specificity so the .pw inline-block rule can't pull it back inline. */
/* COMPACT with a SINGLE-line name keeps used-in on its own second line.
   When the name wraps to TWO lines there's no vertical room for a third
   meta line (it was clipping at the pane's bottom), so used-in folds
   INLINE with the rest of the sub-data instead — driven by .tall on the
   info column. The id keeps these rules above the .pw inline-block rules. */
.preview:not(.preview-large) .preview-info:not(.tall) #preview-meta .pm-used {
    display: block;
    margin-top: 1px;
    /* Reserve the line even while empty so the pane height doesn't jump. */
    min-height: 14px;
}

/* Inline used-in: large mode always, and compact when the name is two
   lines. A leading separator only when the slot has text (no dangling dot
   over an empty reserved slot). */
.preview.preview-large .preview-info #preview-meta .pm-used:not(:empty)::before,
.preview .preview-info.tall #preview-meta .pm-used:not(:empty)::before {
    content: ' · ';
}

/* Thin panels: the items-count text is the one that yields — it truncates
   with an ellipsis so the view toggle and zoom slider always stay visible
   instead of being pushed into .status-left's clipped overflow. */
#status-count {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 0 1 auto;
    min-width: 0;
}

.view-toggle {
    flex: 0 0 auto;
}

.icon-zoom {
    flex: 0 1 auto;
    min-width: 56px;
}

/* Single view-switch button (replaces the two-button toggle group). */
.view-switch {
    -webkit-appearance: none;
    appearance: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 20px;
    padding: 0;
    border: 1px solid var(--border-soft);
    border-radius: 5px;
    background: transparent;
    color: var(--text-dim);
    cursor: pointer;
    flex: 0 0 auto;
}

.view-switch:hover {
    color: var(--text);
}

.view-switch .vs-list { display: none; }
.view-switch.mode-icon .vs-list { display: block; }
.view-switch.mode-icon .vs-grid { display: none; }

/* Below this width the items-count bows out entirely, so the bpc button,
   view toggle, and zoom slider always sit together flush-left with no
   competition at any panel width. */
@media (max-width: 430px) {
    #status-count {
        display: none;
    }
}

/* Freshly generated preview images fade in (same family as the inline
   thumbnail load fade); switching between already-rendered previews is
   deliberately instant — the animation only runs on insertion, and only
   the icon-to-image branch inserts. */
@keyframes pvFadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

.preview-thumb img.pv-fade-in {
    animation: pvFadeIn 0.45s cubic-bezier(0.65, 0, 0.15, 1);
}

/* ====================== TAB STRIP (saved views) ====================== */
/* Sits on the preview surface: active tab matches the preview pane's own
   background (var(--bg)) so they read as one uniform surface; inactive
   tabs sit slightly darker (var(--bg-input)). No accent underline — the
   colour match alone signals which view is live. */
.tab-strip {
    display: flex;
    align-items: stretch;
    gap: 3px;
    padding: 5px 6px 0 6px;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    flex-shrink: 0;
    background: var(--bg);
}

.tab-strip::-webkit-scrollbar { height: 0; }

.tab {
    display: flex;
    align-items: center;
    gap: 6px;
    max-width: 160px;
    padding: 5px 8px 6px 10px;
    border-radius: 6px 6px 0 0;
    background: var(--bg-input);
    color: var(--text-dim);
    font-size: 11.5px;
    font-weight: 500;
    cursor: pointer;
    white-space: nowrap;
    position: relative;
    user-select: none;
    flex-shrink: 0;
    transition: background var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.tab:hover {
    color: var(--text);
    background: var(--bg-hover);
}

.tab.active {
    color: var(--text);
    background: var(--bg);
    cursor: default;
}

.tab.active:hover {
    background: var(--bg);
}

.tab:focus,
.tab:focus-visible {
    outline: none;
}

.tab .tab-name {
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 120px;
}

/* While a rename cascade is mounted, the name box must not vertically clip
   the characters' rise-in (overflow:hidden was eating the transform, so
   only the fade showed). Class is applied by JS for the cascade render
   only — CEP 11's Chromium has no :has(). Horizontal ellipsis still
   applies via text-overflow when overflow returns to hidden. */
.tab .tab-name.cascading {
    overflow: visible;
}

/* Tab-rename reveal: per-character cascade like the preview pane, with a
   slightly longer per-character entrance (stagger between chars stays
   quick). Its own keyframe adds a small rise so the reveal reads clearly
   as characters arriving, not just a fade. */
/* (tab rename reveal now uses the shared word-in cascade above.) */

.tab .tab-rename {
    width: 90px;
    font: inherit;
    color: var(--text);
    background: var(--bg-elev);
    border: 1px solid var(--accent);
    border-radius: 3px;
    padding: 1px 4px;
    outline: none;
}

/* During the rebuild frame (.no-anim on the strip) the close icon snaps to
   its correct size for the current hover state instead of animating, so a
   tab rebuilt under the cursor doesn't visibly collapse then regrow. */
.tab-strip.no-anim .tab-close {
    transition: none !important;
}

.tab .tab-close {
    display: flex;
    align-items: center;
    justify-content: center;
    /* Collapsed to zero width when idle so the tab has NO reserved gap on
       the right; on hover/active it expands to full width, which reads as
       the tab growing slightly to reveal the close affordance. The
       negative margin swallows the parent's gap so a hidden icon leaves no
       trace. */
    width: 0;
    height: 14px;
    margin-left: -6px;
    overflow: hidden;
    border-radius: 3px;
    font-size: 13px;
    line-height: 1;
    color: var(--text-faint);
    opacity: 0;
    transition: opacity var(--dur-fast) var(--ease-out),
                width var(--dur-fast) var(--ease-out),
                margin-left var(--dur-fast) var(--ease-out),
                background var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.tab:hover .tab-close {
    opacity: 1;
    width: 14px;
    margin-left: 0;
}

.tab .tab-close:hover {
    background: rgba(127, 127, 127, 0.25);
    color: var(--text);
}

.tab-strip.single-tab .tab-close { display: none; }

.tab-add {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    flex-shrink: 0;
    margin-left: 2px;
    border: none;
    background: transparent;
    color: var(--text-faint);
    font-size: 17px;
    line-height: 1;
    cursor: pointer;
    border-radius: 5px;
}

.tab-add:hover {
    color: var(--text);
    background: var(--bg-hover);
}

/* Live video preview (Play videos in preview setting). Fills the thumb
   well like the still preview, letterboxing to fit. */
.preview-video {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
    border-radius: 4px;
}

/* ================= Continuous list zoom (--lz, 0.75..1.5) ================ */
/* Row height is the master metric — virtualization re-measures it after
   every scale change. Text and glyphs derive from the same var so the
   whole row scales together, completely smoothly (no snapping). */
.list .row {
    height: calc(26px * var(--lz, 1)) !important;
}

.list .row .name,
.list .row .row-cell {
    font-size: calc(12px * var(--lz, 1));
}

.list .row .row-icon svg {
    width: calc(14px * var(--lz, 1));
    height: calc(14px * var(--lz, 1));
}

/* The icon slot itself was the one row metric NOT scaled by --lz: its
   width stayed at the fixed density size while the shrinking row squeezed
   its height, so inline thumbnails went non-square at small zoom (a
   circular ball rendered as a flattened oval). Scaling the slot with the
   same variable keeps the box — and everything cover-fitted inside it —
   square at every zoom. */
.list .row .row-icon {
    width: calc(var(--row-icon-size, 22px) * var(--lz, 1));
    height: calc(var(--row-icon-size, 22px) * var(--lz, 1));
}

.list .row .twirl svg {
    width: calc(10px * var(--lz, 1));
    height: calc(10px * var(--lz, 1));
}

/* ======================= Render All modal ======================= */
.ra-modal .confirm-body { text-align: center; }

.ra-anim {
    position: relative;
    height: 92px;
    margin: 12px auto 6px;
    width: 92px;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Ball-materialize: each inline-thumb glyph forms out of small dots that
   fade up one by one (fast, random order), holds, then the whole glyph
   fades away and the next one builds — every icon in the cycle before it
   repeats. All on the house curve. */
.ra-dots {
    position: relative;
    width: 80px;
    height: 80px;
    margin: 0 auto;
    color: var(--text-dim);
    opacity: 1;
    transition: opacity 0.3s cubic-bezier(0.05, 0.8, 0.2, 1);
}

.ra-dots.ra-out { opacity: 0; }

.ra-dot {
    position: absolute;
    width: 2.4px;
    height: 2.4px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0;
    animation: ra-dot-in 0.18s cubic-bezier(0.05, 0.8, 0.2, 1) forwards;
}

@keyframes ra-dot-in {
    from { opacity: 0; transform: scale(0.4); }
    to   { opacity: 1; transform: scale(1); }
}

/* Fallback when a glyph can't be dot-sampled: the whole icon fades in on
   the same rhythm so the stage never sits empty. */
.ra-whole {
    display: block;
    width: 56px;
    height: 56px;
    margin: 12px auto 0;
    animation: ra-dot-in 0.3s cubic-bezier(0.05, 0.8, 0.2, 1) backwards;
}

.ra-whole svg { width: 100%; height: 100%; }

.ra-modal .confirm-detail {
    margin: 2px 0 8px;
    font-size: 11px;
}

.ra-bar {
    height: 4px;
    border-radius: 2px;
    background: var(--bg-input);
    margin: 4px 8px 8px;
    overflow: hidden;
}

.ra-fill {
    height: 100%;
    width: 0%;
    border-radius: 2px;
    background: var(--accent);
    transition: width 0.3s cubic-bezier(0.05, 0.8, 0.2, 1);
}

/* ============ Settings sections: one surface per category ============ */
/* Each category is a single elevated box; rows inside are flat, separated
   by thin inset divider lines (not edge to edge). Overrides the previous
   per-row boxes. */
.settings-section {
    background: var(--bg-elev);
    border: 1px solid var(--border-soft);
    border-radius: 8px;
    padding: 12px 0 2px;
    margin-bottom: 18px;
}

.settings-section .settings-section-title {
    padding: 0 14px 8px;
    margin: 0;
}

.settings-section .settings-row-compact {
    background: transparent;
    border: none;
    border-radius: 0;
    position: relative;
}

.settings-section .settings-row-compact + .settings-row-compact {
    margin-top: 0;
}

.settings-section .settings-row-compact + .settings-row-compact::before {
    content: '';
    position: absolute;
    top: 0;
    left: 14px;
    right: 14px;
    height: 1px;
    background: var(--border-soft);
}

/* First on-screen reveal of an already-cached thumb (e.g. after Render
   All): same slow conversion fade as inline generation, so cached thumbs
   glide in instead of popping. Subsequent scroll rebuilds don't carry the
   class and keep the quick fade. */
.row-thumb-img.thumb-reveal,
.icon-thumb-img.thumb-reveal {
    transition-duration: 0.45s;
    transition-timing-function: cubic-bezier(0.65, 0, 0.15, 1);
}

/* Flatten the remaining boxed row variant inside sections (the wide
   .settings-row, e.g. the inline-thumbnails block) so there's never a box
   inside the section box — one surface per category, thin inset dividers
   between every row regardless of which row class it uses. */
.settings-section .settings-row {
    background: transparent;
    border: none;
    border-radius: 0;
    position: relative;
}

.settings-section .settings-row + .settings-row,
.settings-section .settings-row + .settings-row-compact,
.settings-section .settings-row-compact + .settings-row {
    margin-top: 0;
}

.settings-section .settings-row + .settings-row::before,
.settings-section .settings-row + .settings-row-compact::before,
.settings-section .settings-row-compact + .settings-row::before {
    content: '';
    position: absolute;
    top: 0;
    left: 14px;
    right: 14px;
    height: 1px;
    background: var(--border-soft);
}

/* The wide stacked row (.settings-row-stack — the inline-thumbnails block)
   was the last row variant still carrying its own box inside the section
   surface. Flattened like the others, with the same inset dividers across
   every adjacency combination. */
.settings-section .settings-row-stack {
    background: transparent;
    border: none;
    border-radius: 0;
    position: relative;
}

.settings-section .settings-row-stack + .settings-row-stack,
.settings-section .settings-row-stack + .settings-row-compact,
.settings-section .settings-row-compact + .settings-row-stack,
.settings-section .settings-row-stack + .settings-row,
.settings-section .settings-row + .settings-row-stack {
    margin-top: 0;
}

.settings-section .settings-row-stack + .settings-row-stack::before,
.settings-section .settings-row-stack + .settings-row-compact::before,
.settings-section .settings-row-compact + .settings-row-stack::before,
.settings-section .settings-row-stack + .settings-row::before,
.settings-section .settings-row + .settings-row-stack::before {
    content: '';
    position: absolute;
    top: 0;
    left: 14px;
    right: 14px;
    height: 1px;
    background: var(--border-soft);
}

/* First-reveal thumbs stagger top to bottom, echoing the new-item dot
   cascade: each successive visible row/cell starts its slow fade a beat
   later. Delay caps after two dozen so deep windows don't crawl. */
.list .row:nth-child(1) .row-thumb-img.thumb-reveal { transition-delay: 28ms; }
.list .row:nth-child(2) .row-thumb-img.thumb-reveal { transition-delay: 56ms; }
.list .row:nth-child(3) .row-thumb-img.thumb-reveal { transition-delay: 84ms; }
.list .row:nth-child(4) .row-thumb-img.thumb-reveal { transition-delay: 112ms; }
.list .row:nth-child(5) .row-thumb-img.thumb-reveal { transition-delay: 140ms; }
.list .row:nth-child(6) .row-thumb-img.thumb-reveal { transition-delay: 168ms; }
.list .row:nth-child(7) .row-thumb-img.thumb-reveal { transition-delay: 196ms; }
.list .row:nth-child(8) .row-thumb-img.thumb-reveal { transition-delay: 224ms; }
.list .row:nth-child(9) .row-thumb-img.thumb-reveal { transition-delay: 252ms; }
.list .row:nth-child(10) .row-thumb-img.thumb-reveal { transition-delay: 280ms; }
.list .row:nth-child(11) .row-thumb-img.thumb-reveal { transition-delay: 308ms; }
.list .row:nth-child(12) .row-thumb-img.thumb-reveal { transition-delay: 336ms; }
.list .row:nth-child(13) .row-thumb-img.thumb-reveal { transition-delay: 364ms; }
.list .row:nth-child(14) .row-thumb-img.thumb-reveal { transition-delay: 392ms; }
.list .row:nth-child(15) .row-thumb-img.thumb-reveal { transition-delay: 420ms; }
.list .row:nth-child(16) .row-thumb-img.thumb-reveal { transition-delay: 448ms; }
.list .row:nth-child(17) .row-thumb-img.thumb-reveal { transition-delay: 476ms; }
.list .row:nth-child(18) .row-thumb-img.thumb-reveal { transition-delay: 504ms; }
.list .row:nth-child(19) .row-thumb-img.thumb-reveal { transition-delay: 532ms; }
.list .row:nth-child(20) .row-thumb-img.thumb-reveal { transition-delay: 560ms; }
.list .row:nth-child(21) .row-thumb-img.thumb-reveal { transition-delay: 588ms; }
.list .row:nth-child(22) .row-thumb-img.thumb-reveal { transition-delay: 616ms; }
.list .row:nth-child(23) .row-thumb-img.thumb-reveal { transition-delay: 644ms; }
.list .row:nth-child(24) .row-thumb-img.thumb-reveal { transition-delay: 672ms; }
.icon-grid .icon-cell:nth-child(1) .icon-thumb-img.thumb-reveal { transition-delay: 24ms; }
.icon-grid .icon-cell:nth-child(2) .icon-thumb-img.thumb-reveal { transition-delay: 48ms; }
.icon-grid .icon-cell:nth-child(3) .icon-thumb-img.thumb-reveal { transition-delay: 72ms; }
.icon-grid .icon-cell:nth-child(4) .icon-thumb-img.thumb-reveal { transition-delay: 96ms; }
.icon-grid .icon-cell:nth-child(5) .icon-thumb-img.thumb-reveal { transition-delay: 120ms; }
.icon-grid .icon-cell:nth-child(6) .icon-thumb-img.thumb-reveal { transition-delay: 144ms; }
.icon-grid .icon-cell:nth-child(7) .icon-thumb-img.thumb-reveal { transition-delay: 168ms; }
.icon-grid .icon-cell:nth-child(8) .icon-thumb-img.thumb-reveal { transition-delay: 192ms; }
.icon-grid .icon-cell:nth-child(9) .icon-thumb-img.thumb-reveal { transition-delay: 216ms; }
.icon-grid .icon-cell:nth-child(10) .icon-thumb-img.thumb-reveal { transition-delay: 240ms; }
.icon-grid .icon-cell:nth-child(11) .icon-thumb-img.thumb-reveal { transition-delay: 264ms; }
.icon-grid .icon-cell:nth-child(12) .icon-thumb-img.thumb-reveal { transition-delay: 288ms; }
.icon-grid .icon-cell:nth-child(13) .icon-thumb-img.thumb-reveal { transition-delay: 312ms; }
.icon-grid .icon-cell:nth-child(14) .icon-thumb-img.thumb-reveal { transition-delay: 336ms; }
.icon-grid .icon-cell:nth-child(15) .icon-thumb-img.thumb-reveal { transition-delay: 360ms; }
.icon-grid .icon-cell:nth-child(16) .icon-thumb-img.thumb-reveal { transition-delay: 384ms; }
.icon-grid .icon-cell:nth-child(17) .icon-thumb-img.thumb-reveal { transition-delay: 408ms; }
.icon-grid .icon-cell:nth-child(18) .icon-thumb-img.thumb-reveal { transition-delay: 432ms; }
.icon-grid .icon-cell:nth-child(19) .icon-thumb-img.thumb-reveal { transition-delay: 456ms; }
.icon-grid .icon-cell:nth-child(20) .icon-thumb-img.thumb-reveal { transition-delay: 480ms; }
.icon-grid .icon-cell:nth-child(21) .icon-thumb-img.thumb-reveal { transition-delay: 504ms; }
.icon-grid .icon-cell:nth-child(22) .icon-thumb-img.thumb-reveal { transition-delay: 528ms; }
.icon-grid .icon-cell:nth-child(23) .icon-thumb-img.thumb-reveal { transition-delay: 552ms; }
.icon-grid .icon-cell:nth-child(24) .icon-thumb-img.thumb-reveal { transition-delay: 576ms; }

/* Flatten the THIRD row variant (.settings-row-stack — the inline
   thumbnails block) inside section boxes, same as the other two, with the
   inset divider against any neighbouring row type. */
.settings-section .settings-row-stack {
    background: transparent;
    border: none;
    border-radius: 0;
    position: relative;
    margin-top: 0;
}

.settings-section :is(.settings-row, .settings-row-compact, .settings-row-stack) +
:is(.settings-row, .settings-row-compact, .settings-row-stack) {
    margin-top: 0;
}

.settings-section :is(.settings-row, .settings-row-compact, .settings-row-stack) +
:is(.settings-row, .settings-row-compact, .settings-row-stack)::before {
    content: '';
    position: absolute;
    top: 0;
    left: 14px;
    right: 14px;
    height: 1px;
    background: var(--border-soft);
}

/* Grid folder cell as an active drop target: accent ring + soft fill,
   the grid's equivalent of the list row highlight. */
.icon-cell.drop-target {
    box-shadow: inset 0 0 0 2px var(--accent);
    background: rgba(91, 141, 239, 0.12);
    border-radius: 6px;
}

/* Back button as a drag nav-up target. */
.icon-back.drop-target {
    box-shadow: inset 0 0 0 2px var(--accent);
    background: rgba(91, 141, 239, 0.15);
}

/* ========================= Colour themes ========================= */
/* Variables cascade from body, so a class here re-skins everything —
   including the large-preview fade via the per-theme fade triplets. */

/* Darker: the default palette pulled down a step. */
body.theme-darker {
    --bg: #161616;
    --bg-elev: #1e1e1e;
    --bg-hover: #242424;
    --bg-active: #303030;
    --bg-input: #0f0f0f;
    --bg-zebra: #1e1e1e;
    --border: #2b2b2b;
    --border-soft: #232323;
    --fade-full: rgb(22, 22, 22);
    --fade-70: rgba(22, 22, 22, 0.7);
    --fade-0: rgba(22, 22, 22, 0);
}

/* Lighter: the palette lifted a step, with the placeholder checker pattern
   tuned to near-flat so pending thumbnails read calm rather than stripy. */
body.theme-lighter {
    --bg: #252525;
    --bg-elev: #2d2d2d;
    --bg-hover: #333333;
    --bg-active: #414141;
    --bg-input: #1d1d1d;
    --bg-zebra: #2d2d2d;
    --border: #3d3d3d;
    --border-soft: #363636;
    --fade-full: rgb(37, 37, 37);
    --fade-70: rgba(37, 37, 37, 0.7);
    --fade-0: rgba(37, 37, 37, 0);
    /* Much closer pair = the checker nearly melts into the background. */
    --checker-a: #272727;
    --checker-b: #2a2a2a;
}

/* ============== Rule editor: house dropdowns & aligned columns ============ */
/* Fixed widths per column so every condition row lines up — the native
   selects sized to their content and scattered. */
.rule-dd {
    display: inline-flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    background: var(--bg-input);
    border: 1px solid var(--border-soft);
    color: var(--text);
    padding: 5px 8px 5px 10px;
    border-radius: 6px;
    font-family: inherit;
    font-size: 11px;
    cursor: pointer;
    text-align: left;
    transition: background var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.rule-dd:hover {
    background: var(--bg-hover);
    border-color: var(--border);
}

.rule-dd-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
    flex: 1;
}

.rule-dd-chev {
    flex-shrink: 0;
    color: var(--text-faint);
    font-size: 9px;
    line-height: 1;
    display: inline-flex;
}

.rule-dd-chev svg { width: 10px; height: 10px; }

.rule-dd.rule-field { width: 116px; }
.rule-dd.rule-op    { width: 104px; }
.rule-dd.rule-value-dd { min-width: 0; }

.rule-row input.rule-value { min-width: 0; width: 100%; }

/* The menu — same idiom as the context menu: elevated card, quiet rows,
   grouped by small-caps subtitles, current choice in accent. */
/* House scrollbar, matching the list and grid. */
.rule-dd-menu::-webkit-scrollbar { width: 8px; }
.rule-dd-menu::-webkit-scrollbar-track { background: transparent; }
.rule-dd-menu::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
.rule-dd-menu::-webkit-scrollbar-thumb:hover { background: var(--text-faint); }

.rule-dd-menu {
    position: fixed;
    z-index: 1000;
    background: var(--bg-elev);
    border: 1px solid var(--border-soft);
    border-radius: 8px;
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45);
    padding: 4px;
    max-height: 280px;
    overflow-y: auto;
    min-width: 140px;
}

.rule-dd-group {
    padding: 7px 10px 3px;
    font-size: 9px;
    font-weight: 600;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--text-faint);
}

.rule-dd-group:not(:first-child) {
    margin-top: 3px;
    border-top: 1px solid var(--border-soft);
    padding-top: 8px;
}

.rule-dd-item {
    display: block;
    width: 100%;
    text-align: left;
    background: transparent;
    border: none;
    color: var(--text);
    font-family: inherit;
    font-size: 11.5px;
    padding: 5px 10px;
    border-radius: 5px;
    cursor: pointer;
}

.rule-dd-item:hover {
    background: var(--bg-hover);
}

.rule-dd-item.is-current {
    color: var(--accent);
    font-weight: 500;
}

/* ================= Condition pills + guided builder ================= */
#sm-rules {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    align-items: center;
}

.cond-pill {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    max-width: 100%;
    background: var(--bg-input);
    border: 1px solid var(--border-soft);
    color: var(--text);
    padding: 5px 8px 5px 11px;
    border-radius: 999px;
    font-family: inherit;
    font-size: 11px;
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.cond-pill:hover { background: var(--bg-hover); }

.cond-pill-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

.cond-pill-x {
    flex-shrink: 0;
    width: 14px;
    height: 14px;
    line-height: 13px;
    text-align: center;
    border-radius: 50%;
    color: var(--text-faint);
    font-size: 12px;
}

.cond-pill-x:hover {
    background: rgba(224, 108, 108, 0.18);
    color: #e06c6c;
}

/* Builder popover internals (shell shares .rule-dd-menu). */
.cond-builder { padding: 0 0 4px; }

.cb-crumbs {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 7px 10px;
    border-bottom: 1px solid var(--border-soft);
    margin-bottom: 4px;
    position: sticky;
    top: 0;
    background: var(--bg-elev);
    z-index: 1;
}

.cb-crumb {
    background: transparent;
    border: none;
    color: var(--text-faint);
    font-family: inherit;
    font-size: 10.5px;
    padding: 2px 4px;
    border-radius: 4px;
    cursor: pointer;
    max-width: 40%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

button.cb-crumb:hover { color: var(--text); background: var(--bg-hover); }

.cb-crumb.is-here { color: var(--text); font-weight: 500; }

.cb-sep { color: var(--text-faint); font-size: 10px; }

.cond-builder .rule-dd-item { margin: 0 4px; width: calc(100% - 8px); }
.cond-builder .rule-dd-group { padding-left: 14px; }

.cb-input-row {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 10px;
}

.cb-input {
    flex: 1;
    min-width: 0;
    background: var(--bg-input);
    border: 1px solid var(--border-soft);
    color: var(--text);
    border-radius: 6px;
    padding: 5px 8px;
    font-family: inherit;
    font-size: 11.5px;
}

.cb-input:focus { outline: none; border-color: var(--accent); }

.cb-done {
    flex-shrink: 0;
    background: var(--accent);
    border: none;
    color: #fff;
    font-family: inherit;
    font-size: 11px;
    font-weight: 500;
    padding: 5px 12px;
    border-radius: 6px;
    cursor: pointer;
}

.cb-done:hover { filter: brightness(1.1); }

/* Empty conditions: the placeholder invitation sits centred in the box. */
#sm-rules.is-empty {
    justify-content: center;
    min-height: 34px;
}

.cond-pill-placeholder {
    border-style: dashed;
    color: var(--text-dim);
    background: transparent;
}

.cond-pill-placeholder:hover {
    color: var(--text);
    border-color: var(--accent) !important;
    background: rgba(91, 141, 239, 0.08);
}

@keyframes ph-attention {
    0%, 100% { transform: translateX(0); }
    20% { transform: translateX(-4px); }
    45% { transform: translateX(4px); }
    70% { transform: translateX(-2px); }
}

.cond-pill-placeholder.attention {
    animation: ph-attention 0.4s cubic-bezier(0.05, 0.8, 0.2, 1);
    border-color: var(--accent) !important;
}

/* Builder hugs its content. */
.cond-builder {
    width: max-content;
    min-width: 160px;
}

/* The "None" colour swatch: neutral slash treatment (same idiom as the
   label-clear swatch), reading as "no colour change". */
.colour-dot-none {
    background: repeating-linear-gradient(45deg, var(--bg-hover) 0, var(--bg-hover) 3px, var(--bg-input) 3px, var(--bg-input) 6px) !important;
    border: 1px solid var(--border);
}

/* ==================== Micro-polish (Apple-grade feel) ==================== */

/* Modals arrive — a breath of scale and fade instead of blinking into
   existence. The overlay washes in a touch faster than the card settles. */
@keyframes overlay-in {
    from { background: rgba(0, 0, 0, 0); }
    to   { background: rgba(0, 0, 0, 0.5); }
}

@keyframes modal-in {
    from { opacity: 0; transform: scale(0.965) translateY(4px); }
    to   { opacity: 1; transform: scale(1) translateY(0); }
}

.modal-overlay { animation: overlay-in 0.16s ease-out; }
.modal-overlay .modal { animation: modal-in 0.2s cubic-bezier(0.05, 0.8, 0.2, 1); }

/* Toggles get a spring: the knob overshoots ever so slightly into place,
   and pressing stretches it toward the direction of travel (the iOS pill
   squash) before release lets it snap. */
.toggle::after {
    transition: transform 0.28s cubic-bezier(0.34, 1.4, 0.5, 1),
                width 0.15s cubic-bezier(0.05, 0.8, 0.2, 1),
                background 0.15s;
}

.toggle:active::after { width: 15px; }
.toggle.is-on:active::after { transform: translateX(11px); }

/* Tactile presses across the small interactive family — a whisper of
   scale, matching the settings buttons. */
.chip:active,
.cond-pill:active,
.rule-dd:active,
.tab:active,
.colour-dot:active {
    transform: scale(0.96);
}

.chip, .cond-pill, .rule-dd, .tab, .colour-dot {
    transition-property: background, border-color, color, transform;
    transition-duration: var(--dur-fast);
    transition-timing-function: var(--ease-out);
}

.icon-cell:active .icon-cell-graphic { transform: scale(0.985); }
.icon-cell-graphic { transition: transform 0.12s var(--ease-out); }

/* The search clear button pops in when text appears rather than teleporting. */
@keyframes clear-pop {
    from { opacity: 0; transform: scale(0.5); }
    60%  { transform: scale(1.12); }
    to   { opacity: 1; transform: scale(1); }
}

.clear-btn:not(.hidden) { animation: clear-pop 0.22s cubic-bezier(0.05, 0.8, 0.2, 1); }

/* Drop-target rings announce themselves with a single soft pulse when
   acquired, then hold steady. */
@keyframes ring-pulse {
    from { box-shadow: inset 0 0 0 2px var(--accent), 0 0 0 4px rgba(91, 141, 239, 0.25); }
    to   { box-shadow: inset 0 0 0 2px var(--accent), 0 0 0 0 rgba(91, 141, 239, 0); }
}

.icon-cell.drop-target,
.icon-back.drop-target { animation: ring-pulse 0.35s var(--ease-out); }

/* Zoom slider thumb acknowledges the pointer. */
#icon-zoom::-webkit-slider-thumb {
    transition: transform 0.14s var(--ease-out);
}

#icon-zoom:hover::-webkit-slider-thumb { transform: scale(1.2); }
#icon-zoom:active::-webkit-slider-thumb { transform: scale(1.35); }

/* Twirl chevrons rotate rather than jump (harmless if a glyph swap is in
   play; graceful when a rotation is). */
.list .row .twirl svg { transition: transform 0.16s var(--ease-out); }

/* ============== Modal exit + chip bar behaviours + drag lift ============== */

/* Modals breathe OUT — the mirror of the entrance: card recedes on the
   house curve while the dim washes away. pointer-events off so nothing can
   be clicked mid-exit. */
@keyframes overlay-out {
    to { background: rgba(0, 0, 0, 0); }
}

@keyframes modal-out {
    to { opacity: 0; transform: scale(0.965) translateY(4px); }
}

.modal-overlay.modal-closing {
    animation: overlay-out 0.16s ease-out forwards;
    pointer-events: none;
}

.modal-overlay.modal-closing .modal {
    animation: modal-out 0.16s cubic-bezier(0.05, 0.8, 0.2, 1) forwards;
}

/* Dragged settings rows lift while their siblings glide (FLIP) into the
   space being made — transition:none so the FLIP transforms never fight
   the lift. */
.col-row.is-dragging,
.custom-chip-row.is-dragging {
    transform: scale(1.02);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
    position: relative;
    z-index: 2;
    transition: none;
}

/* The + chip at the end of the bar: quiet, dashed, wakes on hover. */
.chip.chip-add {
    border: 1px dashed var(--border);
    color: var(--text-faint);
    padding: 0 8px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    align-self: stretch;
    /* Always visible: when the bar overflows, the + pins to the right edge
       and the chips slide beneath it. Solid background + soft edge shadow
       so the overlap reads clearly. */
    position: sticky;
    right: 0;
    flex-shrink: 0;
    z-index: 1;
    background: var(--bg);
    box-shadow: -8px 0 8px -6px rgba(0, 0, 0, 0.5);
}

.chip.chip-add:hover {
    color: var(--text);
    border-color: var(--text-faint);
}

/* Wheel-cycling landing: a small horizontal overshoot in the direction of
   travel, settling softly — momentum arriving, not a pop. */
@keyframes chip-settle-r {
    0%   { transform: translateX(0); }
    38%  { transform: translateX(3px); }
    100% { transform: translateX(0); }
}

@keyframes chip-settle-l {
    0%   { transform: translateX(0); }
    38%  { transform: translateX(-3px); }
    100% { transform: translateX(0); }
}

.chip.chip-cycled-r { animation: chip-settle-r 0.32s cubic-bezier(0.2, 0.9, 0.3, 1); }
.chip.chip-cycled-l { animation: chip-settle-l 0.32s cubic-bezier(0.2, 0.9, 0.3, 1); }

/* Danger entries in house dropdowns — red text, red wash on hover,
   consistent with destructive actions elsewhere in the panel. */
.rule-dd-item.rule-dd-danger { color: #e06c6c; }
.rule-dd-item.rule-dd-danger:hover {
    background: rgba(224, 108, 108, 0.16);
    color: #e88;
}

/* The chip right-click menu hugs its single word. */
.rule-dd-menu.chip-ctx-menu {
    min-width: 0;
    width: max-content;
}

/* Terminal-chip squash: the wall-side edge is anchored dead still and the
   inner side compresses toward it, then springs back out — momentum
   absorbed by the boundary. Nothing ever extends past the strip's edge. */
@keyframes chip-squash {
    0%   { transform: scaleX(1); }
    38%  { transform: scaleX(0.94); }
    100% { transform: scaleX(1); }
}

.chip.chip-squash-l {
    transform-origin: left center;   /* far-left chip: left edge pinned */
    animation: chip-squash 0.32s cubic-bezier(0.2, 0.9, 0.3, 1);
}

.chip.chip-squash-r {
    transform-origin: right center;  /* far-right chip: right edge pinned */
    animation: chip-squash 0.32s cubic-bezier(0.2, 0.9, 0.3, 1);
}

/* Render All: a square, centred dialog. */
.ra-modal {
    width: 240px;
    aspect-ratio: 1 / 1;
    min-height: 240px;
    display: flex;
    flex-direction: column;
}

.ra-modal .confirm-body {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
}

/* align-items:center collapsed the bar to content width (invisible) —
   give it an explicit width. */
.ra-modal .ra-bar {
    width: 82%;
    align-self: center;
}

.ra-diag {
    margin-top: 7px;
    font-size: 9.5px;
    color: var(--text-faint);
    min-height: 12px;
    max-width: 92%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
