/* ── Admin kit: the controls, tables and editors the admin shares with
   owner pages ──────────────────────────────────────────────────────────────
   Split out of pages/admin.css so a page that only needs the editor does not
   also take the admin's layout. That sheet is not scoped to admin pages: its
   `.app-content { max-width: 1200px }` beats core.css's 600px column, which
   stretched a star's own profile at 601–1099px while it loaded admin.css for
   the "My stories" composer. Loaded by admin.html and myshops.html (just
   before admin.css, which still overrides it) and lazily by celebrity.html.
   Moved verbatim, so like admin.css it is not on the --fs ledger yet. */

/* ── Filters bar ──────────────────────────────────────────────────────────── */

.admin-filters {
    display: flex;
    gap: 10px;
    margin-bottom: 16px;
    flex-wrap: wrap;
    align-items: center;
}

/* The admin form control — one look for every field type in the admin UI.
   Named for what it is, not for one element: it already sits on 50 <select>s,
   9 <input>s and the selections textarea, so the old `.admin-filter-select`
   both misdescribed most of its users and blocked the textarea from reusing it
   (which is why that textarea carried a hand-copied duplicate of this block).

   Height and cursor are the single-line-select traits, split out below — a
   textarea sizes from `rows` and must show a text caret. Same split
   `.tp-input` (components.css) already makes. */
.admin-control {
    box-sizing: border-box;
    padding: 0 12px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    font-family: inherit;
    font-size: 0.875rem;
    color: var(--text-color);
    background: var(--surface);
}
input.admin-control, select.admin-control { height: var(--control-height); }
select.admin-control { cursor: pointer; }
textarea.admin-control { height: auto; padding: 6px 10px; resize: vertical; }
.admin-control:has(option[value=""]:checked) { color: #999; }
.admin-control option { color: var(--text-color); }
.admin-control option[value=""] { color: #999; }

/* ── Popover anchored under an admin control ──────────────────────────────────
   The admin already had a popover shell — .sel-cal-pop, the Selections period
   picker — but it is hard-sized for that modal's right column (400px wide,
   anchored to the field's RIGHT edge). This is the same surface treatment sized
   to its own content and opening under the trigger's LEFT edge, which is what a
   control in a filter bar wants. Used by <date-field> and by the Jobs header
   count's per-type breakdown.

   position:absolute is right in a filter bar and WRONG in the edit <modal>,
   where .modal-body's overflow clips it and the modal's transform re-bases
   position:fixed — a dialog needs the <teleport> treatment ColorPicker.js uses. */
.admin-pop-anchor { position: relative; }
.admin-pop {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 20;
    margin-top: 4px;
    padding: 10px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);
}

/* <date-field> (components/DateField.js) — the trigger has to read like the
   selects beside it. `input.admin-control, select.admin-control` scopes the
   height to those two elements, so a <button> must restate the token, and
   cursor:pointer for the same reason select.admin-control carries it. min-height
   rather than height so an inflated label grows the box instead of spilling out
   of it. The empty-state hint greys itself by composing .text-muted on the value
   span, so there is no second placeholder colour to keep in sync.
   The calendar inside reuses the .sel-cal-* block unchanged. */
.df-trigger {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    min-height: var(--control-height);
    cursor: pointer;
    text-align: left;
}
.df-value { white-space: nowrap; }
.df-pop   { width: 290px; }
/* Range mode's hint takes its own line above Clear / Today. */
.df-pop .sel-cal-foot { flex-wrap: wrap; row-gap: 6px; }
.df-hint { flex-basis: 100%; }

/* ── Table ────────────────────────────────────────────────────────────────── */

.admin-table-wrap {
    overflow-x: auto;
    border-radius: 8px;
    border: 1px solid var(--border-color);
    background: #fff;
}

.admin-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.875rem;
}

.admin-table th {
    background: #f9f9f9;
    padding: 10px 12px;
    text-align: left;
    font-weight: 600;
    color: #666;
    border-bottom: 1px solid var(--border-color);
    white-space: nowrap;
}

.admin-table td {
    padding: 9px 12px;
    border-bottom: 1px solid var(--surface-track);
    vertical-align: middle;
}

/* Column alignment uses the generic .col-left/.col-center/.col-right/.col-nowrap
   utilities (components.css) — no admin-table-scoped copies. Actions columns are
   .col-right too; their flex button row needs the flex equivalent since
   text-align can't move flex items. */
.admin-table .col-right .admin-action-btns { justify-content: flex-end; }
/* A table cell grows to its min-content, so nowrap can't spill here — it only
   stops a wide neighbour column (Brands ▸ Merge candidates' raw-brand badges)
   squeezing the cell until "Review & merge" breaks one word per line. */
.admin-table .admin-action-btns > .btn { white-space: nowrap; flex-shrink: 0; }
.admin-table tr:last-child td { border-bottom: none; }
.admin-table tr:hover td,
.admin-table tr:active td     { background: #fafafa; }

.admin-loading { text-align: center; padding: 40px; color: #999; }
.admin-loading::before {
    content: "";
    display: block;
    width: 28px;
    height: 28px;
    margin: 0 auto 12px;
    border: 3px solid var(--border-color);
    border-top-color: var(--pink-highlight);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}
.admin-empty   { text-align: center; padding: 30px; color: #999; }

/* ── Action icon buttons ──────────────────────────────────────────────────── */

.admin-action-btns {
    display: flex;
    gap: 4px;
    align-items: center;
}

.admin-icon-btn {
    background: none;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    padding: 4px 6px;
    cursor: pointer;
    color: #666;
    display: flex;
    align-items: center;
    transition: background 0.15s, color 0.15s;
}

.admin-icon-btn:hover,
.admin-icon-btn:active,
.admin-icon-btn:focus-visible { background: #f0f0f0; color: var(--text-color); }
.admin-icon-btn.danger:hover  { background: #fde8e8; color: #c0392b; border-color: #f5c6cb; }
.admin-icon-btn.primary:hover { background: #fce4f3; color: var(--pink-highlight); border-color: #f0b8d8; }

.form-row-inline {
    display: flex;
    align-items: center;
    gap: 10px;
}

.form-row-inline label { margin: 0; flex: 1; }

img.favicon-sm { width: 16px; height: 16px; object-fit: contain; border-radius: 2px; flex-shrink: 0; }
span.favicon-sm { width: 16px; height: 16px; display: inline-block; border: 1px dashed #ccc; border-radius: 2px; flex-shrink: 0; }

.form-error { font-size: 0.8rem; color: #c0392b; margin-top: 3px; }

/* Name cells in list tables: clicking the name opens the row's edit modal —
   pointer only, no underline (the Shops-listing pattern, applied everywhere).
   Underline stays reserved for .clickable-count drill-downs. */
.clickable-name { cursor: pointer; }

/* ── Selections module (rule-builder + preview) ─────────────────────────── */

.sel-edit-body { display: flex; flex-direction: column; gap: 18px; }
.sel-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.sel-field { display: flex; flex-direction: column; gap: 4px; }
.sel-field > span { font-size: 0.72rem; font-weight: 600; color: var(--text-light); text-transform: uppercase; letter-spacing: 0.04em; }
/* container-type makes this the query container for the rules builder below.
   It wraps EVERYTHING the stacked layout touches — .sel-section-head and the
   `+ OR group` button live here, outside .sel-group — so the whole block can
   key off one element. See the @container rule further down. */
.sel-rules { display: flex; flex-direction: column; gap: 22px; container: sel-rules / inline-size; }
.sel-section-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; border-bottom: 1px solid var(--border-color); padding-bottom: 6px; }
.sel-section-head h3 { margin: 0; font-size: 0.95rem; }
.sel-conn { display: flex; align-items: center; gap: 4px; font-size: 0.78rem; color: var(--text-light); }
.sel-group { border: 1px solid var(--border-color); border-radius: 10px; padding: 12px; margin-top: 12px; background: var(--bg-light); }
.sel-group-head { display: flex; align-items: center; gap: 6px; margin-bottom: 10px; font-size: 0.78rem; }
/* Right-aligned cluster: move-up / move-down / remove. Order = group priority
   on the compose path, so the arrows are a real ranking control. */
.sel-group-head .sel-group-actions { margin-left: auto; display: flex; gap: 4px; }
.sel-group-move { padding: 2px 6px; }
.sel-group-move:disabled { opacity: 0.35; cursor: default; }
.sel-cond { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; flex-wrap: nowrap; }
/* nowrap is what lets the row fit a 615px container: under `wrap` flex places
   items at their CONTENT width before it shrinks anything, so the two selects
   claimed the line and pushed the value widget onto a second one. The
   pasted-list row is the exception — its textarea is a deliberate full-width
   second line (rule further down), which needs wrapping to exist. */
.sel-cond:has(> .sel-val-textarea) { flex-wrap: wrap; }
/* :where(:not(textarea)) and not a bare :not() — :where() adds ZERO specificity,
   so this stays (0,2,0). The pasted-list textarea is an .admin-control too and
   must keep sizing from its `rows`, not get pinned to the row height. */
.sel-cond > .admin-control:where(:not(textarea)) { height: 34px; }
/* Fixed proportions, not content-then-leftovers: the value widget is the one you
   type into, so it holds 2/8 of the row rather than whatever the two selects
   leave behind — which at a 615px container was 55px, a "value" placeholder cut
   in half. min-width:0 opts every item out of the flex `auto` minimum, which
   otherwise pins each select to its longest option label; a shrunk select just
   ellipsises. max-width:none because the caps below did this job before the
   shares existed and would now fight them.

   :where() again, same reason as the height rule above: at (0,2,0) this stays
   BELOW the `.sel-cond > .sel-val-textarea` full-width-row rule further down,
   which it would otherwise out-rank now that the textarea shares this class —
   collapsing the pasted-list box back inline. */
.sel-cond > .admin-control:where(:not(.sel-val)) { flex: 3 1 0; min-width: 0; }
.sel-cond > .sel-val,
.sel-cond > .sel-val-wide { flex: 2 1 0; min-width: 0; max-width: none; }
.sel-cond > .sel-range { flex: 3 1 0; }
.sel-val { max-width: 130px; }
.sel-val-wide { max-width: 260px; flex: 1; }
.sel-multi { display: flex; flex-wrap: wrap; gap: 6px; }
.sel-cond-x, .sel-group-x { padding: 2px 6px; }
/* Range pair (min/max, date-from/to) kept together as one flex item.
   min-width:0 lets the pair shrink to its container (else its flex default
   min-width:auto holds the inputs' intrinsic width and overflows on mobile). */
.sel-range { display: flex; gap: 8px; min-width: 0; }
.sel-range .sel-val { flex: 1; min-width: 0; }
@media (max-width: 600px) {
    .sel-grid { grid-template-columns: repeat(2, 1fr); }
}

/* Rules builder — stack each condition into a self-contained card so the row
   can't overflow (wider containers keep the inline layout).

   A CONTAINER query, not a viewport one, because the viewport does not
   determine this layout and cannot express it. The builder lives in a
   .modal-dialog, which core.css makes a full-bleed sheet below 1201px but caps
   at 660px from 1201px up — so the container gets NARROWER as the viewport gets
   WIDER, and a viewport breakpoint cannot express that.

   560px is a legibility floor, not a fit one: the row shrinks its selects rather
   than wrapping them, so it never breaks at any width — it only goes unreadable,
   which the probe cannot see. Measured by eye against the longest option labels
   ("Has cross-shop offers", "more than (days) ago"): intact at 580px of
   container, truncated to "Has cross-sho⌄" at 421px.

   This also holds under display zoom, page zoom and font scaling, none of which
   a viewport breakpoint tracks: a CSS viewport is not a device width, and a
   big-screen Samsung set toward "more content" reports 480–540 CSS px. */
@container sel-rules (max-width: 560px) {
    .sel-section-head { flex-wrap: wrap; }
    .sel-group-head   { flex-wrap: wrap; row-gap: 6px; }

    .sel-cond {
        position: relative;
        flex-direction: column;
        align-items: stretch;
        gap: 8px;
        padding: 10px 46px 10px 10px;   /* right gutter reserves room + gap for ✕ */
        margin-bottom: 10px;
        border: 1px solid var(--border-color);
        border-radius: 8px;
        background: #fff;
    }
    /* The shrink-to-fit flex above is a ROW-layout tool; the card stacks, so
       reset it — otherwise flex-grow applies down the vertical main axis. The
       selects stretch the moment anything gives the card free height, and
       .sel-val-wide does the opposite and COLLAPSES: `flex: 1` is `1 1 0%`, and
       in a column a flex-basis replaces `height`, so its 34px lost to a 0 basis
       against a content-height card and the input rendered ~18px tall beside
       two 34px selects. Same reset as the textarea below, same reason. */
    .sel-cond > .admin-control:not(.sel-val),
    .sel-cond > .sel-val,
    .sel-cond > .sel-val-wide,
    .sel-cond > .sel-range { flex: 0 0 auto; }
    /* Every control fills the card width… */
    .sel-cond > .admin-control,
    .sel-cond .sel-val,
    .sel-cond .sel-val-wide,
    .sel-cond .sel-val-textarea { max-width: none; width: 100%; }
    /* Undo the pasted-list textarea's own-row flex (defined below): the card is
       a COLUMN, so a 100% basis would size it against the height. The
       .sel-rules prefix is load-bearing — @container adds NO specificity, and
       the rule it overrides appears LATER in the file, so at equal specificity
       source order would win. */
    .sel-rules .sel-cond > .sel-val-textarea { flex: 0 0 auto; order: 0; }
    /* …except the min/max pair, which shares one row 50/50. */
    .sel-range .sel-val { width: auto; max-width: none; }
    /* Remove control pinned to the card's top-right gutter. */
    .sel-cond-x { position: absolute; top: 8px; right: 8px; margin: 0; }
    /* Full-width tap targets for the add buttons. */
    .sel-group > .btn-sm, .sel-rules > .btn-sm { width: 100%; }
}

/* ── Selections: full-width main fields + status toggle + connector/limit ── */
.sel-main { display: flex; flex-direction: column; gap: 12px; }
.sel-main .sel-grid { margin: 0; }
.sel-conn-select { height: 30px; padding: 0 6px; font-size: 0.78rem; max-width: 96px; }
.sel-group-limit { display: inline-flex; align-items: center; gap: 4px; font-size: 0.78rem; color: var(--text-light); margin-left: 4px; }
.sel-limit-input { width: 58px; height: 30px; padding: 0 6px; border: 1px solid var(--border-color); border-radius: 6px; font-family: inherit; font-size: 0.82rem; }

/* ── Selections: grid-3, multitext textarea, range calendar, collapsible ── */
.sel-grid.sel-grid-3 { grid-template-columns: repeat(3, 1fr); }
/* The element carries .admin-control, so border, radius, type scale, colour and
   surface all come from there — this class only adds what is specific to the
   pasted-list box. It briefly duplicated that block by hand, because back when
   the shared class was called `.admin-filter-select` the row rules keyed on it
   were specific enough to collapse this box back inline; both those rules are
   :where()-wrapped now, so reuse is safe. (Before that it carried .tp-input,
   whose 1.5px border / 10px radius / #fafafa fill read as a different component
   sitting between two selects.) */
.sel-val-textarea { max-width: 260px; flex: 1; min-height: 46px; }
/* Pasted-list widgets ("is any of" / "is none of" on ids, GTINs, brands…) get
   their OWN full-width row inside the condition. Squeezed between two selects
   and the ✕ the textarea was ~260px — too narrow to read a list of ids, which
   is the one value type that genuinely needs width. The ✕ is re-ordered ahead
   of it so row 1 stays [field][operator][✕] and the box sits underneath.
   Unconditional, then RESET inside the @container block above: this is a
   row-layout tool, and in the stacked card `.sel-cond` is a column, where
   `flex-basis: 100%` would resolve against the height. It must not be keyed to
   a viewport breakpoint either — the container can be ≤560px (card layout)
   while the viewport is wide, so a `min-width` media query would leave the
   basis applied down the column axis in that band. */
.sel-cond > .sel-val-textarea { flex: 1 0 100%; max-width: none; order: 1; }
.sel-cond > .sel-cond-x       { order: 0; }
/* Active-period range calendar */
.sel-cal { border: 1px solid var(--border-color); border-radius: 10px; padding: 10px; max-width: 290px; }
.sel-cal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.sel-cal-title { font-weight: 600; font-size: 0.85rem; }
.sel-cal-nav { border: none; background: none; cursor: pointer; font-size: 1.2rem; line-height: 1; padding: 0 10px; color: var(--text-light); }
.sel-cal-nav:hover { color: var(--pink-highlight); }
.sel-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.sel-cal-dow { margin-bottom: 4px; }
.sel-cal-dow span { text-align: center; font-size: 0.64rem; color: var(--text-light); text-transform: uppercase; }
.sel-cal-day { border: none; background: none; height: 30px; border-radius: 6px; cursor: pointer; font-size: 0.8rem; font-family: inherit; color: var(--text-color); padding: 0; }
.sel-cal-day:hover:not(.empty):not(.start):not(.end) { background: var(--pill-grey-bg); }
.sel-cal-day.empty { visibility: hidden; cursor: default; }
.sel-cal-day.inrange { background: var(--pill-pink-bg); border-radius: 0; }
.sel-cal-day.start, .sel-cal-day.end { background: var(--pink-highlight); color: #fff; }
.sel-cal-day.start { border-radius: 6px 0 0 6px; }
.sel-cal-day.end { border-radius: 0 6px 6px 0; }
.sel-cal-day.start.end { border-radius: 6px; }
.sel-cal-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 8px; font-size: 0.75rem; }
/* Collapsible "Placement options" */
.sel-collapse-head { display: flex; align-items: center; gap: 8px; background: none; border: none; cursor: pointer; font-family: inherit; font-size: 0.95rem; font-weight: 600; color: var(--text-color); padding: 0; width: 100%; }
.sel-collapse-head svg { transition: transform 0.2s; }
.sel-collapse-head .sel-chev-open { transform: rotate(180deg); }
.sel-collapse-body { display: flex; flex-direction: column; gap: 12px; padding-top: 12px; }

/* Active-period: single field + calendar popover */
.sel-period { position: relative; }
/* Height 40px matches components.css's `input.tp-input, select.tp-input`, which
   don't target this <div>, so without it the period field renders shorter than
   its sibling Country/Sort/etc controls. */
.sel-period-field { height: 40px; cursor: pointer; display: flex; align-items: center; color: var(--text-color); }
.sel-cal-pop { position: absolute; top: 100%; left: 0; z-index: 20; margin-top: 4px; background: #fff; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16); }

/* Selections: 2-col rows + one wider range-calendar popover. The period field
   sits in the right column, so anchor the popover to the field's RIGHT edge
   (opens leftward) and size it to the modal so it never spills off-screen. */
.sel-grid.sel-grid-2 { grid-template-columns: repeat(2, 1fr); }

/* Section-background control: type dropdown + colour picker(s) (+ angle for
   gradients) all on ONE row — fixed widths sized so even the gradient set
   (dropdown + 2 pickers + angle) fits the 660px dialog without wrapping. */
.sel-bg-field { gap: 8px; }
.sel-bg-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.sel-bg-row .sel-bg-type { flex: none; width: 130px; }
.sel-bg-row .cp { flex: none; width: 132px; }
.sel-bg-row .sel-bg-angle { flex: 1; min-width: 130px; }
/* Mobile: keep dropdown + colour square(s) on ONE row — collapse the pickers
   to their squares (hex stays available inside the popover). */
@media (max-width: 600px) {
    .sel-bg-row .sel-bg-type { flex: 1; width: auto; min-width: 100px; }
    .sel-bg-row .cp { width: auto; }
    .sel-bg-row .cp-hex { display: none; }
}
.sel-bg-angle { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 160px; }
.sel-bg-angle > span { font-size: 0.72rem; font-weight: 600; color: var(--text-light); text-transform: uppercase; letter-spacing: 0.04em; }
.sel-bg-angle input[type="range"] { width: 100%; accent-color: var(--pink-highlight); }
/* Text input + inline colour square(s) on its right (compact color-picker). */
.sel-input-color { display: flex; align-items: center; gap: 8px; }
.sel-input-color .tp-input { flex: 1; min-width: 0; }
.sel-input-color .cp { flex: none; }
/* CTA-link mode switch, in front of the URL field inside .sel-input-color. Both
   children are .tp-input, so without this the two-option select would take the
   50/50 share the rule above gives every field — same fixed width as
   .sel-bg-type so the modal's two select-then-field rows line up. */
.sel-input-color .sel-cta-mode { flex: none; width: 130px; }
@media (max-width: 600px) {
    .sel-input-color .sel-cta-mode { width: auto; min-width: 100px; }
}
/* Live rail-heading preview: mini version of .home-rail-head with the chosen
   background + text/CTA colours (defaults mirror the real rail's theme). */
.sel-bg-preview {
    min-height: 54px;
    padding: 10px 14px;
    border-radius: var(--radius-lg);
    border: 1px solid var(--border-color);
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    margin-bottom: 16px;
}
.sel-prev-heading { min-width: 0; }
.sel-prev-title { font-size: 1.05rem; font-weight: 700; color: var(--text-color); line-height: 1.2; }
.sel-prev-sub { font-size: 0.8rem; color: var(--text-light); margin-top: 2px; }
.sel-prev-cta { flex-shrink: 0; white-space: nowrap; pointer-events: none; }
.sel-cal-pop { max-width: none; width: 400px; left: auto; right: 0; }
.sel-cal-pop .sel-cal-day { height: 38px; }
@media (max-width: 600px) { .sel-cal-pop { width: 80vw; } }

/* A name link in an admin table is pink like every other action here; without
   this the global `a` rule paints it link-blue. */
.admin-table a.clickable-name { color: var(--pink-highlight); }

/* Page picture (Selection modal ▸ Page options) — picture left, its controls
   and description right. The description is four lines of guidance and read as
   a caption stranded under a 260px image when everything stacked.

   Stacks at 620, not at a phone width: the modal body is ~615px on a 1440
   screen, so "wide viewport" tells you nothing about the room this actually has
   — the same reason the rules builder stacks there. */
.sel-page-image {
    display: flex;
    gap: 14px;
    align-items: flex-start;
}

.sel-page-image-thumb {
    /* Fixed basis, not intrinsic: the row must not reflow when the file loads. */
    flex: 0 0 260px;
    max-width: 260px;
    width: 100%;
    border-radius: var(--radius-lg);
    display: block;
}

.sel-page-image-body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

@media (max-width: 620px) {
    .sel-page-image { flex-direction: column; }
    .sel-page-image-thumb { flex: 0 0 auto; }
}

@media (prefers-reduced-motion: reduce) {
    .admin-icon-btn,
    .sel-collapse-head svg {
        transition: none;
        animation: none;
    }

    /* Progress feedback, not decoration: a spinner that stops spinning
       stops saying "still working". Its transitions go; its spin stays. */
    .admin-loading::before {
        transition: none;
    }
}
