/* ============================================================
   Inkara Admin Dashboard — Shared Styles
   Matches Widget Dashboard brand: Inter + DM Serif Display,
   warm cream/paper palette, rose/mauve accent
   ============================================================ */

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

:root {
    /* Brand tokens */
    --ink: #1a1a2e;
    --paper: #faf9f6;
    --accent: #af2860;
    --accent-dark: #8e1f4d;
    --accent-light: #f4e2e8;
    --border: #e2ddd5;
    --muted: #8a8477;
    --success: #2d6a4f;
    --danger: #c0392b;
    --warning: #f39c12;
    --radius: 6px;
    --sidebar-bg: #1a1a2e;
    --sidebar-hover: #2c2c44;
    --card-bg: #fff;

    /* Aliases for backward compat (used in views.js inline styles) */
    --bg: var(--paper);
    --primary: var(--accent);
    --primary-dark: var(--accent-dark);
    --text: var(--ink);
    --text-light: var(--muted);

    /* ── Typography tokens ────────────────────────────────────
       Colour was tokenised from the start; type never was, so the
       same values sat hardcoded in ~135 font-family declarations
       and every style could only be described by pointing at it.
       These feed the named house styles below. */
    --font-serif: 'DM Serif Display', serif;
    --font-sans: 'Inter', sans-serif;

    --text-display: 1.8rem;   /* page heroes */
    --text-heading: 22px;     /* modal + card titles */
    --text-section: 1.2rem;   /* rail / group headers */
    --text-body: 0.95rem;
    --text-caption: 14px;     /* help + description text */
    --text-label: 0.75rem;    /* form field labels */
}

/* ============================================================
   HOUSE STYLES — the named typography vocabulary.

   Say the NAME to refer to a style ("make that the Caption style")
   rather than describing pixels. Six names cover the dashboard
   chrome; add a seventh only when something genuinely recurs.

     Display  page hero titles, and the      (serif, 1.8rem)
              title of a decision modal
              whose CARDS carry Headings
     Heading  modal + card titles         (serif, 22px)   e.g. "Photo Library"
     Section  rail / group headers        (sans, 1.2rem, --muted)
     Body     default running copy        (sans, 0.95rem)
     Caption  help + description text     (sans, 14px muted)
              e.g. "Search for a background image"
     Label    form field labels           (sans, 0.75rem uppercase)

   Semantic names, not descriptive ones: `.hs-heading`, never
   `.hs-serif-22`, so a restyle changes one rule instead of making
   every name a lie.

   SCOPE: admin dashboard chrome only. Typography inside GENERATED
   assets (inklet configs, Matching Fonts, the widget-renderer's
   per-book fonts) is a separate system and these names never
   refer to it.

   ADOPTION: use these for all new work. Existing rules migrate
   opportunistically when we're already editing that area — a
   135-declaration big-bang sweep carries real regression risk and
   no user-visible payoff. The names are useful from today.
   ============================================================ */

.hs-display {
    font-family: var(--font-serif);
    font-size: var(--text-display);
    letter-spacing: -0.3px;
    line-height: 1.2;
    /* No colour: heroes sit on photographic backgrounds and set
       their own (white). */
}
.hs-heading {
    font-family: var(--font-serif);
    font-size: var(--text-heading);
    letter-spacing: -0.3px;
    color: var(--ink);
    line-height: 1.3;
}
.hs-section {
    font-family: var(--font-sans);
    font-size: var(--text-section);
    font-weight: 400;
    color: var(--muted);
    line-height: 1.2;
}
.hs-body {
    font-family: var(--font-sans);
    font-size: var(--text-body);
    color: var(--ink);
    line-height: 1.5;
}
.hs-caption {
    font-family: var(--font-sans);
    font-size: var(--text-caption);
    color: var(--muted);
}
.hs-label {
    font-family: var(--font-sans);
    font-size: var(--text-label);
    font-weight: 500;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

body {
    font-family: 'Inter', system-ui, -apple-system, sans-serif;
    background: var(--paper);
    color: var(--ink);
    line-height: 1.5;
    min-height: 100vh;
}

.hidden { display: none !important; }

/* --- Login --- */
#login-screen {
    background: #faf9f6;
    min-height: 100vh;
    display: flex;
    justify-content: center;
    padding-top: 60px;
}
.login-box {
    text-align: center;
    padding: 2.5rem 3rem;
    background: var(--card-bg);
    border-radius: var(--radius);
    box-shadow: 0 8px 24px rgba(0,0,0,0.08);
    width: 100%;
    max-width: 350px;
    height: fit-content;
}
/* The Set-Password (invite) form carries the Terms-of-Use box + agree check,
   so it needs more room than the slim sign-in card. Widen the login card only
   while that form is the visible one. */
.login-box:has(#change-pw-form:not(.hidden)) {
    max-width: 450px;
}
.login-logo {
    height: 80px;
    width: auto;
    margin-bottom: 1.25rem;
}
.login-heading {
    font-family: 'DM Serif Display', serif;
    font-size: 1.25rem;
    color: var(--ink);
    line-height: 1.4;
    margin-bottom: 1.5rem;
    letter-spacing: -0.3px;
}
.login-subtitle {
    font-size: 0.85rem;
    color: var(--muted);
    margin-bottom: 1.25rem;
    line-height: 1.5;
}
.login-field {
    text-align: left;
    margin-bottom: 1rem;
}
.login-field label {
    display: block;
    font-size: 0.75rem;
    font-weight: 500;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    margin-bottom: 0.3rem;
}
.login-field input {
    width: 100%;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    font-size: 0.875rem;
    font-family: 'Inter', sans-serif;
    background: #fff;
    color: var(--ink);
    transition: border-color 0.15s;
}
.login-field input:focus {
    outline: none;
    border-color: var(--accent);
}
.btn-block {
    display: block;
    width: 100%;
    padding: 0.65rem 1rem;
    margin-top: 0.5rem;
}
.login-link {
    display: inline-block;
    margin-top: 1rem;
    font-size: 0.8rem;
    color: var(--muted);
    text-decoration: none;
    transition: color 0.15s;
}
.login-link:hover {
    color: var(--accent);
}
.login-error {
    background: #fdf0f0;
    border: 1px solid #e8c4c4;
    color: #8b2020;
    font-size: 0.8rem;
    padding: 0.6rem 0.75rem;
    border-radius: var(--radius);
    margin-bottom: 1rem;
    text-align: left;
}

/* --- Buttons --- */
.btn {
    display: inline-block;
    padding: 0.5rem 1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--card-bg);
    color: var(--ink);
    cursor: pointer;
    font-size: 0.875rem;
    font-family: 'Inter', sans-serif;
    font-weight: 500;
    text-decoration: none;
    transition: all 0.15s;
}
.btn:hover { border-color: var(--accent); color: var(--accent); }
.btn-primary { background: var(--accent); color: #fff; border-color: var(--accent); }
.btn-primary:hover { background: var(--accent-dark); border-color: var(--accent-dark); color: #fff; }
.btn-success { background: var(--success); color: #fff; border-color: var(--success); }
.btn-success:hover { opacity: 0.9; }
.btn-danger { background: var(--danger); color: #fff; border-color: var(--danger); }
/* Explicit color/bg/border to win against the generic .btn:hover rule
   above (same specificity), which would otherwise turn the text accent-pink
   on the red bg — unreadable. Darker red maintains contrast. */
.btn-danger:hover { background: #a02822; border-color: #a02822; color: #fff; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-sm { padding: 0.25rem 0.75rem; font-size: 0.8rem; }
.btn-lg { padding: 0.75rem 2rem; font-size: 1rem; }
.btn-spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid rgba(255,255,255,0.3); border-top-color: #fff; border-radius: 50%; animation: spin 0.6s linear infinite; vertical-align: middle; margin-right: 4px; }
@keyframes spin { to { transform: rotate(360deg); } }

/* Outline accent button — used by the Suggest buttons next to the
   BISAC/Thema hint fields on the event-edit modals. Visually it's an
   accent-coloured outline with no fill, going to a soft accent fill on
   hover. */
.btn-outline-accent { background: transparent; border: 1px solid var(--accent); color: var(--accent); }
.btn-outline-accent:hover { background: var(--accent-light); border-color: var(--accent); color: var(--accent); }
/* Editor header "Template" indicator — Inkara accent outline by default,
   flips to neutral muted on hover. Sits in the editor's header slot
   (#ie-template-slot) whenever an Inklet template is being edited. */
.me-template-badge {
    background: transparent;
    border: 1px solid var(--accent);
    color: var(--accent);
    border-radius: 6px;
    padding: 4px 10px;
    cursor: pointer;
    font-size: 0.82rem;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
    transition: border-color 0.15s ease, color 0.15s ease;
}
.me-template-badge:hover { border-color: var(--muted); color: var(--muted); }

/* In-form helper text under labelled inputs (BISAC/Thema hints,
   description fields, etc.). Sits muted + small so it reads as a
   sub-hint rather than competing with the input value. */
.field-help { font-size: 0.8rem; color: var(--muted); margin-top: 4px; line-height: 1.4; }

/* Modal form-field labels — one notch larger + semibold compared to the
   base `.field label` (12px / 500) defined in widget-shared.css. Applied
   to every form modal that uses .modal-user (user, channel, channel-group,
   event, settings-event, upload, etc.). */
.modal-user .field label { font-size: 13px; font-weight: 600; }

/* --- Action menu (3-dot dropdown) — still used by views-users.js;
 *     the Streams page has migrated to the unified Stream Edit modal
 *     below but the Users page hasn't yet. Keep both. --- */
.action-menu-btn { font-size: 1.2rem; line-height: 1; padding: 0.2rem 0.5rem; letter-spacing: 1px; background: var(--paper); border: 1px solid var(--accent); color: var(--accent); cursor: pointer; border-radius: 4px; transition: all 0.15s; font-weight: 900; }
.action-menu-btn:hover { background: var(--accent); border-color: var(--accent); color: #fff; }
.action-menu { position: absolute; right: 0; top: 100%; background: #fff; border: 1px solid var(--border); border-radius: var(--radius); box-shadow: 0 8px 24px rgba(0,0,0,0.08); z-index: 100; min-width: 150px; padding: 4px 0; }
.action-menu-item { display: block; width: 100%; padding: 8px 16px; border: none; background: none; text-align: left; cursor: pointer; font-size: 0.85rem; font-family: 'Inter', sans-serif; color: var(--ink); transition: background 0.15s, color 0.15s; }
.action-menu-item:hover { background: var(--accent-light); color: var(--accent); }

/* --- Stream Edit modal (Streams page row → unified four-tab dialog) --- */
/* Sized 60vw × 80vh per the approved plan. Header (title + close-X)
 * + tab bar + scrollable body are the three vertical sections. */
.modal.stream-edit-modal {
    width: 80vw;
    max-width: 80vw;
    height: 80vh;
    padding: 0;
    display: flex;
    flex-direction: column;
}
.stream-edit-header {
    padding: 16px 20px 12px;
    border-bottom: 1px solid var(--border);
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 12px;
    flex-shrink: 0;
}
/* Tab bar inside the stream-edit modal — kill the default
 * margin-bottom so the body's own padding handles spacing. */
.modal.stream-edit-modal .tab-bar {
    padding: 0 20px;
    margin: 0;
    flex-shrink: 0;
}
/* Body has its own unique class (no clash with .modal) so the simple
 * selector works — earlier I chained .modal.stream-edit-modal-body by
 * accident which matched nothing, leaving the body padding-less. */
.modal.stream-edit-modal .stream-edit-modal-body {
    flex: 1;
    overflow-y: auto;
    padding: 24px 32px;
}

/* --- Layout --- */
#main-screen { display: flex; min-height: 100vh; }

.sidebar {
    width: 240px;
    background: var(--paper);
    color: var(--ink);
    display: flex;
    flex-direction: column;
    position: fixed;
    top: 0; bottom: 0;
    border-right: none;
    transition: width 0.2s ease;
    overflow: hidden;
    z-index: 50;
}
.sidebar-header { padding: 1.5rem; }
.sidebar-header-row { display: flex; align-items: center; justify-content: space-between; }
.sidebar-header img.sidebar-logo { height: 63px; width: auto; }
.sidebar-collapse-btn { background: none; border: none; cursor: pointer; padding: 4px; color: var(--muted); transition: color 0.15s; }
.sidebar-collapse-btn:hover { color: var(--ink); }
.sidebar-collapse-btn svg { width: 18px; height: 18px; }
.nav-links a.nav-disabled { color: var(--muted); opacity: 0.5; cursor: default; pointer-events: none; }
#nav-logout { color: var(--muted); }
#nav-logout:hover { color: var(--accent); background: rgba(175,40,96,0.04); }
.user-email { font-size: 0.75rem; color: var(--muted); word-break: break-all; display: block; margin-top: 0.35rem; }

.nav-links { list-style: none; padding: 0.5rem 0; flex: 1; }
.nav-links a {
    display: block;
    padding: 0.5rem 1.5rem;
    color: var(--ink);
    text-decoration: none;
    font-size: 0.9rem;
    font-weight: 500;
    transition: all 0.15s;
    border-left: 3px solid transparent;
}
.nav-links a:hover, .nav-links a.active {
    background: rgba(175,40,96,0.04);
    color: var(--accent);
}
.nav-links a.active { border-left-color: var(--accent); }
/* Flat top-level rows (Feedback, Inkara HQ): their 3px active-indicator
   border pushes the icon 3px right of the accordion toggles (which carry
   border-left:none) — pull the padding back by the border width so the
   icon column lines up. Keeps the border so the active state still shows. */
.nav-links li.nav-flat > a { padding-left: calc(1.5rem - 3px); }

/* Primary nav cards — Books + Campaigns, the two daily destinations,
   lifted out of the flat link list so the sidebar reads "where I work"
   then "how it's configured". Scoped to li.nav-primary so the ordinary
   rows and every .nav-sub link keep the flat treatment above. */
.nav-links li.nav-primary { margin: 0.4rem 0.75rem; }
.nav-links li.nav-primary > a {
    display: flex;
    align-items: center;
    gap: 0.8rem;
    padding: 0.8rem 1rem;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--card-bg);
    font-size: 1rem;
    font-weight: 600;
    border-left-width: 1px;   /* cancel the 3px transparent accent rail */
}
.nav-links li.nav-primary > a svg { width: 22px; height: 22px; margin-right: 0; }
.nav-links li.nav-primary > a:hover {
    background: rgba(175,40,96,0.05);
    border-color: rgba(175,40,96,0.35);
}
/* Active: tinted fill + a thick accent rail down the left edge. The
   padding claw-back keeps the icon aligned with the inactive card. */
.nav-links li.nav-primary > a.active {
    background: rgba(175,40,96,0.09);
    border-color: rgba(175,40,96,0.28);
    border-left: 4px solid var(--accent);
    padding-left: calc(1rem - 3px);
}

/* Gap between the primary cards and the accordion groups. The visible
   1px line was dropped 2026-08-12 — the cards' borders already do the
   separating and the rule read as clutter. Kept as a spacer element so
   the breathing room survives. */
.nav-links li.nav-rule {
    height: 0;
    margin: 0.6rem 0;
}

/* Nav icons */
.nav-links a svg, .nav-accordion-toggle svg { width: 16px; height: 16px; vertical-align: -2px; margin-right: 8px; stroke-width: 2; }

/* Nav accordions */
/* Selector carries `a` + .nav-links so it outspecifies `.nav-links a`
   (0,1,1), which was silently winning the `display` declaration and
   computing these toggles to `block` — the chevron then sat inline right
   after the label instead of being pushed to the row's end by
   justify-content. The intent was always flex; only the specificity was
   short. */
.nav-links a.nav-accordion-toggle {
    display: flex;
    align-items: center;
    /* flex-start (not space-between): with three flex children the label
       would float to the middle, away from its icon. The chevron is
       pushed right by its own margin-left:auto instead. */
    justify-content: flex-start;
    cursor: pointer;
    border-left: none !important;
}
.nav-accordion-toggle::after {
    content: '\203A';
    font-size: 1.2rem;
    line-height: 1;
    color: var(--muted);
    transition: transform 0.2s ease;
    flex-shrink: 0;
    margin-left: auto;   /* pins the chevron to the row's end */
    margin-right: 0.5rem;
    display: inline-block;
    vertical-align: -3px;
    transform: rotate(90deg);
}
.nav-accordion.open > .nav-accordion-toggle::after {
    transform: rotate(-90deg);
}
.nav-sub {
    list-style: none;
    display: none;
    padding: 0;
}
.nav-accordion.open > .nav-sub {
    display: block;
}
.nav-sub a {
    padding-left: 2.5rem !important;
    padding-top: 0.2rem !important;
    padding-bottom: 0.2rem !important;
    font-size: 0.85rem !important;
}
/* Second-level group inside an accordion's .nav-sub: a static non-clickable
   label with its child links indented one step deeper (Super Admin >
   Streams & Ingestion). */
.nav-sub-group-label {
    display: block;
    padding: 0.45rem 1.5rem 0.15rem 2.5rem;
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
    cursor: default;
}
.nav-sub-group-label svg { width: 13px; height: 13px; vertical-align: -2px; margin-right: 6px; stroke-width: 2; }
.nav-sub a.nav-sub-child { padding-left: 3.25rem !important; }

/* Widths are tight: 240px rail − margins − padding leaves ~197px for
   avatar + name + sign-out. Sized so a long real name ("Mark
   Harvie-Watt") stays on one line rather than wrapping. */
.sidebar-user-card {
    margin: 0.75rem 0.6rem; padding: 0.6rem;
    border-radius: 12px;
    display: flex; align-items: center; gap: 0.45rem;
    text-align: left;
    font-size: 0.8rem; line-height: 1.4;
}
/* Initials disc + sign-out, added when Sign out left the nav list. Both
   sit on the user's pale inkara_colour, so they use a translucent-white
   fill rather than a fixed colour. */
.suc-avatar {
    flex-shrink: 0;
    width: 34px; height: 34px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: rgba(255,255,255,0.55);
    font-weight: 700; font-size: 0.75rem; color: var(--ink);
}
.suc-info { flex: 1; min-width: 0; }
.suc-logout {
    flex-shrink: 0;
    background: rgba(255,255,255,0.55);
    border: 1px solid rgba(0,0,0,0.08);
    border-radius: 8px;
    padding: 6px; line-height: 0; cursor: pointer;
    color: var(--ink); transition: all 0.15s;
}
.suc-logout:hover { background: #fff; color: var(--accent); }
.suc-logout svg { width: 16px; height: 16px; }
.suc-info:hover { opacity: 0.85; }
/* Sits between the logo header and the nav links (moved from above the
   user card 2026-07-30) — the select's own margin-bottom spaces it off
   the Overview link. */
#suc-account-filter-slot { padding: 0.25rem 1rem 0; }
.suc-account-select {
    width: 100%; padding: 5px 8px; margin-bottom: 10px;
    border: 1px solid var(--border); border-radius: 8px;
    background: #fff; color: var(--ink);
    font-size: 0.78rem; font-family: 'Inter', sans-serif;
    cursor: pointer; outline: none; display: block;
}
.sidebar-collapsed #suc-account-filter-slot { display: none; }
.suc-account-select:focus { border-color: var(--accent); }
.sidebar-user-card .suc-name { font-weight: 700; color: var(--ink); font-size: 0.78rem; }
.sidebar-user-card .suc-role { color: var(--ink); opacity: 0.7; font-size: 0.75rem; }
.sidebar-user-card .suc-account { color: var(--ink); opacity: 0.7; font-size: 0.75rem; }
.sidebar-user-card .suc-email { color: var(--ink); opacity: 0.6; font-size: 0.7rem; word-break: break-all; }
/* Build the user is actually running, under the card rather than inside it —
   it describes the app, not the person. Deliberately quiet: it is there to be
   read out when someone reports a bug, not to be noticed day to day. The same
   token rides along on every feedback ticket, so a report can be tied to a
   build even when the reporter doesn't think to mention it. */
.sidebar-version {
    margin: -0.35rem 0.6rem 0.5rem; padding: 0 0.6rem;
    font-size: 0.65rem; line-height: 1.3;
    color: var(--ink); opacity: 0.45;
    letter-spacing: 0.02em;
}
/* Collapsed rail: the card shrinks to just its sign-out button, ordered
   below the avatar disc. Identity is already covered by
   #sidebar-user-avatar, but sign-out is NOT — it left the nav list on
   2026-08-12, and hiding the whole card (the previous rule) would leave a
   collapsed sidebar with no way to sign out at all. `!important` beats the
   inline background-color the card gets from the user's inkara_colour. */
.sidebar-collapsed .sidebar-user-card {
    display: flex; justify-content: center;
    order: 2;
    background: none !important;
    margin: 0 0 12px; padding: 0;
}
.sidebar-collapsed .sidebar-user-card .suc-avatar,
.sidebar-collapsed .sidebar-user-card .suc-info { display: none; }
.sidebar-collapsed .sidebar-version { display: none; }
.sidebar-user-avatar {
    display: none; /* hidden when expanded */
}
.sidebar-collapsed .sidebar-user-avatar {
    display: flex; align-items: center; justify-content: center;
    order: 1;   /* above the collapsed card's sign-out button */
    width: 32px; height: 32px; border-radius: 50%;
    margin: 0 auto 12px; cursor: pointer;
    font-size: 0.85rem; font-weight: 700; color: var(--ink);
    transition: opacity 0.15s;
}
.sidebar-collapsed .sidebar-user-avatar:hover { opacity: 0.75; }

.sidebar-account { padding: 1rem 1.5rem; }
.sidebar-account label { display: block; font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin-bottom: 0.4rem; }
.sidebar-account select { width: 100%; padding: 0.4rem 0.5rem; border: 1px solid var(--border); border-radius: var(--radius); background: #fff; color: var(--ink); font-size: 0.8rem; font-family: 'Inter', sans-serif; cursor: pointer; }
.sidebar-account select:focus { outline: none; border-color: var(--accent); }


.content { margin-left: 240px; padding: 2rem; flex: 1; min-width: 0; transition: margin-left 0.2s ease; }

/* --- Sidebar expand button (visible only when collapsed) --- */
.sidebar-expand { display: none; position: fixed; top: 14px; left: 14px; cursor: pointer; z-index: 100; }
.sidebar-expand-icon { width: 28px; height: 28px; border-radius: 4px; }
.sidebar-expand-icon:hover { opacity: 0.7; }

/* --- Sidebar collapsed state --- */
.sidebar-collapsed .sidebar { width: 56px; background: #f3efe8; }
.sidebar-collapsed .sidebar-header { padding: 0.75rem 0; border-bottom: 1px solid var(--border); }
.sidebar-collapsed .sidebar-header-row { justify-content: center; }
.sidebar-collapsed .sidebar-logo { display: none; }
.sidebar-collapsed .sidebar-collapse-btn { padding: 6px; }
.sidebar-collapsed .sidebar-collapse-btn svg { transform: rotate(180deg); }
.sidebar-collapsed .user-email { display: none; }
.sidebar-collapsed .nav-links a span,
.sidebar-collapsed .nav-accordion-toggle span { display: none; }
.sidebar-collapsed .nav-links a { padding: 0.75rem 0; text-align: center; border-left: none; }
.sidebar-collapsed .nav-links a svg { margin-right: 0; }
.sidebar-collapsed .nav-sub { display: none !important; }
/* Matches the specificity bump on .nav-links a.nav-accordion-toggle above,
   or space-between would win here and shove the lone icon left. */
.sidebar-collapsed .nav-links a.nav-accordion-toggle { justify-content: center; padding: 0.75rem 0; }
.sidebar-collapsed .nav-accordion-toggle::after { display: none; }

/* --- Flyout: sub-menu as a floating panel when sidebar is collapsed --- */
/* JS sets .flyout-open on the <li.nav-accordion> plus inline `top` on the
   .nav-sub so the popover aligns with the clicked icon. */
.sidebar-collapsed .nav-accordion.flyout-open .nav-sub {
    display: block !important;
    position: fixed;
    left: 56px;                /* matches .sidebar-collapsed .sidebar width */
    min-width: 200px;
    padding: 4px 0;
    background: var(--paper);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.12);
    z-index: 200;
    animation: flyoutFade 0.12s ease-out;
}
/* In flyout mode, items use regular horizontal padding (not the 2.5rem
   indent used by the in-sidebar expanded accordion). Show the text labels
   that are hidden in the collapsed sidebar itself. */
.sidebar-collapsed .nav-accordion.flyout-open .nav-sub a {
    padding: 0.5rem 1rem !important;
    font-size: 0.9rem !important;
    display: flex;
    align-items: center;
    gap: 8px;
    text-align: left;
}
.sidebar-collapsed .nav-accordion.flyout-open .nav-sub a span {
    display: inline;
}
.sidebar-collapsed .nav-accordion.flyout-open .nav-sub a svg {
    width: 14px;
    height: 14px;
    margin-right: 0;
}
/* Group label + its children inside a flyout panel: flatter padding to
   match the flyout's compact layout. */
.sidebar-collapsed .nav-accordion.flyout-open .nav-sub .nav-sub-group-label {
    padding: 0.5rem 1rem 0.2rem;
}
.sidebar-collapsed .nav-accordion.flyout-open .nav-sub a.nav-sub-child {
    padding-left: 1.75rem !important;
}
@keyframes flyoutFade {
    from { opacity: 0; transform: translateX(-6px); }
    to   { opacity: 1; transform: translateX(0); }
}
.sidebar-collapsed .nav-links a.active { border-left: none; color: var(--accent); }
/* sidebar-account removed — filter now in suc-account-filter-slot */
.sidebar-collapsed .content { margin-left: 56px; }
.sidebar-collapsed .sidebar-expand { display: none; }

/* --- Cards --- */
.card {
    background: var(--card-bg);
    border: 1px solid rgba(0,0,0,0.04);
    border-radius: 14px;
    box-shadow: 0 2px 12px rgba(0,0,0,0.08);
    padding: 1.5rem;
    margin-bottom: 1.5rem;
}
.card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1rem;
}
.card-header h3 { font-family: 'DM Serif Display', serif; font-size: 1.1rem; letter-spacing: -0.3px; }

/* --- Stats row --- */
.stats-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 1rem;
    margin-bottom: 1.5rem;
}
.stat-card {
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1.25rem;
    box-shadow: 0 1px 3px rgba(0,0,0,0.04);
}
.stat-card .label { font-size: 0.75rem; font-weight: 500; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }
.stat-card .value { font-size: 1.75rem; font-weight: 700; margin-top: 0.25rem; color: var(--ink); }
.stat-card .sub { font-size: 0.8rem; color: var(--muted); margin-top: 0.25rem; }

/* --- Overview mid-row -----------------------------------------------
 * 2-column row: Marketing Campaigns (70%) on the left, Events stacked
 * over Trending (30% column) on the right. The right column's height
 * matches the left exactly via flex:1 on each child card, so the gap
 * is consumed inside the column instead of overflowing it.
 * Marketing card uses flex-direction:column so its tab-bar pins to the
 * top and the empty body region fills the remaining space.
 */
.overview-mid-row {
    display: grid;
    grid-template-columns: 4fr 1fr;   /* 80% / 20% */
    gap: 1.5rem;
    margin-bottom: 1.5rem;
}
.overview-mid-row .card { margin-bottom: 0; }
/* Marketing Campaigns card grows to fit its content — initially 2
 * rows of book cards, more after the user clicks Load more. The
 * right column's height tracks the left automatically because both
 * are children of the same grid row (default align-items:stretch). */
.overview-mid-marketing-card {
    display: flex;
    flex-direction: column;
}
/* Bookmark tab is an icon-only button pinned to the right of the tab bar
   (margin-left:auto inline). Trim the icon down from Feather's default 24px
   and centre it within the tab-btn's padding box. */
.overview-mc-bookmarks-tab {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.overview-mc-bookmarks-tab svg {
    width: 17px;
    height: 17px;
}
.overview-mc-body {
    flex: 1;
    min-height: 0;
    margin-top: 1rem;
}
.overview-mid-right-col {
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
}
/* Right-column cards split available height evenly via flex:1, but
 * each is capped at 500px so a tall Marketing Campaigns panel
 * doesn't drag Events / Trending into the 600+ range. When the
 * marketing card grows past ~1000px the right column simply leaves
 * whitespace at its bottom rather than scaling its children further. */
.overview-mid-right-col .card {
    flex: 1;
    min-height: 0;
    max-height: 500px;
    overflow-y: auto;
}

/* ─── Overview > Events panel ──────────────────────────────────────
   Vertical stack of lozenges populated by /stream-events
   ?upcoming_only=true&limit=5. Three stacked rows per lozenge —
       row 1: icon (left) + recurrence badge (right)
       row 2: event name
       row 3: date window
*/
.overview-events-empty {
    padding: 1rem 0;
    color: var(--muted);
    font-size: 0.85rem;
}
/* Events panel header — title left, neutral-outline edit icon right (links to
   Settings > Events). */
.overview-events-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}
.overview-events-edit-btn {
    color: var(--muted);
    border: 1px solid var(--border);
    border-radius: 6px;
    background: transparent;
    cursor: pointer;
    text-decoration: none;
    transition: color 0.15s, border-color 0.15s;
}
.overview-events-edit-btn:hover {
    color: var(--accent);
    border-color: var(--accent);
}
/* Trending placeholder — feature not built yet. */
.overview-trending-soon {
    padding: 1rem 0;
    color: var(--muted);
    font-size: 0.85rem;
}

/* ISBN editor modal (Phase 6) — 90vw×90vh overlay hosting Views.inkletEditor.
   No left nav (the overlay covers it); the mini hero sits 10px below the modal
   top; closes only via a save option or the hero ×. */
.ie-modal-overlay {
    position: fixed;
    inset: 0;
    z-index: 100002;
    background: rgba(0, 0, 0, 0.45);
    display: flex;
    align-items: center;
    justify-content: center;
}
/* While the 95% editor modal (z 100002) is open, lift its CHILD dialogs (book
   picker, asset library, font browser, rewriting-text, Export Options — opened
   from inside the editor) above it. Scoped to body.ie-modal-open so the shared
   .preset-overlay / .modal-overlay / .series-info-save-overlay z-index is
   untouched everywhere else. The PARENT overlay the editor was launched over
   (e.g. the Book modal) is tagged .ie-behind on open so it stays UNDER the
   editor rather than being lifted with the child dialogs. */
body.ie-modal-open .preset-overlay:not(.ie-behind),
body.ie-modal-open .modal-overlay:not(.ie-behind),
body.ie-modal-open .series-info-save-overlay:not(.ie-behind) {
    z-index: 100010;
}
.ie-modal {
    width: 95vw;
    height: 95vh;
    background: var(--paper, #fff);
    border-radius: 10px;
    overflow: hidden;
    box-shadow: 0 12px 48px rgba(0, 0, 0, 0.30);
    display: flex;
    flex-direction: column;
}
.ie-modal-body {
    flex: 1;
    min-height: 0;
    overflow: auto;
    display: flex;
    flex-direction: column;
}
/* Plain header row (replaces the image hero in modal mode): black title left,
   neutral-outline controls right, 12px top/bottom padding, flush to the editor
   controls below (no other whitespace). */
.ie-modal-hero {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 16px;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}
/* Save dropdown → neutral outline (the base styling is white-on-translucent
   for the dark image hero, invisible on the light modal row). */
.ie-modal-hero .ie-save-dropdown-btn { border-color: var(--border); }
.ie-modal-hero .ie-save-main { background: #fff; color: var(--muted); }
.ie-modal-hero .ie-save-main:hover { background: var(--cream); color: var(--accent); }
.ie-modal-hero .ie-save-caret { background: #fff; color: var(--muted); border-left-color: var(--border); }
.ie-modal-hero .ie-save-caret:hover { background: var(--cream); color: var(--accent); }
/* Editor controls sit flush under the row. */
.ie-modal-body #ie-editor-body { flex: 1; min-height: 0; margin: 0; }
.ie-modal-body #ie-editor-body .widget-editor-layout { margin: 0; }
.overview-event-lozenge {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 0.75rem 0;
    border-top: 1px solid var(--border);
}
.overview-event-lozenge:first-child { border-top: none; }
.overview-event-header {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 0.75rem;
    margin-bottom: 2px;
}
.overview-event-icon {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--accent);
    position: relative;     /* anchor for the .is-loading ::after spinner */
}
.overview-event-icon svg {
    width: 100%;
    height: 100%;
    display: block;
    transition: opacity 0.15s;
}
/* Icon becomes a clickable trigger when wired to load matching books. */
.overview-event-icon[data-trigger="load-books"] {
    cursor: pointer;
    border-radius: 4px;
    padding: 4px;
    margin: -4px;            /* keep the 20×20 visual size despite padding */
    box-sizing: content-box;
    transition: background 0.1s;
}
.overview-event-icon[data-trigger="load-books"]:hover {
    background: var(--accent-light);
}
/* While the books request is in flight, dim the icon SVG and overlay a
   small spinner. Spinner uses the .btn-spinner @keyframes already in style.css. */
.overview-event-icon.is-loading svg { opacity: 0.2; }
.overview-event-icon.is-loading::after {
    content: '';
    position: absolute;
    width: 14px; height: 14px;
    border: 2px solid var(--accent);
    border-top-color: transparent;
    border-radius: 50%;
    animation: spin 0.6s linear infinite;
}
.overview-event-name {
    font-weight: 600;
    font-size: 0.95rem;
    line-height: 1.25;
    min-width: 0;
}
.overview-event-window {
    color: var(--muted);
    font-size: 0.8rem;
}
.overview-event-badge {
    flex-shrink: 0;
    font-size: 0.7rem;
}

/* ─── Book Match modal (Overview > Events calendar-icon click) ──────
   Three-step flow: pick books → pick tagline → preview + Generate.
   Sizing mirrors the multi-editor's ISBN book picker so curators get
   a consistent "picker" feel across the dashboard. Layout class lives
   here; positioning + backdrop are inline-styled in
   views-overview.js::_overviewOpenBookMatchModal because they reuse
   the lib-modal pattern (z-index 99999, fixed inset). */
.bm-modal-inner {
    display: flex;
    flex-direction: column;
    padding: 0;
}
.bm-header {
    padding: 16px 20px 12px;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}
.bm-header h2 {
    font-family: 'DM Serif Display', serif;
    margin: 0 0 4px;
    font-size: 1.25rem;
    color: var(--ink);
}
.bm-header .bm-subtitle {
    color: var(--muted);
    font-size: 0.9rem;
}
.bm-back {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--accent);
    font-size: 0.85rem;
    text-decoration: none;
    cursor: pointer;
    padding: 0;
    border: none;
    background: none;
    margin-top: 8px;
}
.bm-back:hover { text-decoration: underline; }
.bm-body {
    flex: 1;
    overflow-y: auto;
    padding: 16px 20px;
}
/* Footer is two-sided so the Back button can live bottom-LEFT and the
   step's primary action sits bottom-RIGHT. When the footer holds only
   one child (Step 1's solo [Add (n)] or Step 3's solo [Back]) the
   :has() rule keeps it right-anchored vs anchored to the start so the
   layout doesn't drift between steps. */
.bm-footer {
    padding: 12px 20px;
    border-top: 1px solid var(--border);
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}
.bm-footer:has(> :only-child:not(.btn-outline-accent)) { justify-content: flex-end; }

/* Step 1 — 2-col book grid. Each lozenge is a flex row: thumb left,
   four-line meta block right. Selected state outlines + tints with
   the Inkara accent. */
.bm-books-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 12px;
}
.bm-book-lozenge {
    position: relative;     /* anchor for the book-open icon top-right */
    display: flex;
    gap: 12px;
    padding: 10px;
    border: 1px solid var(--border);
    border-radius: 6px;
    cursor: pointer;
    background: #fff;
    transition: border-color 120ms, background 120ms;
}
.bm-book-lozenge:hover { border-color: var(--accent); }
.bm-book-lozenge.bm-selected {
    border-color: var(--accent);
    background: var(--accent-light);
}
.bm-book-lozenge img {
    width: 64px;
    height: 96px;
    object-fit: cover;
    flex-shrink: 0;
    border: 1px solid var(--border);
}
.bm-book-lozenge .bm-meta {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    font-size: 0.85rem;
}
.bm-book-lozenge .bm-title {
    font-weight: 600;
    color: var(--ink);
    line-height: 1.25;
}
.bm-book-lozenge .bm-subtitle {
    color: var(--muted);
    font-style: italic;
    font-size: 0.8rem;
    line-height: 1.3;
}
.bm-book-lozenge .bm-isbn-format,
.bm-book-lozenge .bm-pubdate {
    color: var(--muted);
    font-size: 0.78rem;
}
/* Top-right book-open icon — opens the book detail modal without
   toggling the lozenge's selection state (click handler calls
   stopPropagation). Padding around the icon keeps it well clear of
   the rounded lozenge border. */
.bm-book-lozenge .bm-book-open-icon {
    position: absolute;
    top: 8px;
    right: 8px;
    width: 28px;
    height: 28px;
    padding: 0;
    background: transparent;
    border: none;
    border-radius: 4px;
    color: var(--muted);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: color 120ms, background 120ms;
}
.bm-book-lozenge .bm-book-open-icon:hover {
    color: var(--accent);
    background: var(--accent-light);
}
/* Push the meta column slightly so the title row never collides with
   the absolutely-positioned icon on very narrow viewports. */
.bm-book-lozenge .bm-meta { padding-right: 28px; }

/* Step 2 — full-width single-select tagline lozenges. */
.bm-tagline-lozenge {
    padding: 14px 18px;
    border: 1px solid var(--border);
    border-radius: 6px;
    cursor: pointer;
    background: #fff;
    margin-bottom: 10px;
    font-size: 0.95rem;
    color: var(--ink);
    transition: border-color 120ms, background 120ms;
}
.bm-tagline-lozenge:hover { border-color: var(--accent); }
.bm-tagline-lozenge.bm-selected {
    border-color: var(--accent);
    background: var(--accent-light);
    font-weight: 600;
}
/* Custom tagline (the 5th option) — the lozenge wraps a borderless input; a
   hint + live character count sit underneath. */
.bm-custom-tagline { padding: 0; cursor: text; margin-bottom: 4px; }
.bm-custom-tagline-input {
    width: 100%;
    box-sizing: border-box;
    border: none;
    outline: none;
    background: transparent;
    padding: 14px 18px;
    border-radius: 6px;
    font-size: 0.95rem;
    color: var(--ink);
}
.bm-custom-tagline-input::placeholder { color: var(--muted); }
.bm-custom-tagline-meta {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin: 0 2px 10px;
    font-size: 0.78rem;
    color: var(--muted);
}

/* Step 3 — centred tagline above either a horizontal thumb row
   (initial state) OR the generated campaign image (post-generate);
   Generate / Regenerate button sits below. */
.bm-step3-tagline {
    text-align: center;
    font-family: 'DM Serif Display', serif;
    font-size: 1.3rem;
    margin: 4px 0 6px;
    color: var(--ink);
}
.bm-step3-thumbs {
    display: flex;
    gap: 12px;
    justify-content: center;
    margin-bottom: 24px;
}
.bm-step3-thumbs img {
    height: 120px;
    object-fit: cover;
    border: 1px solid var(--border);
}
/* Generated-campaign image replaces the thumb row once the OpenAI
   call completes. Clickable to open at full size in a new tab. */
.bm-step3-image {
    display: flex;
    justify-content: center;
    margin-bottom: 16px;
}
.bm-step3-image a { display: block; }
.bm-step3-image img {
    max-width: 100%;
    max-height: 45vh;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: #f4f4f6;
}
/* Amber stale-prompt notice — fires when the curator edited the
   prompt after this campaign was generated. Mirrors the wording of
   the Marketing tab's stale hint so curators see a consistent
   pattern. */
.bm-step3-stale {
    text-align: center;
    margin: 4px 0 16px;
    padding: 8px 14px;
    background: #fff3cd;
    color: #856404;
    border-radius: 4px;
    font-size: 0.85rem;
}
.bm-step3-actions {
    display: flex;
    justify-content: center;
}

/* ── Event Match modal polish pass (Step 2 refresh + Step 3 label /
   format line / greyscale placeholder / selected-thumb row) ─────── */

/* Step 2 — small icon-only refresh-cw button under the tagline
   lozenges. Circular outline, muted by default, accent on hover. */
.bm-refresh-taglines-row { text-align: center; margin-top: 12px; }
.bm-refresh-taglines {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border: 1px solid var(--border);
    border-radius: 50%;
    background: transparent;
    color: var(--muted);
    cursor: pointer;
    padding: 0;
    transition: color 120ms, border-color 120ms;
}
.bm-refresh-taglines:hover {
    color: var(--accent);
    border-color: var(--accent);
}
.bm-refresh-taglines:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* Step 3 — small all-caps "MARKETING IDEA" label above the tagline.
   Matches the attached design (Inkara accent-pink, bold, letter-
   spaced). The format line under it is the muted format descriptor. */
.bm-step3-section-label {
    text-align: center;
    color: #b73b73;
    font-weight: 800;
    font-size: 0.95rem;
    letter-spacing: 1px;
    text-transform: uppercase;
    margin: 8px 0 4px;
}
.bm-step3-format-line {
    text-align: center;
    color: var(--muted);
    font-size: 0.82rem;
    margin-bottom: 16px;
}

/* Step 3 — greyscale sample-asset placeholder shown when no campaign
   has been generated for this selection but the prompt has been used
   on a different event/book combination. Visual language matches the
   Marketing tab's `.marketing-prompt-card-thumb.placeholder` pattern. */
.bm-step3-placeholder {
    display: flex;
    justify-content: center;
    margin-bottom: 12px;
}
/* Anchor wrapper — `position:relative` makes the eye icon + Sample label
   anchor against the image (not the row). `inline-block` so the wrapper
   sizes exactly to the image's natural width (since we set
   `width:auto` on the img to preserve aspect ratio). */
.bm-step3-placeholder a {
    display: inline-block;
    position: relative;
    border-radius: 4px;
    overflow: hidden;
    transition: transform 160ms ease, box-shadow 160ms ease;
}
.bm-step3-placeholder img {
    max-width: 100%;
    /* Smaller than the full Step-3 image (which uses max-height: 40vh)
       so the placeholder reads as "preview / not your asset yet" — the
       monochrome treatment + the smaller height combine to de-emphasise. */
    height: 225px;
    width: auto;
    border: 1px solid var(--border);
    border-radius: 4px;
    filter: grayscale(100%);
    opacity: 0.5;
    display: block;
    transition: filter 160ms ease, opacity 160ms ease;
}
/* "SAMPLE" watermark — centred over the image (both axes). Light,
   semi-transparent type with wide letter-spacing reads as a watermark
   rather than a UI control. Text-shadow gives readability on any
   underlying cover without needing a backdrop band. */
.bm-step3-placeholder-label {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    color: rgba(255, 255, 255, 0.82);
    font-size: 1.5rem;
    font-weight: 400;
    letter-spacing: 0.25em;
    text-transform: uppercase;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.65);
    pointer-events: none;
    transition: color 160ms ease;
    white-space: nowrap;
}
/* Rollover — stays monochrome (no colour bleed). Lift opacity, raise
   the tile, brighten the watermark. */
.bm-step3-placeholder a:hover img {
    opacity: 0.85;
}
.bm-step3-placeholder a:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);
}
.bm-step3-placeholder a:hover .bm-step3-placeholder-label {
    color: rgba(255, 255, 255, 1);
}

/* Step 3 — small selected-book thumb row. Only visible when no image
   has been generated yet for the current selection. */
.bm-step3-selected-thumbs {
    display: flex;
    gap: 10px;
    justify-content: center;
    margin-bottom: 20px;
    flex-wrap: wrap;
}
.bm-step3-selected-thumbs img {
    height: 90px;
    width: 60px;
    object-fit: cover;
    border: 1px solid var(--border);
    border-radius: 2px;
}

/* ── Multi-Book asset modal (migrations 071+072) ────────────────────
   openMultiBookAssetModal in views-books.js.
   - .bm-step-subtitle — generic muted subtitle used on Step 1 + 2.
   - .bm-book-lozenge-grid + .bm-book-lozenge — Step 1's book picker
     (current book pre-selected; click toggles; range enforced).
   - .bm-empty — Step 1 / Step 2 empty-state filler.
   - .mba-customtext-row — Step 2 input rows (one per
     {customTextLabel-N:::…} parsed from the prompt body).
   - .mba-step3-ct-echo — Step 3 stack of "Label: value" rows showing
     what the curator typed in Step 2.
*/
.bm-step-subtitle {
    text-align: center;
    color: var(--muted);
    font-size: 0.95rem;
    margin: 0 0 14px;
}
.bm-empty {
    text-align: center;
    color: var(--muted);
    font-size: 0.9rem;
    padding: 24px;
    background: #fafafa;
    border-radius: 6px;
}
.bm-book-lozenge-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 8px;
    margin: 0 0 18px;
}
.bm-book-lozenge {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 6px;
    cursor: pointer;
    font-size: 0.9rem;
    text-align: left;
    transition: border-color 0.12s, background 0.12s;
}
.bm-book-lozenge:hover { border-color: var(--accent); }
.bm-book-lozenge.selected {
    border-color: var(--accent);
    background: rgba(67, 56, 202, 0.06);
}
.bm-book-lozenge img {
    width: 30px;
    height: 45px;
    object-fit: cover;
    border-radius: 2px;
    flex-shrink: 0;
}
.bm-book-lozenge-title {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.mba-customtext-row { margin-bottom: 16px; }
.mba-customtext-row label {
    display: block;
    font-size: 0.85rem;
    color: var(--muted);
    margin-bottom: 6px;
}
.mba-customtext-row input,
.mba-customtext-row textarea {
    width: 100%;
    padding: 10px 12px;
    border: 1px solid var(--border);
    border-radius: 6px;
    font-size: 0.95rem;
    box-sizing: border-box;
    font-family: inherit;
}
.mba-customtext-row textarea {
    /* 3-line height; let users resize taller if they want more room. */
    min-height: calc(1.45em * 3 + 22px);
    resize: vertical;
    line-height: 1.45;
}
.mba-customtext-row input:focus,
.mba-customtext-row textarea:focus { outline: none; border-color: var(--accent); }
/* All-caps per-slot toggle under the textarea. Small inline checkbox +
   label. When the row gets `.mba-caps`, both chips and the textarea
   render uppercased via `text-transform`. The underlying value stays
   canonical — the transform applies to display + (at serialization
   time) the payload sent downstream. */
.mba-caps-toggle-label {
    display: inline-flex !important;
    align-items: center;
    gap: 6px;
    margin: 8px 0 0 !important;
    font-size: 0.8rem;
    color: var(--muted);
    cursor: pointer;
    user-select: none;
}
.mba-caps-toggle-label input[type="checkbox"] {
    width: auto;
    margin: 0;
    cursor: pointer;
}
.mba-customtext-row.mba-caps textarea,
.mba-customtext-row.mba-caps .mba-preset-chip,
.mba-customtext-row.mba-caps .mba-preset-generate {
    text-transform: uppercase;
}
/* Per-slot preset chips above each input. Curator-managed list of pre-coded
   one-line strings (stored at
   `ai_prompts.parameters.custom_text_presets[slotKey]`). Clicking a chip
   fills the input below; typing into the input clears the highlight. The
   row scrolls horizontally if the curator added more chips than fit. */
/* Amazon A+ 3-panel form — per-panel "Review / Marketing copy" segmented toggle. */
.aap-mode {
    font-size: 0.72rem;
    padding: 3px 11px;
    border: 1px solid var(--border);
    background: #fff;
    color: var(--muted);
    border-radius: 14px;
    cursor: pointer;
    font-weight: 500;
}
.aap-mode.active {
    background: var(--accent);
    color: #fff;
    border-color: var(--accent);
}

/* Amazon A+ composed form — tabbed steps (2026-07-09). One numbered tab per
   step (ISBN + one per panel); tabs + description + textarea are pinned in
   the modal frame and only .aap-pane-scroll (chips / book picker) scrolls. */
.aap-tabs {
    display: flex;
    flex-wrap: wrap;          /* 5-panel template = 8 tabs; wrap rather than overflow */
    gap: 0;
    padding: 0 20px;
    border-bottom: 2px solid var(--border);
    flex-shrink: 0;
}
.aap-tab-btn {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 0.55rem 0.9rem;
    border: none;
    background: none;
    cursor: pointer;
    font-family: 'Inter', sans-serif;
    font-size: 0.85rem;
    font-weight: 500;
    color: var(--muted);
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;
    transition: color 0.15s, border-color 0.15s;
    white-space: nowrap;
}
.aap-tab-btn:hover { color: var(--ink); }
.aap-tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); }
.aap-step-circle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--accent);
    color: #fff;
    font-size: 0.78rem;
    font-weight: 600;
    flex-shrink: 0;
}
/* Overrides .bm-body's own overflow-y:auto for THIS modal only: the body is
   a fixed flex frame; the active pane owns the scroll region. */
.aap-body {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.aap-pane { display: none; }
.aap-pane.active {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}
.aap-tab-desc {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--muted);
    font-size: 0.85rem;
    margin: 2px 0 8px;
    flex-shrink: 0;
}
.aap-pane-scroll {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}
/* Panel-pane 50/50 split: textarea + chips on the left (chips scroll under
   the pinned textarea), live preview on the right, generous gutter. Same
   layout for landscape, square and portrait formats. */
.aap-cols {
    display: flex;
    gap: 48px;
    flex: 1;
    min-height: 0;
}
.aap-col-left {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
}
.aap-col-left .mba-ct-input {
    width: 100%;
    flex-shrink: 0;
    margin-bottom: 6px;
}
.aap-col-right {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    /* Own scroll: the background pool grows every time the curator generates
       another trio, and with the interior strip below it the column routinely
       outgrows the pane. min-height:0 is what actually lets a flex child
       shrink below its content and scroll. */
    min-height: 0;
    overflow-y: auto;
    padding-right: 4px;   /* keeps the scrollbar off the thumbs */
}
/* The preview itself stays put while the strips beneath it scroll. */
.aap-col-right > .aap-panel-preview { flex-shrink: 0; }
/* The Preview step is a single scrolling stack (.aap-pane-scroll) — leave it. */
.aap-final-cols .aap-col-right { overflow: visible; padding-right: 0; }
.aap-preview-hint {
    color: var(--muted);
    font-size: 0.78rem;
    margin-top: 6px;
}
/* Per-slot All-caps toggle (2026-08-02) — mirrors .mba-caps: display-only
   transform on the textarea + live preview text; the dispatched
   custom_texts get the literal uppercase at serialization time. The
   checkbox shares the step-description line but is pinned so its right
   edge aligns with the TEXTAREA column, not the full-width row — the
   panes are a fixed 50/50 split with a 48px gap, so the left column ends
   24px left of centre. */
.aap-tab-desc { position: relative; }
.aap-tab-desc .aap-desc-toggles {
    position: absolute; top: 0; right: calc(50% + 24px);
    display: inline-flex; align-items: center; gap: 16px;
}
.aap-tab-desc .aap-desc-toggles .mba-caps-toggle-label { margin: 0 !important; }
.aap-pane.aap-caps .mba-ct-input,
.aap-pane.aap-caps .aap-panel-preview-text {
    text-transform: uppercase;
}
/* Final Preview step (assembly flow) — two columns: bold "Preview" label +
   disclaimer on the left, the three rough-layout panels stacked at 560px on
   the right (keeps all three on screen without scrolling). The right column
   is content-sized rather than half the row, so the panels hold 560px.
   Per-slot All-caps carries over via the wrapper class. */
.aap-preview-label { font-weight: 600; }
.aap-final-cols .aap-col-right { flex: 0 1 560px; max-width: 560px; }
.aap-final-cols .aap-preview-hint { margin-top: 0; }
.aap-final-stack {
    display: flex; flex-direction: column; gap: 12px;
}
/* One row per parameters.composite_rows entry. A multi-panel row lays its
   panels out side by side, each flex-weighted by its own width so a row of
   three 600px squares fills the same span as the 1940px banner above it. */
.aap-final-row { display: flex; gap: 12px; align-items: flex-start; }
.aap-final-row--multi > .aap-final-slot { min-width: 0; }
.aap-final-slot { flex: 1 1 0; }
.aap-final-slot.aap-caps .aap-panel-preview-text { text-transform: uppercase; }
/* Per-slot background history strip (2026-08-02) — the title's other
   generated backgrounds for this slot, newest first; click = make active
   (title-wide). The in-use thumb gets the accent outline. */
/* Chip tabs (2026-08-02) — assembly panels are interchangeable, so each
   offers the same chip kinds one tab at a time: Title & subtitle, Book Cover
   (the "Show 3D cover" box), Tagline, Reviews, Awards, Custom (empty — type
   your own). Same look as the step tabs above, minus the numbered circle.
   Kinds the book has nothing for are dropped, so a tab always has content. */
.aap-chip-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 0;
    border-bottom: 2px solid var(--border);
    margin-bottom: 12px;
}
.aap-chip-tab {
    padding: 0.4rem 0.75rem;
    border: none;
    background: none;
    cursor: pointer;
    font-family: 'Inter', sans-serif;
    font-size: 0.85rem;
    font-weight: 500;
    color: var(--muted);
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;
    transition: color 0.15s, border-color 0.15s;
    white-space: nowrap;
}
/* "Previous" — a step-tab that isn't a step: pushed flush right, no circle,
   greyed + unclickable until the prompt has produced something for the book. */
.aap-tab-prev { margin-left: auto; }
.aap-tab-prev:disabled { opacity: 0.45; cursor: default; }
.aap-tab-prev:disabled:hover { color: var(--muted); }
/* Previously-generated cards — newest first, left to right. The whole card
   is the link; it opens the full asset in a new tab. */
.aap-prev-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
    gap: 16px;
    padding-bottom: 8px;
}
.aap-prev-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 12px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--paper);
    text-decoration: none;
    transition: border-color 0.15s, box-shadow 0.15s;
}
.aap-prev-card:hover { border-color: var(--accent); box-shadow: 0 2px 8px rgba(26,26,46,0.08); }
.aap-prev-thumb {
    display: block;
    width: 100%;
    border-radius: 6px;
    overflow: hidden;
    background: #fff;
    border: 1px solid var(--border);
}
.aap-prev-thumb img { display: block; width: 100%; height: auto; }
.aap-prev-format { font-size: 0.85rem; font-weight: 600; color: var(--ink); }
.aap-prev-when { font-size: 0.78rem; color: var(--muted); }
.aap-prev-user {
    font-size: 0.75rem;
    font-weight: 600;
    padding: 2px 10px;
    border-radius: 999px;
    background: var(--accent-light);
    color: var(--accent-dark);
}
.aap-chip-tab:hover { color: var(--ink); }
.aap-chip-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.aap-chip-pane { display: none; }
.aap-chip-pane.active { display: block; }
.aap-cover-toggle { display: inline-flex; margin: 0 !important; }
.aap-panel-history { margin-top: 10px; }
.aap-panel-history-label {
    font-size: 0.72rem; font-weight: 600; color: var(--muted);
    text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 5px;
}
/* Backgrounds | Interior images side by side. Equal flex so the split is
   predictable whatever the pool sizes; min-width:0 lets each strip wrap
   instead of forcing the row wider. A panel with no interiors renders one
   column and it takes the full width. */
.aap-panel-history-cols {
    display: flex;
    gap: 28px;              /* the gap alone separates them — no divider rule */
    align-items: flex-start;
}
.aap-panel-history-col { flex: 1 1 0; min-width: 0; }
.aap-panel-history-strip {
    display: flex; flex-wrap: wrap; gap: 6px;
}
.aap-hist-thumb {
    display: block; width: 132px; padding: 0; border: 2px solid transparent;
    border-radius: 5px; background: none; cursor: pointer;
    transition: border-color 0.15s;
}
.aap-hist-thumb img {
    display: block; width: 100%; height: auto; aspect-ratio: 1940 / 600;
    object-fit: cover; border: 1px solid var(--border); border-radius: 3px;
    background: #f4f4f6;
}
/* Square (300x300) backgrounds — the base rule is the wide banner's aspect.
   Narrower than the wide thumbs so a 10-panel pool still fits one strip row. */
.aap-hist-thumb--sq { width: 64px; }
.aap-hist-thumb--sq img { aspect-ratio: 1 / 1; }
/* A slot locked to an interior crop: its text inputs are inert, so grey them
   out rather than letting the curator type into something that is ignored. */
.aap-input-locked {
    background: #f4f4f6;
    color: var(--muted);
    cursor: not-allowed;
}
.aap-tools-locked { opacity: 0.45; pointer-events: none; }
.aap-hist-thumb:hover { border-color: var(--muted); }
.aap-hist-thumb.active { border-color: var(--accent); cursor: default; }
.aap-hist-thumb:disabled { opacity: 0.5; cursor: wait; }
/* Missing-assets "Generate now": translucent overlay over the whole form
   card while the Branding jobs run (quill + copy + Minimise button). */
.aap-genwait {
    position: absolute;
    inset: 0;
    z-index: 5;
    background: rgba(255, 255, 255, 0.96);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    text-align: center;
    padding: 24px;
}
/* Minimised A+ prerequisite generation: one pill per ISBN, docked to the
   right edge. Sits ABOVE every modal overlay (form 99999, quill dialogs
   100001) so the generating / ready state stays visible wherever the curator
   navigates — including into the Book modal to watch the Branding tab.
   The stack owns the positioning so two books generating at once don't sit
   on top of each other; see aplus-prereq.js. */
.aap-dock-stack {
    position: fixed;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    z-index: 100002;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 8px;
}
/* --aap-bg / --aap-ink are set per badge by aplus-prereq.js from the Inkara
   palette, so concurrent badges are visually distinct. The accent remains the
   fallback for a palette that failed to load. --aap-ink is NOT decoration: the
   swatches are pale by design (migration 189) and white text on them fails
   contrast, so each carries a paired AA-measured dark ink. */
.aap-dock {
    display: flex;
    align-items: flex-start;
    background: var(--aap-bg, var(--accent));
    color: var(--aap-ink, #fff);
    border-radius: 8px 0 0 8px;
    max-width: 210px;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25);
}
/* brightness() rather than a second colour var: the darken has to work for any
   palette hue, and filter: brightness is already the idiom in this sheet.
   color-mix() is deliberately absent — see the note above .rc-previewing-original. */
.aap-dock:hover { filter: brightness(0.93); }
/* The clickable body and the dismiss × are siblings — a <button> nested
   inside a <button> is invalid and gets hoisted out by the browser. */
.aap-dock-body {
    flex: 1;
    background: none;
    border: none;
    color: inherit;
    padding: 14px 6px 14px 14px;
    font-family: 'Inter', sans-serif;
    font-size: 0.8rem;
    font-weight: 600;
    line-height: 1.35;
    text-align: left;
    cursor: pointer;
}
.aap-dock-title {
    display: block;
    font-weight: 700;
    /* 0.95 not 0.85: the ink is measured at ~5:1, and 0.85 dropped the title
       under AA for 0.8rem text. The × below keeps 0.7 — it's a glyph, not
       body text. */
    opacity: 0.95;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.aap-dock-label { display: block; }
.aap-dock-x {
    background: none;
    border: none;
    color: inherit;
    padding: 10px 10px 10px 4px;
    font-size: 1.05rem;
    line-height: 1;
    opacity: 0.7;
    cursor: pointer;
}
.aap-dock-x:hover { opacity: 1; }
.aap-dock--ready { animation: aapDockPulse 1.6s ease-in-out infinite; }
@keyframes aapDockPulse {
    0%, 100% { box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25); transform: scale(1); }
    /* var() inside a keyframe resolves against the animating element, so the
       per-badge glow set inline applies here. */
    50%      { box-shadow: 0 2px 18px var(--aap-glow, rgba(175, 40, 96, 0.6)); transform: scale(1.05); }
}
/* Failure keeps its own red — a pale Sage pill reading "Generation failed"
   would be a semantics regression. color is restated because the per-badge
   --aap-ink is a PALE-swatch ink and would be unreadable on this dark red.
   Equal specificity to .aap-dock and later in the file, so it wins without
   !important — don't reorder these two rules. */
.aap-dock--failed { background: #a12734; color: #fff; animation: none; }
.aap-panel-preview {
    position: relative;
    width: 100%;
    max-height: 100%;
    background-size: cover;
    background-position: center;
    border: 1px solid var(--border);
    border-radius: 6px;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    flex-shrink: 0;
}
.aap-panel-preview[hidden] { display: none; }
/* Middle panel with the 3D cover showing on its right half: centre the
   text within the LEFT half (matches the real render's layout). */
.aap-panel-preview--cover {
    justify-content: flex-start;
}
.aap-panel-preview--cover .aap-panel-preview-text {
    width: 50%;
}
/* Middle panel only: the 3D cover the real render composites on the right. */
.aap-panel-preview-cover {
    position: absolute;
    right: 3%;
    top: 50%;
    transform: translateY(-50%);
    height: 84%;
    width: auto;
    pointer-events: none;
}
.aap-panel-preview-cover[hidden] { display: none; }
/* Cover-only panel (3D render ticked, no text, no interior pick). That slot
   never reaches a model: compositeCoverCentred scales the real PNG to 80% of
   the panel height, caps its width at the panel minus an 8% margin each side,
   and centres it BOTH ways. Mirror that instead of pinning it right.
   `right:auto` is required — the base rule sets right:3% with no reset — and
   `object-fit:contain` is required so the cover shrinks proportionally rather
   than distorting once max-width binds. Applies to wide banners too: the
   renderer centres any blank cover-ticked slot, whatever its shape. */
.aap-panel-preview--cover-only .aap-panel-preview-cover {
    right: auto;
    left: 50%;
    transform: translate(-50%, -50%);
    height: 80%;
    max-width: 84%;
    object-fit: contain;
}
.aap-edit-zone--side .aap-panel-preview {
    width: 200px;
}
/* Square step previews. The base rule is width:100% + max-height:100%, which
   for a 1:1 box means the row height crops it into a landscape sliver — the
   wide banners never hit the cap because they're only ~0.31 of their width
   tall. Cap the WIDTH against the viewport instead (the modal is a fixed
   fraction of it) so the square stays square and leaves room for the hint and
   the history strip beneath it. Step panes only: the Preview step's copies sit
   in auto-height wrappers, where max-height:100% resolves to none. */
.aap-col-right > .aap-panel-preview--sq {
    max-width: min(100%, 34vh);
    margin: 0 auto;
}
.aap-panel-preview-text {
    text-align: center;
    color: #fff;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6), 0 0 12px rgba(0, 0, 0, 0.35);
    padding: 4% 6%;
    max-height: 100%;
    overflow: hidden;
    line-height: 1.3;
    word-break: break-word;
}

.mba-preset-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    /* Chips sit BELOW the textarea now (text-field → chips → caps toggle),
       so the margin shape is top-only — separates chips from the input
       above and lets the caps-toggle's own top margin handle the gap
       below. */
    margin: 8px 0 0;
    /* No horizontal scroll — wrap is more readable when the chip count is
       modest. If a future prompt has 20+ chips per slot we can revisit. */
}
.mba-preset-chip {
    background: #f4f4f6;
    border: 1px solid var(--border);
    /* Was `border-radius: 999px` (pill). Long, wrapped chips need
       softer-but-still-square corners — pure pills look strange when
       the chip spans 2-3 lines. */
    border-radius: 8px;
    padding: 6px 12px;
    font-size: 0.8rem;
    font-family: inherit;
    color: var(--ink);
    cursor: pointer;
    /* Allow long preset strings (e.g. resolved `{review1-20}` quotes) to
       wrap inside the chip instead of overflowing the row. */
    white-space: normal;
    text-align: left;
    /* Keep individual chips from growing wider than the row's content
       column on wide modals — gives the chip row a "tile" feel rather
       than one giant chip spanning the modal. */
    max-width: 100%;
    line-height: 1.35;
    transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
}
.mba-preset-chip:hover { background: #ebebef; border-color: #bbb; }
.mba-preset-chip.active {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
    font-weight: 600;
}
/* AI-generate sibling of the preset chip: same dimensions, outline-accent
   treatment so it visually invites a click but reads as "different action"
   from the value-fill chips next to it. Each click hits
   /books/{id}/generate-tagline with the slot's configured prompt_code
   and replaces the input value with the model's suggestion. */
.mba-preset-generate {
    background: transparent;
    border: 1px solid var(--accent);
    color: var(--accent);
    border-radius: 999px;
    padding: 4px 12px;
    font-size: 0.8rem;
    font-family: inherit;
    font-weight: 600;
    line-height: 1.3;
    cursor: pointer;
    white-space: nowrap;
    transition: background 120ms ease, color 120ms ease;
}
.mba-preset-generate:hover { background: var(--accent); color: #fff; }
.mba-preset-generate:disabled,
.mba-preset-generate.mba-preset-generate-loading {
    opacity: 0.65;
    cursor: progress;
}
.mba-step3-ct-echo {
    margin: 12px 0 16px;
    padding: 12px 14px;
    background: #fafafa;
    border-radius: 6px;
    font-size: 0.9rem;
}
.mba-step3-ct-row { padding: 2px 0; }
.mba-step3-ct-label {
    color: var(--muted);
    font-weight: 500;
}
.mba-step3-ct-value { color: var(--text); }

/* Scaled-down (~75%) variant of .campaign-grid for the Overview's
 * Marketing Campaigns panel. Reuses the .campaign-book-card markup
 * unchanged — the descendant selectors below trim paddings + font
 * sizes so the cards display ~3/4 the size of the Campaigns page.
 * Grid keeps 5 columns; the narrower panel + scaled-down internals
 * compound to deliver the visual 75%. */
/* Grey per-tab subtitle under the Marketing-Campaigns tab bar — reuses
   .section-title-pair .section-subtitle for the grey colour, but centred
   and sized to match the .tab-btn font (0.85rem) above it. */
.overview-mc-subtitle-wrap { margin: 4px 0 14px; justify-content: center; }
/* id selector (1,0,0) so it beats the later, equal-specificity shared
   rule .section-title-pair .section-subtitle (0,2,0) regardless of order. */
#overview-mc-subtitle { font-size: 0.85rem; font-style: italic; }
.overview-mc-grid {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    /* 50% wider gutters than the original 0.75rem so the cards
       breathe better against each other and against the panel edge. */
    gap: 1.125rem;
}
@media (max-width: 1200px) { .overview-mc-grid { grid-template-columns: repeat(4, 1fr); } }
@media (max-width: 900px)  { .overview-mc-grid { grid-template-columns: repeat(3, 1fr); } }
.overview-mc-grid .campaign-book-card { border-radius: 8px; }
.overview-mc-grid .campaign-book-meta { padding: 9px 11px; }
.overview-mc-grid .campaign-book-format { font-size: 0.75rem; color: var(--muted); }
.overview-mc-grid .campaign-book-date   { font-size: 0.75rem; color: var(--muted); margin-top: 3px; }
.overview-mc-grid .campaign-book-view-btn { font-size: 0.7rem; padding: 4px 10px; }

/* When there's no cover image the placeholder shows the title +
 * author centred + vertically aligned in the cover box (instead of
 * the generic "No cover" string the campaigns page uses). Scoped to
 * the overview surfaces so the campaigns page's existing empty
 * state stays unchanged. */
.overview-mc-grid .campaign-book-cover-empty,
.overview-mc-stack-modal-grid .campaign-book-cover-empty {
    flex-direction: column;
    padding: 12px;
    text-align: center;
    box-sizing: border-box;
    color: #475569;
    font-size: 0.85rem;
}
.overview-mc-grid .campaign-book-cover-empty-title,
.overview-mc-stack-modal-grid .campaign-book-cover-empty-title {
    font-weight: 600;
    line-height: 1.25;
}
.overview-mc-grid .campaign-book-cover-empty-author,
.overview-mc-stack-modal-grid .campaign-book-cover-empty-author {
    margin-top: 6px;
    font-size: 0.75rem;
    color: #64748b;
    line-height: 1.25;
}

/* Card wrapper — every card is wrapped (singleton or stack) so that
 * single→stack upgrades on Load-more can be done by outerHTML-
 * replacing the wrapper in place. The multi-format indicator is now
 * a Feather `layers` icon inside the card body (see .campaign-book-
 * stack-icon below) — no outside-the-card badge any more. */
/* Stack wrapper styling applied to BOTH the panel grid AND the
 * formats modal — so cards inside the modal match the overview
 * cards 1:1 (hover behaviour, cursor, structure). */
.overview-mc-grid .campaign-book-stack,
.overview-mc-stack-modal-grid .campaign-book-stack {
    position: relative;
    cursor: pointer;
}
.overview-mc-grid .campaign-book-card,
.overview-mc-stack-modal-grid .campaign-book-card {
    transition: filter 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}
/* Hover darken — sits on top of the existing .campaign-book-card
 * shadow + translate-Y effects (defined further down). brightness()
 * dims every pixel uniformly so the cover, the format / layers icon,
 * and the inline date all darken together. */
.overview-mc-grid .campaign-book-stack:hover .campaign-book-card,
.overview-mc-stack-modal-grid .campaign-book-stack:hover .campaign-book-card {
    filter: brightness(0.93);
}

/* Format row: holds either the format label OR (for multi-format
 * stack cards) a left-aligned Feather `layers` icon in its place.
 * Single column, left-aligned — applied to both the panel grid and
 * the formats modal grid (modal cards always show the format text). */
.overview-mc-grid .campaign-book-format-row,
.overview-mc-stack-modal-grid .campaign-book-format-row {
    display: flex;
    align-items: center;
    gap: 6px;
}
.overview-mc-grid .campaign-book-stack-icon,
.overview-mc-stack-modal-grid .campaign-book-stack-icon {
    width: 14px;
    height: 14px;
    color: var(--muted);
    flex-shrink: 0;
}
.overview-mc-grid .campaign-book-stack-icon svg,
.overview-mc-stack-modal-grid .campaign-book-stack-icon svg {
    width: 14px;
    height: 14px;
    stroke: var(--muted);
}

/* Stack modal — lists every edition in the clicked group as full-
 * size .campaign-book-card (no .overview-mc-grid descendant scale-
 * downs apply here). 3-col grid sized to fit ~5 editions max
 * comfortably. */
.overview-mc-stack-modal {
    position: fixed; inset: 0; z-index: 99999;
    background: rgba(26,26,46,0.45);
    backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
    display: flex; align-items: center; justify-content: center;
    animation: fadeIn 0.2s ease;
}
.overview-mc-stack-modal-inner {
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    width: 760px; max-width: 92vw;
    max-height: 88vh;
    padding: 0;
    display: flex; flex-direction: column;
    box-shadow: 0 8px 32px rgba(0,0,0,0.25);
    overflow: hidden;
}
.overview-mc-stack-modal-shell { display: flex; flex-direction: column; height: 100%; }
.overview-mc-stack-modal-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 1rem;
    padding: 1.25rem 1.5rem 1rem;
    border-bottom: 1px solid var(--border);
}
.overview-mc-stack-modal-title {
    font-family: 'DM Serif Display', serif;
    font-size: 1.1rem;
    letter-spacing: -0.3px;
    color: var(--ink);
}
.overview-mc-stack-modal-sub {
    font-size: 0.8rem;
    color: var(--muted);
    margin-top: 4px;
}
.overview-mc-stack-modal-close {
    background: none; border: none;
    font-size: 1.5rem; line-height: 1;
    cursor: pointer; color: var(--muted);
    padding: 0 0.25rem;
}
.overview-mc-stack-modal-close:hover { color: var(--ink); }
.overview-mc-stack-modal-grid {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 0.75rem;
    padding: 1.25rem 1.5rem 1.5rem;
    overflow-y: auto;
}
/* Mirror the .overview-mc-grid scale-downs so cards in the modal
 * look identical to the cards in the panel — same paddings + font
 * sizes, same rounded-corner radius, same per-line meta lines. */
.overview-mc-stack-modal-grid .campaign-book-card { border-radius: 8px; }
.overview-mc-stack-modal-grid .campaign-book-meta { padding: 9px 11px; }
.overview-mc-stack-modal-grid .campaign-book-format { font-size: 0.75rem; color: var(--muted); }
.overview-mc-stack-modal-grid .campaign-book-date   { font-size: 0.75rem; color: var(--muted); margin-top: 3px; }
.overview-mc-stack-modal-grid .campaign-book-view-btn { font-size: 0.7rem; padding: 4px 10px; }

/* --- Tables --- */
.table-wrap { overflow: visible; }
table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
th { text-align: left; padding: 0.75rem; border-bottom: 2px solid var(--border); color: var(--muted); font-weight: 600; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.5px; white-space: nowrap; }
td { padding: 0.75rem; border-bottom: 1px solid var(--border); }
tr:hover td { background: #f8f6f2; }

/* --- Badges --- */
.badge {
    display: inline-block;
    padding: 0.2rem 0.5rem;
    border-radius: 4px;
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
}
.badge-active { background: #d4edda; color: #155724; }
.badge-inactive { background: #f8d7da; color: #721c24; }
.badge-pending { background: #fff3cd; color: #856404; }
.badge-hidden { background: #fff3cd; color: #856404; }
.badge-primary { background: var(--accent-light); color: var(--accent); }
.badge-unmatched { background: #ffe0b2; color: #e65100; }
.badge-failed { background: #f8d7da; color: #721c24; }
.badge-succeeded { background: #d4edda; color: #155724; }
.badge-processing { background: #d1ecf1; color: #0c5460; }

/* Feedback status badges */
.badge-new { background: #dbeafe; color: #1d4ed8; }
.badge-open { background: #fef3c7; color: #92400e; }
.badge-awaiting-user { background: #fef3c7; color: #92400e; }
.badge-awaiting-inkara { background: #ede9fe; color: #5b21b6; }
.badge-closed { background: #d1fae5; color: #065f46; }

/* Feedback table + modal */
.feedback-table tr { cursor: pointer; }
.feedback-clip { vertical-align: middle; }
th[data-sort-key] { cursor: pointer; user-select: none; }
th[data-sort-key]:hover { color: var(--accent); }
th .sort-arrow { font-size: 0.65rem; margin-left: 4px; }
/* Higher specificity than the base `.modal` rule (which is declared LATER in
   this file with max-height:80vh) so the feedback modal's taller cap wins. */
.modal.modal-feedback { max-width: 900px; max-height: 95vh; overflow-y: auto; }
.feedback-detail-grid {
    display: grid; grid-template-columns: auto 1fr; gap: 4px 12px;
    font-size: 0.85rem; margin-bottom: 16px;
}
.feedback-detail-label { color: var(--muted); font-weight: 500; }
.feedback-detail-comment {
    background: var(--cream, #faf9f5); border: 1px solid var(--border);
    border-radius: 6px; padding: 12px; margin: 0 0 16px;
    white-space: pre-wrap; font-size: 0.85rem; line-height: 1.5;
}
.feedback-detail-screenshot img {
    width: 100%; border-radius: 6px; border: 1px solid var(--border); margin-top: 8px;
    cursor: pointer; transition: opacity 0.15s;
}
.feedback-detail-screenshot img:hover { opacity: 0.85; }

/* Feedback thread — message bubbles inside the detail modal */
.fb-thread {
    max-height: 360px;
    overflow-y: auto;
    margin-bottom: 16px;
    padding: 8px 0;
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
}
.fb-msg {
    margin-bottom: 12px;
    padding: 10px 12px;
    border-radius: 8px;
    font-size: 0.85rem;
    line-height: 1.5;
}
.fb-msg-user {
    background: var(--cream, #faf9f5);
    border: 1px solid var(--border);
    margin-right: 24px;
}
.fb-msg-admin {
    background: #eef2ff;
    border: 1px solid #c7d2fe;
    margin-left: 24px;
}
.fb-msg-header {
    font-size: 0.75rem;
    color: var(--muted);
    margin-bottom: 4px;
}
.fb-msg-email { margin-left: 4px; }
.fb-msg-time { margin-left: 8px; }
.fb-msg-body { white-space: pre-wrap; word-break: break-word; }
.fb-msg-screenshot img {
    max-width: 100%;
    border-radius: 4px;
    border: 1px solid var(--border);
    margin-top: 6px;
    cursor: pointer;
    max-height: 200px;
    object-fit: cover;
}
.fb-msg-screenshot img:hover { opacity: 0.85; }

/* Reply form */
.fb-reply-form {
    margin-top: 8px;
}
.fb-reply-textarea {
    width: 100%;
    box-sizing: border-box;
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 10px;
    font-size: 0.85rem;
    font-family: 'Inter', sans-serif;
    resize: vertical;
    min-height: 80px;
    background: var(--paper, #fff);
    color: var(--ink);
}
.fb-reply-textarea:focus { outline: none; border-color: var(--accent); }

/* Feedback screenshot lightbox — full-screen overlay with a dark top bar */
.feedback-lightbox {
    position: fixed; inset: 0; z-index: 100000;
    background: rgba(0,0,0,0.92);
    display: flex; flex-direction: column;
    cursor: pointer;
}
.feedback-lightbox-bar {
    flex-shrink: 0; height: 40px;
    background: rgba(0,0,0,0.6);
    display: flex; align-items: center; justify-content: flex-end;
    padding: 0 12px;
}
.feedback-lightbox-close {
    background: none; border: none; color: #fff; cursor: pointer;
    font-size: 1.2rem; line-height: 1; padding: 4px 8px; opacity: 0.8;
    transition: opacity 0.15s;
}
.feedback-lightbox-close:hover { opacity: 1; }
.feedback-lightbox-body {
    flex: 1; display: flex; align-items: center; justify-content: center;
    padding: 16px; overflow: auto;
}
.feedback-lightbox-body img {
    max-width: 100%; max-height: 100%; object-fit: contain;
    border-radius: 4px; pointer-events: none;
}
.badge-received { background: #e2e3e5; color: #383d41; }
.badge-format { background: #e8e6e1; color: #383d41; }
.badge-full { background: #d1ecf1; color: #0c5460; }
.badge-partial { background: #fff3cd; color: #856404; }
/* Role badge — User. Inkara lilac, distinct from the yellow Pending status. */
.badge-role-user { background: #ece9f7; color: #5b3a8e; }

/* --- Page header --- */
.page-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1.5rem;
}
.page-header h2 {
    font-family: 'DM Serif Display', serif;
    font-size: 1.5rem;
    letter-spacing: -0.3px;
}

/* --- Page Hero Header --- */
.page-hero {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 2rem 2.5rem;
    margin-bottom: 1.5rem;
    border-radius: 14px;
    min-height: 120px;
    background-size: cover;
    background-position: center;
    position: relative;
}
.page-hero.has-dropdown { overflow: visible; }
.page-hero:not(.has-dropdown) { overflow: hidden; }
.page-hero::before {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(135deg, rgba(0,0,0,0.45), rgba(0,0,0,0.25));
    border-radius: 14px;
}
.page-hero > * { position: relative; z-index: 2; }
/* House style: Display (colour overridden — hero sits on a photo) */
.page-hero h2 {
    font-family: var(--font-serif);
    font-size: var(--text-display);
    letter-spacing: -0.3px;
    color: #fff;
    margin: 0;
}
.page-hero .btn {
    background: rgba(255,255,255,0.15);
    color: #fff;
    border: 1.5px solid rgba(255,255,255,0.6);
    backdrop-filter: blur(4px);
}
.page-hero .btn:hover {
    background: rgba(255,255,255,0.25);
    border-color: #fff;
}

/* Hero stream switcher (pill-style dropdown in hero banner) */
.hero-stream-switcher {
    display: flex; align-items: center; gap: 6px;
    background: rgba(255,255,255,0.15); border: 1px solid rgba(255,255,255,0.4);
    border-radius: 20px; padding: 6px 14px;
    color: #fff; cursor: pointer; font-size: 0.85rem; font-weight: 500;
    user-select: none; white-space: nowrap;
}
.hero-stream-switcher:hover { background: rgba(255,255,255,0.25); }
.hero-stream-dropdown {
    display: none; position: absolute; top: 100%; right: 0; margin-top: 6px;
    background: var(--card-bg); border: 1px solid var(--border); border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.15); min-width: 220px; z-index: 1000;
    max-height: 300px; overflow-y: auto;
}
.hero-stream-dropdown.open { display: block; }
.hero-stream-option {
    padding: 10px 14px; cursor: pointer; color: var(--ink); font-size: 0.85rem;
    border-bottom: 1px solid var(--border);
}
.hero-stream-option:last-child { border-bottom: none; }
.hero-stream-option:hover { background: var(--cream); }
.hero-stream-option.active { color: var(--accent); font-weight: 600; }

/* --- Compact Hero (Inklet Editor) --- */
.page-hero-sm {
    min-height: 50px !important;
    padding: 0.8rem 2rem !important;
    overflow: visible !important;
}
.page-hero-sm h2 { font-size: 1.2rem; }

/* --- Inline Name Editor --- */
.ie-editor-name {
    background: none; border: none; border-bottom: 1px solid transparent;
    color: #fff; font-family: 'DM Serif Display', serif;
    font-size: 1.1rem; outline: none; padding: 2px 4px;
    flex: 1; min-width: 150px; max-width: 500px;
}
.ie-editor-name:focus {
    border-bottom-color: rgba(255,255,255,0.6);
}
.ie-editor-name::placeholder { color: rgba(255,255,255,0.4); }

/* --- Channel Switcher --- */
.ie-channel-switcher {
    display: flex; align-items: center; gap: 8px;
    background: rgba(255,255,255,0.15); border: 1px solid rgba(255,255,255,0.4);
    border-radius: 20px; padding: 4px 12px 4px 4px;
    color: #fff; cursor: pointer; position: relative;
    font-size: 0.85rem; white-space: nowrap;
    transition: background 0.15s;
}
.ie-channel-switcher:hover { background: rgba(255,255,255,0.25); }
.ie-channel-switcher > img {
    width: 28px; height: 28px; border-radius: 50%; object-fit: cover;
}
.ie-channel-dropdown {
    display: none; position: absolute; top: 100%; left: 0;
    margin-top: 6px; background: var(--card-bg);
    border: 1px solid var(--border); border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.12); padding: 6px 0;
    min-width: 200px; z-index: 1000;
}
.ie-channel-dropdown.open { display: block; }
.ie-channel-option {
    display: flex; align-items: center; gap: 10px;
    padding: 8px 14px; cursor: pointer; color: var(--ink);
    font-size: 0.85rem; transition: background 0.1s;
}
.ie-channel-option:hover { background: var(--cream); }
.ie-channel-option.active { color: var(--accent); font-weight: 600; }
.ie-channel-option img {
    width: 24px; height: 24px; border-radius: 4px; object-fit: cover;
}

/* --- Forms --- */
.form-row { display: flex; gap: 1rem; margin-bottom: 1rem; flex-wrap: wrap; }
.form-group { display: flex; flex-direction: column; flex: 1; min-width: 200px; }
/* House style: Label */
.form-group label { font-size: var(--text-label); font-weight: 500; color: var(--muted); margin-bottom: 0.25rem; text-transform: uppercase; letter-spacing: 0.5px; }
/* User Settings account/stream pickers: per-item labels inherit the uppercase
   from `.form-group label` above — revert to standard capitalisation, and
   neutralise the bold Stream-Access account headers (were near-black). */
.modal-user .user-accounts-list label,
.modal-user #m-streams-list label,
.modal-user #ie-streams-list label,
.modal-user #ch-streams-picker label,
.modal-user #m-streams-list .ust-acct-head,
.modal-user #ie-streams-list .ust-acct-head,
.modal-user #ch-streams-picker .ust-acct-head { text-transform: none; letter-spacing: normal; }
.modal-user #m-streams-list .ust-acct-head,
.modal-user #ie-streams-list .ust-acct-head,
.modal-user #ch-streams-picker .ust-acct-head { color: var(--muted); font-size: 0.75rem; font-weight: 500; }
/* AI Prompt form Streams field reuses the shared account-grouped picker
   (renderStreamAccessCheckboxes) but isn't inside .modal-user, so style its
   account headings here. */
#ap-streams-list .ust-acct-head { color: var(--muted); font-size: 0.72rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; }
#ap-streams-list > div + div .ust-acct-head { margin-top: 0.35rem; }
/* Inkara-template stream-access checklist (Inklet Settings modal) — mirrors
   the user modal's #m-streams-list box. */
.modal-user #ie-streams-list { max-height: 200px; overflow-y: auto; border: 1px solid var(--border); border-radius: var(--radius); padding: 0.5rem; }
/* Users table: keep all action icons on one line even with 4+ icons. */
.user-actions-cell { white-space: nowrap; }
input, select, textarea {
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    font-size: 0.875rem;
    font-family: 'Inter', sans-serif;
    background: var(--paper);
    color: var(--ink);
    transition: border-color 0.15s;
}
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); }

/* --- Filter bar --- */
.filter-bar {
    display: flex;
    gap: 0.75rem;
    align-items: flex-end;
    margin-bottom: 1rem;
    flex-wrap: wrap;
}
.filter-bar .form-group { min-width: 150px; flex: 0 0 auto; }
.filter-bar input[type="text"] { width: 250px; }

/* --- Tabbed book search helper (views-book-search.js) ---
   Search bar + Recent/Released/Coming/Category tabs + BISAC dropdowns
   + Load-more. Reuses .tab-bar / .tab-btn from the global tab vocabulary;
   the .bs-* scope only adds spacing + the search-mode mute. */
.bs-host { margin-bottom: 1rem; }
.bs-bar {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    margin-bottom: 0.75rem;
}
.bs-bar .bs-input {
    flex: 1;
    padding: 0.55rem 0.85rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: #fff;
    font-size: 0.9rem;
}
.bs-bar .bs-search-btn { flex-shrink: 0; }
.bs-tab-bar.tab-bar { margin-bottom: 0.75rem; }
.bs-tab-bar.bs-tabs-search-mode .tab-btn {
    /* When a search query is live, mute the tabs so users see they're
       in search mode but can still click a tab to clear and reload. */
    color: var(--border);
}
.bs-tab-bar.bs-tabs-search-mode .tab-btn.active { border-bottom-color: transparent; }
.bs-cat-row {
    display: flex;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
    flex-wrap: wrap;
}
.bs-cat-row select {
    flex: 1;
    min-width: 220px;
    max-width: 360px;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: #fff;
    font-size: 0.85rem;
}
.bs-no-stream {
    padding: 0.85rem 1rem;
    background: var(--paper);
    border: 1px dashed var(--border);
    border-radius: var(--radius);
    color: var(--muted);
    font-size: 0.85rem;
    margin-bottom: 0.75rem;
}
.bs-load-more-wrap {
    margin: 0.75rem 0;
    text-align: center;
}

/* --- Modal --- */
.modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(26,26,46,0.45);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 99999;
    animation: fadeIn 0.2s ease;
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

/* Template bulk-create modal (widgets/template-bulk-modal.js).
   Fixed-size shell (90vw × 90vh) — top bar, controls row (channel-icon
   picker + dropdowns + refresh), and a scaled preview grid. Backdrop
   matches .modal-overlay. */
.tbm-modal {
    position: fixed; inset: 0; z-index: 99999;
    background: rgba(26,26,46,0.45);
    backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
    display: flex; align-items: center; justify-content: center;
    animation: fadeIn 0.2s ease;
}
.tbm-modal-inner {
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    width: 90vw; height: 90vh;
    padding: 0; overflow: hidden;
    display: flex; flex-direction: column;
}
.tbm-modal .modal-close-x {
    background: none; border: none; font-size: 1.5rem; cursor: pointer;
    color: var(--muted); padding: 0 4px; line-height: 1;
}
.tbm-modal .modal-close-x:hover { color: var(--ink); }

/* Expanded-state top bar: mirrors inklet-preview-shell.js style. */
.tbm-top-bar {
    background: #d4d4d4;
    padding: 10px 20px;
    display: flex; justify-content: space-between; align-items: center;
    flex-shrink: 0;
}
.tbm-top-bar .section-title-pair { display: flex; align-items: baseline; gap: 12px; }
.tbm-top-bar .section-title { font-family: 'DM Serif Display', serif; font-size: 1.2rem; color: #1a1a2e; }
.tbm-top-bar .section-subtitle { font-size: 0.9rem; color: #64748b; font-weight: 500; }

/* (Assets (AI) fullscreen modal CSS retired in Phase 5 of the
 * Marketing Assets refactor — the surface is now an inline Book modal
 * tab, so the .assets-ai-modal-inner / .assets-ai-top-row / .assets-ai-body
 * / #assets-marketing-host overrides that lived here are dead code.
 * Branding tab sub-panels use the base .book-asset-panel rules below;
 * Marketing Assets uses the .campaigns-filter-row + .campaign-asset-grid
 * styling shared with the Campaigns page.) */

.tbm-controls {
    display: flex; justify-content: center; align-items: center;
    gap: 12px; padding: 16px; flex-shrink: 0;
    border-bottom: 1px solid var(--border);
}
/* Unified 36px height across all controls (selects, channel-icon pills,
   refresh button) so the row reads as a single horizontal strip. */
.tbm-controls select {
    height: 36px;
    padding: 0 10px; border: 1px solid var(--border); border-radius: 6px;
    background: #fff; font-size: 0.9rem; cursor: pointer;
}
.tbm-refresh-btn {
    height: 36px; width: 36px;
    background: none; border: 1px solid var(--border); border-radius: 6px;
    padding: 0; cursor: pointer; color: var(--ink);
    display: inline-flex; align-items: center; justify-content: center;
    line-height: 1;
}
.tbm-refresh-btn svg { width: 16px; height: 16px; }
.tbm-refresh-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.tbm-refresh-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.tbm-refresh-btn.tbm-spinning svg { animation: spin 0.8s linear infinite; }

/* Channel-icon picker inside the controls row. Reuses .me-channel-bar
   pill styling from widget-shared.css; this just nudges spacing so it sits
   flush with the dropdowns to its right. */
.tbm-channel-bar {
    margin: 0;
    margin-right: 4px;
}

/* Stacked controls: a search-bar row on top, the tab bar beneath. Replaces
   the old single filter-dropdown + refresh row. */
.tbm-controls-stack { flex-direction: column; align-items: stretch; gap: 10px; }
.tbm-controls-row { display: flex; align-items: center; gap: 10px; }
.tbm-search-input {
    flex: 1; height: 36px; padding: 0 12px;
    border: 1px solid var(--border); border-radius: 6px;
    background: #fff; font-size: 0.9rem;
}
.tbm-controls-row .tbm-search-btn,
.tbm-controls-row .tbm-clear-btn { height: 36px; flex-shrink: 0; }
.tbm-tabs { display: flex; gap: 6px; flex-wrap: wrap; }
.tbm-tab {
    height: 34px; padding: 0 14px;
    border: 1px solid var(--border); border-radius: 6px;
    background: #fff; color: var(--ink);
    font-size: 0.9rem; cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.tbm-tab:hover { border-color: var(--accent); color: var(--accent); }
.tbm-tab.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.tbm-tab svg { width: 16px; height: 16px; }

.tbm-grid {
    flex: 1; overflow-y: auto;
    padding: 16px;
    display: flex; flex-wrap: wrap; gap: 16px;
    align-content: flex-start;
}
.tbm-load-more-wrap {
    display: flex; justify-content: center;
    padding: 12px 16px;
    flex-shrink: 0;
    border-top: 1px solid var(--border);
}
.tbm-load-more-btn {
    height: 36px;
    padding: 0 20px;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: #fff; color: var(--ink);
    font-size: 0.9rem; cursor: pointer;
    transition: border-color 0.15s ease, color 0.15s ease;
}
.tbm-load-more-btn:hover:not(:disabled) {
    border-color: var(--accent);
    color: var(--accent);
}
.tbm-load-more-btn:disabled { opacity: 0.6; cursor: not-allowed; }
.tbm-tile {
    position: relative;
    overflow: hidden;
    cursor: pointer;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 6px;
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.tbm-tile:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 16px rgba(0,0,0,0.12);
}
.tbm-tile-scaler { pointer-events: none; }

.modal {
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 2rem;
    width: 90%;
    max-width: 560px;
    max-height: 80vh;
    overflow-y: auto;
    box-shadow: 0 20px 60px rgba(0,0,0,0.18);
    animation: slideUp 0.25s ease;
}
@keyframes slideUp { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }

.modal h3 { font-family: 'DM Serif Display', serif; margin-bottom: 1.25rem; letter-spacing: -0.3px; }

/* Run detail modal — large, near-fullscreen */
.modal-run-detail {
    background: var(--paper);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    width: 90vw;
    height: 90vh;
    display: flex;
    flex-direction: column;
    box-shadow: 0 20px 60px rgba(0,0,0,0.18);
    animation: slideUp 0.25s ease;
    overflow: hidden;
}
.modal-run-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}
.modal-run-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1.25rem 1.5rem;
    border-bottom: 1px solid var(--border);
    background: var(--card-bg);
    flex-shrink: 0;
}
.modal-run-header h3 {
    font-family: 'DM Serif Display', serif;
    font-size: 1.25rem;
    letter-spacing: -0.3px;
    margin: 0;
}
.modal-run-close {
    background: none;
    border: none;
    font-size: 1.5rem;
    color: var(--muted);
    cursor: pointer;
    padding: 0 4px;
    line-height: 1;
    transition: color 0.15s;
}
.modal-run-close:hover { color: var(--ink); }
.modal-run-body {
    padding: 1.5rem;
    overflow-y: auto;
    flex: 1;
}

/* --- Book detail modal — top zone (cover thumb + title stack + 3-col grid) ---
   The modal-run-detail shell (90vw × 90vh, flex column, overflow:hidden) is
   shared with the run detail modal. Book detail uses a different inner
   layout: a non-scrolling top zone + non-scrolling tab bar + scrolling
   tab-content panel. This avoids the previous "everything scrolls" feel
   where the tab bar disappeared on long descriptions. */
.book-detail-close {
    position: absolute;
    top: 12px;
    right: 16px;
    z-index: 2;
    background: none;
    border: none;
    font-size: 1.5rem;
    color: var(--muted);
    cursor: pointer;
    padding: 0 4px;
    line-height: 1;
}
.book-detail-close:hover { color: var(--ink); }

.book-detail-top {
    flex-shrink: 0;
    padding: 1.5rem 1.5rem 1rem;
    border-bottom: 1px solid var(--border);
}
/* 4-column top panel:
   col 1 = bookmark star (auto width — narrow, ~32px),
   col 2 = cover thumbnail with stream row stacked below it,
   col 3 = title stack (status chips + title/subtitle + ISBN/Pub date/Format),
   col 4 = bibliographic stack (Authors / Publisher / Imprint / Series). */
/* Book detail header: 5-column grid.
   star (auto) | cover (auto) | col-1 (1fr) | col-2 (1fr) | col-3 (1fr)
   - The three 1fr columns are equal-width by design so the content
     redistributes evenly across the header strip.
   - Stream now lives in col-3 (was previously stacked under the cover
     thumb in the col-cover column). */
/* Header layout: [star] [cover] [text-left 2/3] [text-right 1/3].
 * Left text col carries badges + title + subtitle. Right text col
 * carries the two key fields (ISBN + Pub Date) — everything else
 * lives in the Key Info tab now. */
.book-detail-grid-4col {
    display: grid;
    grid-template-columns: auto auto 2fr 1fr;
    gap: 1.5rem;
    align-items: start;
}
.book-detail-col-star {
    display: flex;
    justify-content: center;
}
.book-detail-col-star .library-star { padding: 4px; border-radius: 4px; transition: background 0.15s; }
.book-detail-col-star .library-star:hover { background: rgba(212, 168, 67, 0.12); }
.book-detail-col-cover {
    /* Pre-allocate placeholder space at the cover's 2:3 ratio so the
       column doesn't snap wider when the image finishes loading.
       Matches the cover-thumb height (130px) × 2/3 ≈ 87px min-width. */
    min-width: 87px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.book-detail-col-left,
.book-detail-col-right {
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-width: 0;       /* allow long titles / publisher strings to wrap */
}
.book-cover-thumb {
    height: 130px;
    width: auto;
    /* No aspect-ratio override — let the image show at its natural
       proportions. Without this rule a square cover got squished to
       87 × 130 to honour 2:3. The right-column content can be pushed
       wider for non-portrait covers; the column's min-width: 87px
       still reserves a typical paperback's worth of space so a
       portrait cover doesn't cause its neighbours to reflow. */
    border-radius: 4px;
    cursor: pointer;
    border: 1px solid var(--border);
    background: linear-gradient(110deg, #f0ede8 30%, #e8e4de 50%, #f0ede8 70%);
    background-size: 200% 100%;
    animation: img-skeleton 1.5s ease-in-out infinite;
    transition: transform 0.15s ease, box-shadow 0.15s ease;
    display: block;
}
.book-cover-thumb:hover {
    transform: scale(1.03);
    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.book-cover-thumb-empty {
    width: 87px;
    background: #cccccc;
    align-items: center;
    justify-content: center;
    color: #666;
    font-size: 0.75rem;
    cursor: default;
    animation: none;
    display: flex;
}
.book-cover-thumb-empty:hover {
    transform: none;
    box-shadow: none;
}
.book-detail-status-row {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
    align-items: center;
    margin-bottom: 2px;
}
.book-detail-title {
    margin: 0;
    font-family: 'DM Serif Display', serif;
    font-size: 1.25rem;
    line-height: 1.2;
}
.book-detail-subtitle {
    font-size: 0.9rem;
    color: var(--muted);
    margin-bottom: 4px;
}

/* Tab bar — non-scrolling band */
.book-detail-tabs {
    flex-shrink: 0;
    padding: 0 1.5rem;
    border-bottom: 2px solid var(--border);
}
.book-detail-tabs .tab-bar {
    border-bottom: none;
    margin-bottom: 0;
}

/* Tab content — only this scrolls */
.book-detail-tab-content {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 1rem 1.5rem 1.5rem;
}

/* The book detail modal-run-detail needs position:relative so the absolute
   close button anchors to it. (.modal-run-detail's existing rule doesn't
   set position; flex shells default to static.) */
.modal-run-detail { position: relative; }

/* --- Admin → Suggestions page (Phase B) ---
   Two-column layout: main suggestion stream + sidebar showing recent runs. */
.suggestions-layout {
    display: grid;
    grid-template-columns: 1fr 280px;
    gap: 1rem;
    margin-top: 1rem;
}
.suggestions-sidebar { min-width: 0; }
.suggestions-main { min-width: 0; }
.sg-card {
    margin-bottom: 0.75rem;
    transition: box-shadow 0.15s ease;
}
.sg-card:hover { box-shadow: 0 4px 14px rgba(0,0,0,0.07); }
.sg-cover {
    width: 90px;
    height: 135px;
    border-radius: 4px;
    object-fit: cover;
    flex-shrink: 0;
    background: linear-gradient(110deg, #f0ede8 30%, #e8e4de 50%, #f0ede8 70%);
    background-size: 200% 100%;
    animation: img-skeleton 1.5s ease-in-out infinite;
    border: 1px solid var(--border);
}
.sg-cover-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    background: #cccccc;
    color: #666;
    font-size: 0.7rem;
    animation: none;
}
.sg-headline-line {
    margin-top: 6px;
    font-size: 0.85rem;
    line-height: 1.4;
}
.sg-headline-link {
    color: var(--ink);
    text-decoration: none;
    border-bottom: 1px dotted var(--muted);
}
.sg-headline-link:hover { border-bottom-color: var(--accent); color: var(--accent); }
.sg-reasoning {
    margin-top: 6px;
    font-size: 0.85rem;
    line-height: 1.5;
    color: var(--ink);
    font-style: italic;
}
.sg-actions {
    display: flex;
    gap: 6px;
    margin-top: 10px;
    flex-wrap: wrap;
}
.sg-run-row {
    padding: 6px 0;
    border-bottom: 1px solid var(--border);
}
.sg-run-row:last-child { border-bottom: none; }

/* Now Trending — grouped card (one per book, with stacked headlines) */
.nt-card { margin-bottom: 0.75rem; transition: box-shadow 0.15s ease; }
.nt-card:hover { box-shadow: 0 4px 14px rgba(0,0,0,0.07); }
.nt-cover {
    width: 100px; height: 150px;
    border-radius: 4px; object-fit: cover; flex-shrink: 0;
    background: linear-gradient(110deg, #f0ede8 30%, #e8e4de 50%, #f0ede8 70%);
    background-size: 200% 100%;
    animation: img-skeleton 1.5s ease-in-out infinite;
    border: 1px solid var(--border);
}
.nt-cover-empty {
    display: flex; align-items: center; justify-content: center;
    background: #cccccc; color: #666; font-size: 0.7rem; animation: none;
}
.nt-card-body { flex: 1; min-width: 0; }
.nt-card-head { padding-bottom: 8px; border-bottom: 1px solid var(--border); margin-bottom: 8px; }
.nt-title {
    margin: 0; font-family: 'DM Serif Display', serif;
    font-size: 1.1rem; line-height: 1.25;
}
.nt-subtitle {
    font-size: 0.85rem; color: var(--muted); margin-top: 2px;
}
.nt-meta-row {
    display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
    margin-top: 6px; font-size: 0.8rem; color: var(--muted);
}
.nt-meta-sep { color: var(--border); }
.nt-isbn { color: var(--ink); background: var(--paper); padding: 1px 5px; border-radius: 3px; }
.nt-pub-date { color: var(--ink); }
.nt-stream { font-style: italic; }
.nt-match-count {
    background: var(--accent-light, #f8d7e6);
    color: var(--accent, #1a1a2e);
    padding: 1px 7px; border-radius: 10px;
    font-size: 0.7rem; font-weight: 600; font-style: normal;
}
.nt-card-actions {
    display: flex; gap: 6px; margin-top: 8px;
}

/* Headline rows under each card. */
.nt-headline-list { display: flex; flex-direction: column; gap: 6px; }
.nt-headline-row {
    display: flex; gap: 10px; align-items: flex-start;
    padding: 6px 8px; border-radius: 4px;
    transition: background 0.15s;
}
.nt-headline-row:hover { background: var(--paper); }
.nt-hr-main { flex: 1; min-width: 0; }
.nt-hr-head {
    display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
}
.nt-hr-title {
    color: var(--ink); text-decoration: none;
    border-bottom: 1px dotted var(--muted);
    font-size: 0.85rem; line-height: 1.35;
}
.nt-hr-title:hover { color: var(--accent); border-bottom-color: var(--accent); }
.nt-hr-source { font-size: 0.7rem; color: var(--muted); }
.nt-hr-reason {
    margin-top: 4px; font-size: 0.8rem; line-height: 1.4;
    color: var(--muted); font-style: italic;
}
.nt-hr-actions {
    flex-shrink: 0; display: flex; align-items: center;
}

@media (max-width: 1100px) {
    .suggestions-layout { grid-template-columns: 1fr; }
    .suggestions-sidebar { order: -1; }
}
@media (max-width: 700px) {
    .nt-card { flex-direction: column !important; }
    .nt-cover { width: 80px; height: 120px; }
}

/* Now Trending — tab bar (In the News | Events). Reuses the global
   .tab-bar / .tab-btn vocabulary; this scope just nudges spacing so
   the tab bar sits flush above the filters card. */
.nt-tab-bar {
    margin-bottom: 1rem;
    margin-top: -0.25rem;
}

/* Events-tab card variant — same chrome as the news card, but the
   row title is the event name (not a clickable link) and the source
   slot carries the date window. */
.nt-card-event { /* nothing distinct yet — variant hook for future tweaks */ }
.nt-event-name {
    color: var(--ink);
    font-weight: 500;
    font-size: 0.85rem;
    line-height: 1.35;
    border-bottom: none;
}

/* News-feeds modal — per-stream Used by checkbox list */
.nf-stream-help { font-size: 0.8rem; color: var(--muted); margin-bottom: 6px; }
.nf-stream-list {
    max-height: 220px; overflow-y: auto;
    border: 1px solid var(--border); border-radius: 6px;
    padding: 6px 8px;
}
.nf-stream-row {
    display: flex; align-items: center; gap: 8px;
    padding: 4px 0; font-size: 0.85rem;
}
.nf-stream-row + .nf-stream-row { border-top: 1px solid var(--paper); }
.nf-stream-name { flex: 1; }
.nf-stream-account { color: var(--muted); font-size: 0.75rem; }

/* Now Trending — Collections section (one card per headline cluster) */
.nt-collection-card {
    margin-bottom: 0.75rem;
    padding: 0.9rem 1.25rem 1rem;
    border-left: 3px solid var(--accent, #c2185b);
}
.nt-coll-head {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    font-size: 0.85rem;
    margin-bottom: 6px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--border);
}
.nt-coll-star { color: var(--accent, #c2185b); }
.nt-coll-title { font-weight: 600; color: var(--ink); }
.nt-coll-trait { color: var(--muted); font-style: italic; margin-left: auto; font-size: 0.78rem; }
.nt-coll-headline {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    font-size: 0.9rem; margin-bottom: 8px;
}
.nt-coll-headline-link {
    color: var(--ink); text-decoration: none;
    border-bottom: 1px dotted var(--muted);
}
.nt-coll-headline-link:hover { color: var(--accent); border-bottom-color: var(--accent); }
.nt-coll-source { font-size: 0.72rem; color: var(--muted); }
.nt-coll-rows { display: flex; flex-direction: column; gap: 8px; }
.nt-coll-row {
    display: flex; align-items: center; gap: 10px;
    padding: 6px;
    border-radius: 4px;
    background: var(--paper);
}
.nt-coll-row:hover { background: var(--card-bg, #fff); box-shadow: 0 1px 4px rgba(0,0,0,0.04); }
.nt-coll-cover {
    width: 60px; height: 90px; object-fit: cover; flex-shrink: 0;
    border-radius: 3px; border: 1px solid var(--border);
    background: linear-gradient(110deg, #f0ede8 30%, #e8e4de 50%, #f0ede8 70%);
    background-size: 200% 100%;
    animation: img-skeleton 1.5s ease-in-out infinite;
}
.nt-coll-cover-empty {
    display: flex; align-items: center; justify-content: center;
    background: #cccccc; color: #666; font-size: 0.7rem; animation: none;
}
.nt-coll-row-body { flex: 1; min-width: 0; }
.nt-coll-row-title { font-size: 0.9rem; font-weight: 500; color: var(--ink); line-height: 1.3; }
.nt-coll-row-author { font-size: 0.75rem; color: var(--muted); margin-top: 1px; }
.nt-coll-row-reason { font-size: 0.78rem; color: var(--muted); font-style: italic; margin-top: 3px; line-height: 1.4; }
.nt-coll-row-actions {
    display: flex; align-items: center; gap: 6px; flex-shrink: 0;
}

/* Now Trending — Re-run feed button + modal */
.nt-rerun-btn {
    background: none; border: 1px solid var(--border); border-radius: 6px;
    padding: 5px 8px; cursor: pointer;
    color: var(--ink);
    display: inline-flex; align-items: center; justify-content: center;
    transition: border-color 0.15s, color 0.15s;
}
.nt-rerun-btn svg { width: 14px; height: 14px; }
.nt-rerun-btn:hover { border-color: var(--accent); color: var(--accent); }

.nt-rerun-overlay {
    position: fixed; inset: 0; z-index: 100000;
    background: rgba(26, 26, 46, 0.55);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
    display: flex; align-items: center; justify-content: center;
    animation: fadeIn 0.15s ease;
}
.nt-rerun-modal {
    background: var(--card-bg, #fff);
    border-radius: 10px;
    padding: 32px 28px 26px;
    width: 380px;
    max-width: calc(100vw - 36px);
    box-shadow: 0 20px 60px rgba(0,0,0,0.25);
    text-align: center;
    position: relative;
}
.nt-rerun-close {
    position: absolute; top: 8px; right: 12px;
    background: none; border: none; font-size: 1.4rem;
    color: var(--muted); cursor: pointer; line-height: 1;
}
.nt-rerun-close:hover { color: var(--ink); }
.nt-rerun-quill {
    display: flex; justify-content: center;
    margin-bottom: 14px;
    height: 80px;
}
.nt-rerun-quill-body {
    transform-origin: 32px 64px;
    animation: nt-rerun-quill-rock 1.6s ease-in-out infinite;
}
@keyframes nt-rerun-quill-rock {
    0%, 100% { transform: rotate(-8deg); }
    50%      { transform: rotate(8deg); }
}
.nt-rerun-ink {
    animation: nt-rerun-ink-drop 2s ease-out infinite;
    transform-origin: 20px 72px;
    opacity: 0;
}
@keyframes nt-rerun-ink-drop {
    0%   { transform: translateY(0); opacity: 0.95; }
    35%  { transform: translateY(8px); opacity: 0.6; }
    60%  { transform: translateY(14px); opacity: 0; }
    100% { opacity: 0; }
}
.nt-rerun-headline {
    font-family: 'DM Serif Display', serif;
    font-size: 1.05rem;
    color: var(--ink);
    margin-bottom: 14px;
    min-height: 1.4em;
    transition: color 0.2s;
}
.nt-rerun-progress-track {
    background: var(--paper);
    border-radius: 12px;
    height: 8px;
    overflow: hidden;
    margin-bottom: 4px;
}
.nt-rerun-progress-fill {
    height: 100%;
    background: linear-gradient(90deg, var(--accent, #c2185b), var(--accent-light, #f8d7e6));
    transition: width 0.4s ease;
    width: 5%;
}
.nt-rerun-progress-pct {
    font-size: 0.75rem; color: var(--muted);
    margin-bottom: 14px;
}
.nt-rerun-phases {
    list-style: none;
    margin: 0; padding: 0;
    text-align: left;
    font-size: 0.85rem;
    color: var(--ink);
}
.nt-rerun-phases li {
    padding: 4px 0;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}
.nt-rerun-phases li:not([data-phase="done"]) { /* default styling */ }

.modal-user { max-width: 425px; }
/* Inklet Settings modal is wider — it carries the Inklet Type dropdown + Active
   + Stream access. Scoped to this modal so the shared .modal-user stays 425px. */
.modal.modal-user.modal-inklet-settings { max-width: 575px; }

/* "Understanding Inklets & Templates" info icon — neutral by default, Inkara
   accent on the outline (and icon) on rollover. Sits left of the Emporium
   user/scope dropdown. */
.ie-info-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--paper);
    color: var(--muted);
    cursor: pointer;
    transition: border-color 0.15s ease, color 0.15s ease;
}
.ie-info-btn:hover {
    border-color: var(--accent);
    color: var(--accent);
}
.modal-apikeys { max-width: 650px; }
.form-row-name { flex-wrap: nowrap; }
.form-row-name .form-group:first-child { flex: 1; min-width: 0; }
.form-row-name .form-group:last-child { flex: 2; min-width: 0; }
.modal-actions, .form-actions { display: flex; gap: 0.5rem; justify-content: flex-end; margin-top: 3rem; }

/* --- Colour Picker --- */
.colour-picker { display: flex; gap: 10px; margin-top: 4px; flex-wrap: wrap; }
.colour-swatch {
    width: 26px; height: 26px; border-radius: 50%; border: 3px solid transparent;
    cursor: pointer; display: flex; align-items: center; justify-content: center;
    transition: border-color 0.15s, transform 0.15s; padding: 0;
    /* Don't let the flex row compress width below 26px (was making the swatches
       oval); wrap instead so they stay perfect circles. */
    flex: 0 0 26px; box-sizing: border-box;
}
.colour-swatch:hover { transform: scale(1.12); }
.colour-swatch.selected { border-color: var(--ink); }

/* --- Loading --- */
.loading { text-align: center; padding: 3rem; color: var(--muted); }
.loading::after { content: ''; display: inline-block; width: 20px; height: 20px; border: 2px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.6s linear infinite; margin-left: 0.5rem; vertical-align: middle; }

/* --- Clickable rows --- */
.clickable { cursor: pointer; }
.clickable:hover td { background: #f5efe8; }

/* --- Pagination --- */
.pagination { display: flex; gap: 0.5rem; justify-content: center; margin-top: 1rem; }

/* --- Toast ---
   Bottom-right but indented past the floating feedback button (44px wide
   at right:24px, bottom:24px). Match the button's bottom edge + min-height
   so the two sit on the same horizontal line. 5.5rem (88px) right clears
   the button + leaves a ~20px gap. */
.toast {
    position: fixed;
    bottom: 24px;
    right: 5.5rem;
    min-height: 44px;
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    padding: 0.5rem 1.25rem;
    background: var(--ink);
    color: #fff;
    border-radius: var(--radius);
    font-size: 0.875rem;
    box-shadow: 0 4px 12px rgba(0,0,0,0.2);
    z-index: 2147483647;   /* always on top — above modals (99999) and lightboxes (100000) */
    animation: fadeInUp 0.3s ease-out;
}
.toast.error { background: var(--danger); }
.toast.success { background: var(--success); }
/* Error toasts carry a dismiss X (Toast.show in api.js stops auto-dismissing
   for type='error'). Sits just to the right of the message with a subtle
   hover lift. Non-error toasts have no children, just textContent. */
.toast .toast-msg { margin-right: 0.75rem; }
.toast .toast-close {
    background: transparent;
    border: none;
    color: rgba(255, 255, 255, 0.85);
    font-size: 0.95rem;
    line-height: 1;
    cursor: pointer;
    padding: 2px 4px;
    margin-left: 0.25rem;
    transition: color 0.15s ease;
}
.toast .toast-close:hover { color: #fff; }
@keyframes fadeInUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }

/* --- Detail view --- */
.detail-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: 1rem;
}
.detail-item .label { font-size: 0.75rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }
.detail-item .value { font-size: 1rem; margin-top: 0.15rem; }

/* --- Tabs --- */
.tab-bar { display: flex; gap: 0; border-bottom: 2px solid var(--border); margin-bottom: 1rem; }
.tab-btn { padding: 0.6rem 1.2rem; border: none; background: none; cursor: pointer; font-family: 'Inter', sans-serif; font-size: 0.85rem; font-weight: 500; color: var(--muted); border-bottom: 2px solid transparent; margin-bottom: -2px; transition: color 0.15s, border-color 0.15s; }
.tab-btn:hover { color: var(--ink); }
.tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); }
/* Disabled tab (e.g. Interior Images with nothing uploaded): greyed,
   unclickable — the dispatcher also ignores it as defensive depth. */
.tab-btn-disabled, .tab-btn-disabled:hover {
    color: #c3c8cf; cursor: default; pointer-events: none;
    border-bottom-color: transparent;
}
/* Panel Assets block inspector (Emporium): five format tabs — Wide / Square /
   Landscape / Vertical / Portrait — overflow this panel's narrow width on one
   line, so this variant wraps and tightens the buttons. The shared underline
   rail goes with it: once wrapped, .tab-bar's border-bottom only lines up
   under the LAST row, and .tab-btn's margin-bottom:-2px would pull the first
   row's buttons over nothing. Each active button keeps its own underline. */
.me-aplus-tabs.tab-bar {
    flex-wrap: wrap; gap: 2px 0; border-bottom: none; margin-bottom: 0.5rem;
}
.me-aplus-tabs .tab-btn {
    padding: 0.4rem 0.7rem; margin-bottom: 0; font-size: 0.78rem;
}
.tab-panel { display: none; }
.tab-panel.active { display: block; }

/* --- Breadcrumb --- */
.breadcrumb { font-size: 0.85rem; color: var(--muted); margin-bottom: 1rem; }
.breadcrumb a { color: var(--accent); text-decoration: none; }
.breadcrumb a:hover { text-decoration: underline; }

/* --- ISBN Emporium --- */

/* CTA button */
.ie-cta-wrap { text-align: center; margin: 3rem 0 2rem; }
.ie-cta-btn {
    display: inline-block; padding: 18px 48px;
    background: var(--accent); color: #fff;
    border: none; border-radius: 30px;
    font-size: 1.3rem; font-weight: 600;
    font-family: 'Inter', sans-serif;
    cursor: pointer; transition: background 0.2s, transform 0.15s;
}
.ie-cta-btn:hover { background: var(--accent-dark); transform: translateY(-1px); }
.ie-cta-sub {
    margin-top: 12px; font-size: 0.85rem;
    color: var(--ink); max-width: 500px;
    margin-left: auto; margin-right: auto;
    line-height: 1.5;
}

/* Create modal */
.ie-create-modal { align-items: flex-start; justify-content: center; padding-top: 50px; }
.ie-create-panel {
    background: var(--card-bg); border-radius: 16px;
    padding: 2.5rem; position: relative;
    box-shadow: 0 8px 30px rgba(0,0,0,0.15);
}
.ie-create-close {
    position: absolute; top: 12px; right: 16px;
    background: none; border: none; font-size: 1.5rem;
    color: var(--muted); cursor: pointer;
}
.ie-create-close:hover { color: var(--ink); }
.ie-create-options { display: flex; gap: 40px; justify-content: center; padding: 1rem 0; }
.ie-create-option-wrap { text-align: center; }
.ie-create-option {
    width: 190px; height: 190px;
    background: #f0f0f0; border: none;
    border-radius: 16px; cursor: pointer;
    display: block; position: relative;
    padding: 0; overflow: hidden; transition: all 0.2s;
}
.ie-create-option:hover {
    box-shadow: 0 4px 16px rgba(0,0,0,0.08);
    transform: translateY(-2px);
}
.ie-create-option.disabled {
    opacity: 0.4; cursor: default; pointer-events: none;
}
.ie-create-option-img {
    position: absolute; top: 0; left: 0;
    width: 100%; height: 100%;
    object-fit: cover;
    border-radius: 16px;
}
.ie-create-option-label {
    margin-top: 12px; font-size: 0.85rem; color: var(--ink);
    font-weight: 500; line-height: 1.4;
}
.ie-create-option-label.ie-label-disabled { opacity: 0.4; }
.ie-label-isbn { font-weight: 500; }

/* Wizard modal */
.ie-wizard-panel { width: 820px; max-width: 90vw; }
.ie-wiz-step { margin-bottom: 8px; }
.ie-wiz-header {
    font-size: 0.82rem; font-weight: 600; color: var(--muted);
    text-transform: uppercase; letter-spacing: 0.3px; margin-bottom: 10px;
}
.ie-wiz-summary {
    padding: 10px 14px; background: var(--cream); border-radius: 8px;
    font-size: 0.85rem; color: var(--ink); display: flex; align-items: center; gap: 8px;
    flex-wrap: wrap;
}
.ie-wiz-change {
    color: var(--accent); font-size: 0.78rem; text-decoration: none;
    margin-left: auto; white-space: nowrap;
}
.ie-wiz-change:hover { text-decoration: underline; }

/* Search inside wizard */
.ie-modal-search-title { font-family: 'DM Serif Display', serif; font-size: 1.2rem; color: var(--ink); margin-bottom: 1rem; letter-spacing: -0.3px; }
.ie-wiz-step .ie-search-container { max-width: 100%; width: 100%; }
.ie-wiz-step .ie-search-box { border-radius: 12px; }
.ie-wiz-step .ie-results { border-radius: 0 0 12px 12px; max-height: 350px; }
.ie-modal-back:hover { color: var(--ink); }

/* Browse tabs + book cards */
.ie-new-books-section { margin-top: 1.5rem; }
.ie-browse-tabs {
    display: flex; gap: 4px; margin-bottom: 1rem;
    border-bottom: 1px solid var(--border); padding-bottom: 0;
}
.ie-browse-tab {
    background: none; border: none; border-bottom: 2px solid transparent;
    padding: 8px 14px; font-size: 0.82rem; font-weight: 500;
    color: var(--muted); cursor: pointer; transition: all 0.2s;
    margin-bottom: -1px;
}
.ie-browse-tab:hover:not(.disabled) { color: var(--ink); }
.ie-browse-tab.active {
    color: var(--accent); border-bottom-color: var(--accent); font-weight: 600;
}
.ie-browse-tab.disabled {
    opacity: 0.35; cursor: default; pointer-events: none;
}
.ie-new-books-scroll { max-height: 420px; overflow-y: auto; }
.ie-new-books-grid {
    display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px;
    align-items: start;
}
.ie-book-card {
    cursor: pointer; text-align: center; transition: transform 0.15s;
    display: flex; flex-direction: column;
}
.ie-book-card:hover { transform: translateY(-2px); }
.ie-book-card-cover-wrap {
    width: 100%; height: 190px;
    border-radius: 6px; overflow: hidden;
    background: #ccc;
}
.ie-book-card-cover-wrap img {
    width: 100%; height: 100%;
    object-fit: cover; display: block;
}
.ie-book-card-placeholder {
    background: #ccc;
}
.ie-book-card-title {
    font-size: 0.78rem; font-weight: 600; color: var(--ink);
    margin-top: 6px; line-height: 1.3;
    overflow: hidden; text-overflow: ellipsis;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.ie-book-card-author {
    font-size: 0.72rem; color: var(--muted); margin-top: 2px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ie-book-card-stream {
    font-size: 0.65rem; color: var(--text-light); margin-top: 2px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    font-style: italic;
}
.ie-view-more-btn {
    background: var(--card-bg); color: var(--ink);
    border: 1px solid var(--border); padding: 8px 24px;
    border-radius: 20px; font-size: 0.85rem; cursor: pointer;
    transition: all 0.2s;
}
.ie-view-more-btn:hover {
    background: var(--accent); color: #fff; border-color: var(--accent);
}

.ie-channel-grid {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;
    margin-top: 16px;
}

/* ── Settings > Events — master-list lozenge picker ──
 * 3-column grid of clickable lozenges. Each shows the event name (bold),
 * its window dates (muted), and a recurrence badge in the corner. Single
 * click toggles the .selected state — a soft accent fill + border. Used
 * exclusively by the picker modal in views-settings-events.js. Sized
 * for the 980 px-wide picker modal. */
.se-events-picker-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 12px;
    padding: 4px;
}
.se-event-lozenge {
    position: relative;
    padding: 14px 14px 18px;
    border: 2px solid var(--border);
    border-radius: 10px;
    background: var(--cream);
    cursor: pointer;
    transition: border-color 0.12s, background 0.12s, transform 0.12s, box-shadow 0.12s;
}
.se-event-lozenge:hover {
    border-color: var(--accent);
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(0,0,0,0.06);
}
.se-event-lozenge.selected {
    border-color: var(--accent);
    background: var(--accent-light);
}
.se-event-lozenge-name {
    font-weight: 700;
    font-size: 0.92rem;
    color: var(--ink);
    line-height: 1.25;
    margin-bottom: 6px;
    /* Cap to ~2 lines so the grid stays uniform */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.se-event-lozenge-window {
    font-size: 0.78rem;
    color: var(--muted);
}
.se-event-lozenge-badge {
    position: absolute;
    top: 10px;
    right: 10px;
    font-size: 0.68rem;
}

.ie-channel-card {
    display: flex; flex-direction: column; align-items: center; text-align: center;
    padding: 12px 10px; border-radius: 10px; border: 2px solid var(--border);
    background: var(--cream); cursor: pointer; transition: all 0.15s;
}
.ie-channel-card:hover { border-color: var(--accent); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
.ie-channel-card.selected { border-color: var(--accent); background: var(--accent-light); }
.ie-channel-card-icon { margin-bottom: 6px; opacity: 0.6; line-height: 1; }
.ie-channel-card-icon svg { width: 28px; height: 28px; }
.ie-channel-card-name { font-weight: 700; font-size: 0.9rem; color: var(--ink); margin-bottom: 4px; }
.ie-channel-card-dims { font-size: 0.78rem; color: var(--muted); margin-bottom: 4px; }
.ie-channel-card-desc { font-size: 0.72rem; color: var(--text-light); line-height: 1.3; }

/* ── Campaigns "+ New" wizard — step 1 info zone ──
 * Fills the whitespace under the marketing-area cards with three quiet
 * elements: real sample assets ("What you can make", click = shortcut
 * to that format), a 4-step how-it-works strip, and one rotating pro
 * tip. Deliberately muted — the area cards above stay the loudest
 * thing in the modal. Rendered by renderStep1Info (views-shared.js). */
.cw-info { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--border); }
/* Primary mode: the tile strip IS step 1's content (the marketing-area
 * cards are gone). A fixed flex frame: .cw-scroll owns the tile zone's
 * scrolling; the steps + pro-tip band below stays pinned (the modal body
 * gets overflow:hidden from renderStep1Tiles). */
.cw-info.cw-info-primary {
    margin-top: 6px; padding-top: 0; border-top: none;
    flex: 1; min-height: 0; display: flex; flex-direction: column;
}
.cw-info-primary .cw-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-right: 4px; }
.cw-info-label {
    font-size: 0.68rem; font-weight: 600; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--muted); margin-bottom: 10px;
}
.cw-samples { display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start; }
.cw-sample { background: none; border: none; padding: 0; cursor: pointer; text-align: left; }
.cw-sample-img {
    display: block; height: 125px; border-radius: 8px; overflow: hidden;
    border: 1px solid var(--border); background: #fff;
    transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}
.cw-sample-img img { display: block; height: 125px; width: auto; max-width: 240px; object-fit: cover; }
/* Fixed-aspect mode: the box carries the CHANNEL's aspect (inline width
 * from renderStep1Tiles) and the sample cover-crops to fill it. */
.cw-sample-img.cw-sample-fixed img { width: 100%; height: 100%; max-width: none; }
/* Placeholder tile: a channel whose prompts have no sample yet still
 * needs a tile — the strip is the only route to that format. */
.cw-sample-img.cw-sample-ph {
    display: flex; align-items: center; justify-content: center;
    background: #FAF9F5; color: var(--muted);
}
.cw-sample-img.cw-sample-ph svg { width: 26px; height: 26px; opacity: 0.55; }
/* Duo face: two examples of a narrow format side by side inside the one
 * button (single caption, one click target). */
.cw-sample-duo { display: flex; gap: 6px; }
.cw-sample:hover .cw-sample-img {
    border-color: var(--accent); transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0,0,0,0.08);
}
.cw-sample-cap { display: block; font-size: 0.72rem; color: var(--muted); margin-top: 6px; }
.cw-sample-grp { display: block; font-size: 0.65rem; color: var(--muted); opacity: 0.7; margin-top: 1px; }
/* Bottom band: steps 1-4 stacked vertically on the LEFT, the pro-tip box
 * on the RIGHT stretched to match the steps' height. Pinned below the
 * scrolling tile zone (flex-shrink 0). */
.cw-info-bottom {
    display: flex; gap: 28px; align-items: stretch;
    margin-top: 16px; flex-shrink: 0;
    border-top: 1px solid var(--border); padding-top: 14px;
}
.cw-steps {
    display: flex; flex-direction: column; align-items: flex-start; gap: 10px;
    font-size: 0.8rem; color: var(--muted); padding-top: 2px; flex: 0 0 auto;
}
.cw-step { display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; }
.cw-step-dot {
    display: inline-flex; align-items: center; justify-content: center;
    width: 18px; height: 18px; border-radius: 50%; flex: 0 0 18px;
    background: var(--accent-light); color: var(--accent);
    font-size: 0.68rem; font-weight: 600;
}
.cw-step-arrow { color: #c9c2b8; }
.cw-steps-note { color: var(--text-light); font-style: italic; margin-top: 2px; }
.cw-tip {
    display: flex; gap: 10px; align-items: center; flex: 0 1 33.33%; min-width: 0;
    background: #FAF9F5; border: 1px solid var(--border);
    border-left: 3px solid var(--accent); border-radius: 8px;
    padding: 12px 16px; font-size: 14px; color: var(--muted); line-height: 1.5;
}
.cw-tip svg { width: 16px; height: 16px; flex: 0 0 16px; color: var(--accent); }
.cw-tip b { color: var(--ink); font-weight: 600; }
.cw-tip-next {
    align-self: center; margin-left: 2px; flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; border-radius: 50%;
    background: none; border: 1px solid var(--border); cursor: pointer;
    color: var(--muted); padding: 0; transition: all 0.15s;
}
.cw-tip-next:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-light); }
.cw-tip-next svg { width: 13px; height: 13px; flex: 0 0 13px; margin-top: 0; color: inherit; }
/* Promo slot — CMS-managed artwork (content pages category Promo) in the
 * band's remaining right-hand space. The card fills the band's height (the
 * steps column sets it); the image keeps its own aspect inside that. */
.cw-promo {
    flex: 1 1 auto; min-width: 0;
    /* center, NOT stretch: the band runs a few px taller than the 156px
     * artwork (the steps column sets it), and a stretched card shows that
     * difference as a white sliver under the image with the dots on it. */
    display: flex; align-items: center; justify-content: flex-end; gap: 8px;
}
.cw-promo-card {
    background: none; border: 1px solid var(--border); border-radius: 8px;
    padding: 0; cursor: pointer; overflow: hidden; max-width: 100%;
    transition: box-shadow 0.15s, border-color 0.15s;
}
.cw-promo-card:hover { border-color: var(--accent); box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
/* Fixed image height ≈ the steps column, so a wide promo can never grow
 * the band and eat the tiles zone; width follows the artwork's aspect up
 * to the available space. With a promo live the Pro Tip box is hidden
 * (views-shared.js) and the strip gets the full width right of the steps. */
.cw-promo-card img { display: block; height: auto; max-height: 156px; max-width: 100%; }
.cw-promo-next { align-self: center; }
/* Live dot indicators overlaid bottom-centre of the artwork — one per live
 * promo (rendered by views-shared.js, never baked into the image), current
 * one filled, all clickable. White + shadow stays legible on any ground. */
.cw-promo-frame { position: relative; display: inline-flex; }
.cw-promo-dots {
    position: absolute; left: 50%; bottom: 9px; transform: translateX(-50%);
    display: flex; gap: 7px; z-index: 2;
}
.cw-promo-dot {
    width: 9px; height: 9px; border-radius: 50%; border: none; padding: 0;
    cursor: pointer; background: rgba(255, 255, 255, 0.55);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35); transition: background 0.15s;
}
.cw-promo-dot:hover, .cw-promo-dot.is-active { background: #fff; }

/* ── PRO TIPS — anchored one-shot coach marks (pro-tips.js) ──
 * House-styled version of the "NEW HERE" mockup: Label pill, Heading
 * line, Body copy, full-width accent CTA at weight 500 (accent-fill
 * buttons are never 600 — house rule). z-index clears the Studio form
 * modal (99999/100001 family). */
/* Backdrop pair (pro-tips.js): the scrim is a transparent full-screen
 * click-catcher ("not now"); the spot sits over the anchor and its huge
 * box-shadow dims everything EXCEPT the anchor — the spotlight cut-out. */
.pro-tip-scrim { position: fixed; inset: 0; z-index: 100048; }
.pro-tip-spot {
    position: fixed; z-index: 100049; pointer-events: none;
    border-radius: 10px;
    box-shadow: 0 0 0 100vmax rgba(26, 26, 46, 0.45);
    animation: fadeIn 0.18s ease;
}
.pro-tip-pop {
    position: fixed; z-index: 100050; width: 300px;
    background: #fff; border: 1px solid var(--border); border-radius: 12px;
    box-shadow: 0 10px 32px rgba(0, 0, 0, 0.16);
    padding: 16px 18px 18px; animation: fadeIn 0.18s ease;
}
.pro-tip-pill {
    display: inline-block; background: var(--accent-light); color: var(--accent);
    font-size: 0.66rem; font-weight: 600; letter-spacing: 0.12em;
    text-transform: uppercase; padding: 4px 12px; border-radius: 999px;
}
.pro-tip-title {
    font-weight: 700; color: var(--ink); font-size: 0.98rem;
    margin: 10px 0 4px; line-height: 1.3;
}
.pro-tip-body { color: var(--muted); font-size: 0.85rem; line-height: 1.45; }
.pro-tip-body p { margin: 0 0 6px; }
.pro-tip-body p:last-child { margin-bottom: 0; }
.pro-tip-got {
    margin-top: 14px; width: 100%; background: var(--accent); color: #fff;
    border: none; border-radius: 999px; padding: 10px 0;
    font-family: inherit; font-weight: 500; font-size: 0.9rem; cursor: pointer;
    transition: background 0.15s;
}
.pro-tip-got:hover { background: color-mix(in srgb, var(--accent) 88%, #000); }
.pro-tip-x {
    position: absolute; top: 10px; right: 12px;
    background: none; border: none; cursor: pointer; color: var(--muted);
    font-size: 1.15rem; line-height: 1; padding: 2px 4px;
}
.pro-tip-x:hover { color: var(--ink); }
/* "Reset all Pro Tips" row in User Settings (self only) — small circular
 * icon control with its text label beside it, per the profile mockup. */
.protip-reset-row { display: flex; align-items: center; gap: 10px; }
.protip-reset-btn {
    width: 34px; height: 34px; border-radius: 50%; flex: 0 0 34px;
    border: 1px solid var(--border); background: #fff; color: var(--muted);
    display: inline-flex; align-items: center; justify-content: center;
    cursor: pointer; padding: 0; transition: all 0.15s;
}
.protip-reset-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); background: var(--accent-light); }
.protip-reset-btn:disabled { opacity: 0.55; cursor: default; }
.protip-reset-label { font-size: 0.9rem; color: var(--ink); cursor: pointer; user-select: none; }

/* Column accordions in Display tab (two-column mode) */
.col-accordion-header {
    display: flex; justify-content: space-between; align-items: center;
    padding: 10px 12px; background: var(--cream); border: 1px solid var(--border);
    border-radius: 6px; cursor: pointer; font-weight: 600; font-size: 0.85rem;
    color: var(--ink); user-select: none;
}
.col-accordion-header:hover { background: #f0ede8; }
.col-accordion-header.active { border-color: var(--accent); color: var(--accent); }
.col-accordion-arrow { font-size: 0.7rem; transition: transform 0.2s; }
.col-accordion-header.active .col-accordion-arrow { transform: rotate(180deg); }
.col-accordion-body { padding: 8px 0; }

/* Display sub-tab panes */
.display-tab-pane { display: none; padding-top: 12px; }
.display-tab-pane.active { display: block; }
.ie-channel-card-img {
    width: 150px; height: 150px; border-radius: 12px; overflow: hidden;
    background: #f0ede8; box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.ie-channel-card-img img {
    width: 100%; height: 100%; object-fit: cover; display: block;
}
.ie-channel-card-name {
    font-size: 0.82rem; font-weight: 600; color: var(--ink);
    margin-top: 8px; text-align: center;
}
.ie-channel-card-summary {
    font-size: 0.7rem; color: var(--muted); text-align: center;
    margin-top: 2px;
}
.ie-channel-card-dims {
    font-size: 0.65rem; color: var(--text-light); text-align: center;
    margin-top: 1px;
}

.ie-search-wrap { display: flex; justify-content: center; margin: 2rem 0; }
.ie-search-container { position: relative; width: 100%; max-width: 650px; }
.ie-search-box {
    display: flex; align-items: stretch;
    border: 2px solid var(--border); border-radius: 30px;
    background: var(--card-bg); transition: border-color 0.2s;
    overflow: visible;
}
.ie-search-box:focus-within { border-color: var(--accent); }

/* Custom stream picker */
.ie-stream-picker {
    position: relative; flex-shrink: 0;
    border-right: 1px solid var(--border);
    cursor: pointer; user-select: none;
}
.ie-stream-display {
    display: flex; flex-direction: column; justify-content: center;
    padding: 10px 28px 10px 20px; min-width: 120px; max-width: 220px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%238a8477' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right 10px center;
}
.ie-stream-label {
    font-size: 0.7rem; font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.05em; color: var(--ink);
}
.ie-stream-name {
    font-size: 0.8rem; color: var(--muted); margin-top: 2px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ie-stream-dropdown {
    display: none; position: absolute; top: calc(100% + 6px); left: 0;
    min-width: 220px; max-height: 280px; overflow-y: auto;
    background: var(--card-bg); border: 1px solid var(--border);
    border-radius: var(--radius); box-shadow: 0 4px 12px rgba(0,0,0,0.12);
    z-index: 200;
}
.ie-stream-dropdown.open { display: block; }
.ie-stream-option {
    padding: 10px 16px; font-size: 0.85rem; cursor: pointer;
    border-bottom: 1px solid var(--border); transition: background 0.15s;
}
.ie-stream-option:last-child { border-bottom: none; }
.ie-stream-option:hover { background: var(--paper); }
.ie-stream-option.active { background: var(--accent-light); color: var(--accent); font-weight: 500; }

.ie-search {
    flex: 1; padding: 18px 20px 18px 16px; font-size: 1.2rem;
    border: none; outline: none; background: transparent;
    font-family: 'Inter', sans-serif; box-sizing: border-box;
    min-width: 0;
}
.ie-subtitle { font-family: 'DM Serif Display', serif; font-size: 1.2rem; color: var(--ink); margin: 2rem 0 1rem; }
.ie-categories { display: flex; gap: 12px; flex-wrap: wrap; }
.ie-category {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 10px 20px; background: var(--card-bg);
    border: 1px solid var(--border); border-radius: 24px;
    font-size: 0.9rem; font-weight: 500; color: var(--ink);
    cursor: pointer; transition: background 0.2s, border-color 0.2s;
}
.ie-category:hover { background: var(--accent-light); border-color: var(--accent); }
.ie-category svg { width: 16px; height: 16px; }

/* Search results dropdown */
.ie-results {
    position: absolute; top: calc(100% + 4px); left: 0; right: 0;
    background: var(--card-bg); border: 1px solid var(--border);
    border-radius: var(--radius); box-shadow: 0 4px 12px rgba(0,0,0,0.1);
    max-height: 420px; overflow-y: auto; z-index: 100;
}
.ie-result {
    display: flex; align-items: center; gap: 12px;
    padding: 10px 16px; border-bottom: 1px solid var(--border); cursor: default;
}
.ie-result:last-child { border-bottom: none; }
.ie-result:hover { background: var(--paper); }
.ie-result-img {
    width: 40px; height: 40px; object-fit: cover;
    border-radius: 4px; background: #eee; flex-shrink: 0;
}
.ie-result-img.author { border-radius: 50%; }
.ie-result-info { flex: 1; min-width: 0; }
.ie-result-title {
    font-weight: 500; font-size: 0.9rem;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ie-result-sub { font-size: 0.8rem; color: var(--muted); }

/* --- Widget Grid (ISBN Emporium) --- */
.ie-widget-grid {
    display: flex; flex-wrap: wrap; gap: 16px; justify-content: flex-start;
}
.ie-widget-card {
    width: 120px; height: 110px; display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 8px;
    background: var(--cream); border: 1px solid var(--border); border-radius: 10px;
    cursor: pointer; transition: all 0.15s ease; text-decoration: none; color: var(--ink);
}
.ie-widget-card:hover {
    border-color: var(--accent); box-shadow: 0 2px 8px rgba(175,40,96,0.12);
    transform: translateY(-2px);
}
.ie-widget-icon { width: 32px; height: 32px; color: var(--ink); }
.ie-widget-icon svg { width: 100%; height: 100%; }
.ie-widget-label { font-size: 0.75rem; font-weight: 500; text-align: center; line-height: 1.2; }

/* --- Widget Editor (Inklet Editor) --- */
.ie-editor-widget-switcher {
    display: flex; align-items: center; gap: 8px;
    background: rgba(255,255,255,0.15); border: 1px solid rgba(255,255,255,0.4);
    border-radius: 20px; padding: 6px 14px 6px 8px;
    color: #fff; cursor: pointer; font-size: 0.85rem; font-weight: 500;
    position: relative; user-select: none;
}
.ie-editor-widget-switcher:hover { background: rgba(255,255,255,0.25); }
.ie-editor-widget-icon-sm { width: 20px; height: 20px; }
.ie-editor-widget-icon-sm svg { width: 100%; height: 100%; }
.ie-editor-widget-dropdown {
    display: none; position: absolute; top: 60px; left: 16px;
    background: var(--card-bg); border: 1px solid var(--border); border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.15); min-width: 220px; z-index: 1000;
    max-height: 400px; overflow-y: auto;
}
.ie-editor-widget-dropdown.open { display: block; }
.ie-editor-widget-option {
    display: flex; align-items: center; gap: 10px;
    padding: 10px 14px; cursor: pointer; color: var(--ink); font-size: 0.85rem;
    border-bottom: 1px solid var(--border);
}
.ie-editor-widget-option:last-child { border-bottom: none; }
.ie-editor-widget-option:hover { background: var(--cream); }
.ie-editor-widget-option.active { color: var(--accent); font-weight: 600; }
.ie-editor-widget-option .ie-editor-widget-icon { width: 22px; height: 22px; flex-shrink: 0; }
.ie-editor-widget-option .ie-editor-widget-icon svg { width: 100%; height: 100%; }

/* --- Inklet Gallery --- */
.inklet-gallery-grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 16px;
}
/* Single-row variant used by ISBN Emporium > Inklets (Create + Generate
   sections). Same minmax breakpoint as the grid above; layout flexes to
   fit available width. The frontend caps card count to ROW_LIMIT so
   overflow only matters at very small viewports. */
.inklet-gallery-row {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 16px;
}
/* Footer strip below each Inklet row: optional left-aligned controls
   (e.g. show-archived toggle), right-aligned "View all". No divider —
   the row above is treated as the visual section boundary. */
.inklet-row-foot {
    display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; margin-top: 12px; flex-wrap: wrap;
}
/* "View all" link, bottom-right of each row. Placeholder behaviour for
   now (no href); see views-isbn.js. */
.inklet-gallery-view-all {
    font-size: 13px; color: var(--accent); text-decoration: none;
}
.inklet-gallery-view-all:hover { text-decoration: underline; }
/* Muted variant — same text styling as `.inklet-gallery-view-all`, used
   for the "Archived →" link that sits to the right of "View all →". */
.inklet-gallery-view-all--muted {
    color: var(--muted);
}
.inklet-gallery-view-all--muted:hover { color: var(--muted); text-decoration: underline; }
/* Wrap the View all + Archived links so they sit side-by-side at the
   start of the row footer. Without this wrapper the `.inklet-row-foot`
   `space-between` rule would push them to opposite ends. */
.inklet-row-foot-links {
    display: flex; align-items: center; gap: 1rem;
}
/* Two-tone section title used by ISBN Emporium > Inklets section headers.
   Mirrors the book detail modal's contrasting title/subtitle font pairing
   (DM Serif Display + Inter); see views-books.js:194 for the modal source. */
.section-title-pair {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin: 0;
}
.section-title-pair .section-title {
    font-family: 'DM Serif Display', serif;
    font-size: 1.5rem;
    font-weight: 400;
    letter-spacing: -0.3px;
    color: var(--ink);
    line-height: 1.2;
}
/* House style: Section */
.section-title-pair .section-subtitle {
    font-family: var(--font-sans);
    font-size: var(--text-section);
    font-weight: 400;
    color: var(--muted);
    line-height: 1.2;
}

.inklet-gallery-card {
    position: relative; border: 1px solid var(--border); border-radius: 10px;
    overflow: hidden; cursor: pointer; background: var(--paper);
    transition: box-shadow 0.15s, transform 0.15s;
}
.inklet-gallery-card:hover { box-shadow: 0 4px 16px rgba(0,0,0,0.1); transform: translateY(-2px); }
.inklet-gallery-thumb {
    width: 100%; aspect-ratio: 1; overflow: hidden;
    background: linear-gradient(110deg, #f0ede8 30%, #e8e4de 50%, #f0ede8 70%);
    background-size: 200% 100%;
    animation: img-skeleton 1.5s ease-in-out infinite;
}
.inklet-gallery-thumb img {
    width: 100%; height: 100%; object-fit: cover; display: block;
    opacity: 0; transition: opacity 0.3s ease;
}
.inklet-gallery-thumb img.loaded { opacity: 1; }
/* Containing block for the pending-thumbnail spinner, applied only while a
   thumbnail is generating. Scoped to its own class deliberately: the card is
   already position:relative, so an unscoped rule here would let the
   `position:absolute; inset:0` overlay cover the whole card — name, badges and
   corner buttons included — instead of just the image. */
.inklet-gallery-thumb.is-thumb-pending { position: relative; }
@keyframes img-skeleton {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}
.inklet-gallery-meta { padding: 10px 12px; }
.inklet-gallery-name {
    font-size: 0.82rem; font-weight: 600; color: var(--ink);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.inklet-gallery-badge {
    display: inline-block; font-size: 0.65rem; padding: 2px 6px; border-radius: 4px;
    background: var(--sand); color: var(--muted); margin-top: 4px;
}
/* Channel line on inklet cards: "{Group} {Channel}" — flush-left plain
   text (no pill bg), group rendered heavier than the channel name. */
.inklet-gallery-channel {
    font-size: 0.68rem; color: var(--muted); margin-top: 4px;
}
.inklet-gallery-channel-group { font-weight: 600; display: block; }
.inklet-gallery-channel-name  { font-weight: 400; display: block; }
.inklet-gallery-channel-desc  { font-weight: 400; display: block; color: var(--text-light); }
.inklet-gallery-date { font-size: 0.68rem; color: var(--muted); margin-top: 4px; }
/* Corner-action buttons (Duplicate + Delete) shown on rollover of any
   gallery card. Same SVG icons + 24×24 dark-pill treatment used by the
   multi-editor canvas tools row, so the visual language is consistent. */
.inklet-gallery-corner-btn {
    position: absolute;
    top: 6px; width: 24px; height: 24px;
    border-radius: 50%; border: none;
    background: rgba(0,0,0,0.5);
    color: #fff;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    opacity: 0; transition: opacity 0.15s, background 0.15s;
    padding: 0;
    z-index: 3;
}
.inklet-gallery-card:hover .inklet-gallery-corner-btn { opacity: 1; }
.inklet-gallery-corner-btn:hover { background: var(--accent); }
/* Right-most button = Delete/Archive; sits flush right. Duplicate sits to
   its left. 2026-06-29 — Download/export (Feather download) sits to the
   left of Duplicate at 66px, between it and the cog. Inklet-Settings cog
   sits on the far left of the row. Phase 26 toggle-template (Feather
   log-out) is retired but its offset rule is kept harmless. Order from
   right to left:
     active : [ archive @ 6 ] [ dup @ 36 ] [ download @ 66 ] [ cog @ 96 ]. */
.inklet-gallery-delete           { right: 6px; }
.inklet-gallery-duplicate        { right: 36px; }
.inklet-gallery-download         { right: 66px; }
.inklet-gallery-toggle-template  { right: 66px; }
.inklet-gallery-cog              { right: 66px; }
/* When the download/export button is present (active all-action cards) the
   cog shifts one slot left so the four-button row stays gap-free. */
.inklet-gallery-card:has(.inklet-gallery-download) .inklet-gallery-cog,
.inklet-gallery-card:has(.inklet-gallery-toggle-template) .inklet-gallery-cog {
    right: 96px;
}
/* Share (Feather user-plus) takes the slot between download and the cog, so
   the row reads cog · share · download · duplicate · archive left-to-right.
   These buttons are absolutely positioned in 30px slots by `right:`, NOT a
   flex row — a new button with no offset falls back to its static position and
   lands adrift at the card's left edge. This rule follows the download-cog
   override above so it wins on order when both match. */
.inklet-gallery-share { right: 96px; }
.inklet-gallery-card:has(.inklet-gallery-share) .inklet-gallery-cog { right: 126px; }
/* Archive button (active cards) uses the default accent rollover from
   .inklet-gallery-corner-btn:hover — no red override (it archives, not deletes). */
/* Archived cards: right-aligned like active cards. Order (left→right):
   restore (upload) then delete-forever (trash) flush at the corner.
   Delete-forever is always red — white icon on danger bg — matching the
   destructive trash treatment used elsewhere (.btn-danger). */
.inklet-gallery-restore          { right: 36px; }
.inklet-gallery-delete-forever   { right: 6px; background: var(--danger); }
.inklet-gallery-delete-forever:hover { background: #a02822; }

/* Generate-row hover overlay: mutes the card and surfaces explicit Edit + Use
   buttons for live (non-archived) template cards. Mirrors the
   .inklet-gallery-delete opacity pattern so visual cohesion stays consistent
   across hover affordances. */
.inklet-gallery-hover-actions {
    position: absolute; inset: 0;
    background: rgba(20, 20, 30, 0.62);
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    gap: 10px;
    opacity: 0; pointer-events: none;
    transition: opacity 0.15s ease;
    z-index: 1;
}
.inklet-gallery-card:hover .inklet-gallery-hover-actions {
    opacity: 1; pointer-events: auto;
}
/* Match the hero-stream-switcher pill: translucent white on a dark backdrop,
   rounded, white text. Overrides .btn / .btn-primary background + border so
   the buttons read as "hero chrome" rather than primary CTAs. */
.inklet-gallery-hover-actions .btn {
    width: 150px;
    padding: 6px 14px;
    font-size: 0.85rem;
    font-weight: 500;
    background: rgba(255, 255, 255, 0.15);
    border: 1px solid rgba(255, 255, 255, 0.4);
    border-radius: 20px;
    color: #fff;
}
.inklet-gallery-hover-actions .btn:hover {
    background: rgba(255, 255, 255, 0.25);
    border-color: rgba(255, 255, 255, 0.5);
    color: #fff;
}

/* Lift the corner archive / restore / delete-forever buttons above the new
   overlay so they remain clickable when the overlay is visible. */
.inklet-gallery-delete,
.inklet-gallery-restore,
.inklet-gallery-delete-forever {
    z-index: 2;
}

/* --- Feedback Widget --- */
.ie-feedback-btn {
    position: fixed;
    bottom: 24px;
    right: 24px;
    /* Above the 95% editor modal (100002), layers palette (100005), and the
       editor's child dialogs (100010) so a ticket can be raised from anywhere.
       Below the action-modal blocker (2147483647) which is the "I'm working,
       don't touch me" overlay. */
    z-index: 100020;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: none;
    background: var(--accent);
    color: #fff;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 12px rgba(0,0,0,0.2);
    transition: background 0.15s, transform 0.15s;
}
.ie-feedback-btn:hover { background: #8a1f3c; transform: scale(1.05); }
/* Red dot indicator for unread replies */
.ie-feedback-btn.has-unread::after {
    content: '';
    position: absolute;
    top: 2px;
    right: 2px;
    width: 12px;
    height: 12px;
    background: #ef4444;
    border-radius: 50%;
    border: 2px solid var(--paper, #fff);
    pointer-events: none;
}
.ie-feedback-panel {
    position: fixed;
    bottom: 80px;
    right: 24px;
    z-index: 100020;
    width: 360px;
    background: var(--card-bg, #fff);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 16px;
    box-shadow: 0 8px 32px rgba(0,0,0,0.15);
    font-family: 'Inter', sans-serif;
}
.ie-feedback-header {
    font-size: 0.95rem;
    font-weight: 600;
    margin-bottom: 4px;
}
.ie-feedback-email {
    font-size: 0.75rem;
    color: var(--muted);
    margin-bottom: 12px;
}
.ie-feedback-textarea {
    width: 100%;
    box-sizing: border-box;
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 10px;
    font-size: 0.85rem;
    font-family: 'Inter', sans-serif;
    resize: vertical;
    min-height: 80px;
    margin-bottom: 8px;
    background: var(--paper, #fff);
    color: var(--ink);
}
.ie-feedback-textarea:focus {
    outline: none;
    border-color: var(--accent);
}
.ie-feedback-checkbox {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.8rem;
    color: var(--muted);
    margin-bottom: 12px;
    cursor: pointer;
}
.ie-feedback-status {
    font-size: 0.75rem;
    color: var(--muted);
    text-align: center;
    margin-top: 8px;
    min-height: 1em;
}

/* Ticket list in the feedback panel */
.ie-fb-ticket-list {
    max-height: 340px;
    overflow-y: auto;
}
.ie-fb-ticket-row {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 8px;
    border-bottom: 1px solid var(--border);
    cursor: pointer;
    transition: background 0.1s;
    font-size: 0.8rem;
}
.ie-fb-ticket-row:hover { background: var(--cream, #faf9f5); }
.ie-fb-ticket-row:last-child { border-bottom: none; }
.ie-fb-ticket-info { flex: 1; min-width: 0; }
.ie-fb-ticket-comment {
    font-weight: 500;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--ink);
    margin-bottom: 2px;
}
.ie-fb-ticket-meta { font-size: 0.7rem; color: var(--muted); display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.ie-fb-unread-dot {
    width: 8px; height: 8px;
    background: #ef4444;
    border-radius: 50%;
    flex-shrink: 0;
    margin-top: 6px;
}

/* Thread messages in the feedback panel */
.ie-fb-thread-messages {
    max-height: 300px;
    overflow-y: auto;
    margin-bottom: 8px;
    padding: 4px 0;
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
}

/* Reply form in the feedback panel */
.ie-fb-reply-form { margin-top: 4px; }

/* New Inklet card variant — reuses .inklet-gallery-card for dimensions/hover.
   Overrides the skeleton gradient/animation on .inklet-gallery-thumb so a
   random hero background image can show through, darkened slightly, with a
   white Feather plus-circle centred on top. */
.inklet-gallery-card.inklet-gallery-new .inklet-gallery-thumb {
    background: none;
    animation: none;
    position: relative;
}
.inklet-gallery-card.inklet-gallery-new .inklet-gallery-bg {
    position: absolute;
    inset: 0;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
.inklet-gallery-card.inklet-gallery-new .inklet-gallery-bg::after {
    content: '';
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.28);
    pointer-events: none;
}
.inklet-gallery-card.inklet-gallery-new .inklet-gallery-plus {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 64px;
    height: 64px;
    color: #fff;
    stroke-width: 1.2;
    pointer-events: none;
    z-index: 1;
    transition: color 0.15s ease;
}
.inklet-gallery-card.inklet-gallery-new:hover .inklet-gallery-plus {
    /* Subtle neutral contrast vs the default white — keeps the lozenge
     * visually different on hover without competing with the accent
     * colour used for primary CTAs elsewhere. */
    color: #d4d4d8;
}

/* --- Save Split Button --- */
.ie-save-dropdown-btn {
    display: flex; align-items: stretch; border-radius: 6px; overflow: hidden;
    border: 1px solid rgba(255,255,255,0.3);
}
.ie-save-main {
    background: rgba(255,255,255,0.2); color: #fff; border: none;
    padding: 4px 14px; font-size: 0.85rem; cursor: pointer; font-family: 'Inter', sans-serif;
}
.ie-save-main:hover { background: rgba(255,255,255,0.3); }
.ie-save-caret {
    background: rgba(255,255,255,0.15); color: #fff; border: none; border-left: 1px solid rgba(255,255,255,0.3);
    padding: 4px 8px; font-size: 0.6rem; cursor: pointer;
}
.ie-save-caret:hover { background: rgba(255,255,255,0.3); }
.ie-save-dropdown-menu {
    position: absolute; top: 100%; right: 0; margin-top: 4px;
    background: var(--card-bg); border: 1px solid var(--border); border-radius: 8px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.15); min-width: 140px; z-index: 1000;
}
.ie-save-option {
    display: block; width: 100%; text-align: left; border: none; background: transparent;
    padding: 10px 14px; font-size: 0.85rem; color: var(--ink); cursor: pointer;
    font-family: 'Inter', sans-serif;
}
.ie-save-option:hover { background: var(--cream); }

/* --- Save Status --- */
.save-status-saving { color: var(--muted); animation: pulse-fade 1s infinite; }
.save-status-saved { color: var(--success); }
.save-status-failed { color: var(--danger); }
.save-status-dirty { color: var(--muted); }
@keyframes pulse-fade { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }

/* --- Widget Editor Layout (inline mode) --- */
.widget-editor-layout {
    display: grid; grid-template-columns: 370px 1fr; gap: 25px;
    min-height: calc(100vh - 120px);
}
.widget-panel {
    overflow-y: auto; max-height: calc(100vh - 120px);
    background: var(--paper);
    padding: 0;
    position: relative;
    display: flex; flex-direction: column;
}
.panel-main-content { flex: 1; overflow-y: auto; }

/* Column tabs */
.col-tab-bar { border-bottom: 1px solid var(--border); }
.col-tab {
    padding: 8px 14px; border: none; background: transparent;
    font-size: 12px; font-weight: 500; font-family: 'Inter', sans-serif;
    color: var(--muted); cursor: pointer; border-bottom: 2px solid transparent;
    text-transform: uppercase; letter-spacing: 0.5px; transition: all 0.15s;
}
.col-tab:hover { color: var(--ink); }
.col-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.col-tab.dragging { opacity: 0.4; }
.col-tab.drag-over { border-bottom-color: var(--accent); }
.col-tab-add {
    padding: 6px 10px; border: 1px dashed var(--border); border-radius: 4px;
    background: transparent; font-size: 11px; font-family: 'Inter', sans-serif;
    color: var(--muted); cursor: pointer; transition: all 0.15s;
}
.col-tab-add:hover { border-color: var(--accent); color: var(--accent); }
.col-tab-cog {
    padding: 6px 8px; border: none; background: transparent;
    font-size: 16px; color: var(--muted); cursor: pointer; transition: all 0.15s;
}
.col-tab-cog:hover { color: var(--accent); }
.col-tab-cog.active { color: var(--accent); }

/* Bottom action bar */
.panel-action-bar {
    display: flex; gap: 8px; padding: 12px 20px;
    border-top: 1px solid var(--border); background: var(--paper);
    flex-shrink: 0;
}
.panel-action-btn {
    flex: 1; padding: 8px 4px; border-radius: 8px; border: 1px solid var(--border);
    background: var(--paper); color: var(--ink); font-size: 0.78rem; font-weight: 500;
    cursor: pointer; text-align: center; transition: all 0.15s ease;
    font-family: 'Inter', sans-serif;
}
.panel-action-btn:hover { border-color: var(--accent); color: var(--accent); }
.panel-action-reset { color: var(--muted); font-weight: 400; }
.panel-action-reset:hover { color: var(--accent); }
#pc-col-delete-btn:hover { color: #dc2626; }
/* Fixed Book slot (not draggable, not deletable) */
.slot-item.slot-item-fixed {
    background: var(--sand);
}
.slot-grab.slot-grab-disabled {
    opacity: 0 !important;
    pointer-events: none;
    cursor: default;
}
.widget-output {
    overflow-y: auto; max-height: calc(100vh - 120px);
    padding: 0;
}

/* Slot edit button + text preview */
.slot-edit {
    border: none; background: transparent; cursor: pointer;
    color: var(--muted); font-size: 13px; padding: 2px 4px;
    flex-shrink: 0;
}
.slot-edit:hover { color: var(--accent); }
.slot-text-preview {
    flex: 1; font-size: 10px; color: var(--muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    margin: 0 2px; min-width: 0;
}

/* Slot editor — overlays the full editing panel */
.slot-editor-inline {
    position: absolute; top: 0; left: 0; right: 0; bottom: 0;
    background: var(--card-bg); z-index: 100;
    overflow-y: auto;
}
/* Per-slot alignment control */
.se-align-row { margin-bottom: 4px; }
.se-align-label {
    display: block; font-size: 12px; font-weight: 500; color: var(--muted);
    text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 5px;
}
.se-align-icons { display: flex; gap: 4px; }
.se-align-btn {
    width: 32px; height: 28px; display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--border); border-radius: 4px; background: var(--paper);
    color: var(--muted); cursor: pointer; transition: all 0.15s;
}
.se-align-btn:hover { border-color: var(--accent); color: var(--accent); }
.se-align-btn.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.slot-editor-close-btn {
    border: 1px solid var(--border) !important; background: transparent !important;
    color: var(--ink) !important; font-size: 0.82rem !important; padding: 5px 16px !important;
    border-radius: 6px !important; cursor: pointer; transition: all 0.15s;
}
.slot-editor-close-btn:hover {
    background: var(--accent) !important; color: #fff !important; border-color: var(--accent) !important;
}
/* Inline typography section in slot editors */
.se-typo-section {
    margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--border);
}
.se-typo-header {
    display: block;
    font-size: 12px; font-weight: 500; color: var(--muted);
    text-transform: uppercase; letter-spacing: 0.5px;
    padding: 4px 0 8px;
}
.se-typo-body .field { margin-bottom: 10px; }
.se-typo-body .field-row { display: flex; gap: 10px; margin-bottom: 10px; }
.se-typo-body .field-row .field { flex: 1; margin-bottom: 0; }
.se-typo-body .toggle-row { display: flex; align-items: center; gap: 8px; }
.slot-editor-header {
    display: flex; justify-content: space-between; align-items: center;
    padding: 16px 20px 12px; border-bottom: 1px solid var(--border);
}
.slot-editor-header h3 { margin: 0; font-size: 1rem; }
.slot-editor-close {
    border: none; background: transparent; font-size: 1.3rem;
    cursor: pointer; color: var(--muted); padding: 0; line-height: 1;
}
.slot-editor-close:hover { color: var(--ink); }
.slot-editor-body {
    padding: 16px 20px;
}
/* Quill rich text editor overrides */
.slot-editor-inline .ql-toolbar {
    border-color: var(--border); border-radius: 6px 6px 0 0;
    background: var(--sand); padding: 6px 8px;
}
.slot-editor-inline .ql-container {
    border-color: var(--border); border-radius: 0 0 6px 6px;
    font-family: 'Inter', sans-serif; font-size: 13px;
}
.slot-editor-inline .ql-editor {
    min-height: 120px; max-height: 400px; overflow-y: auto;
}
.slot-editor-inline .ql-editor.ql-blank::before {
    font-style: normal; color: var(--muted);
}
.slot-editor-body .field { margin-bottom: 12px; }
/* Labels inherit from base .field label in widget-shared.css (12px, 500, uppercase) */
.slot-editor-body input[type="text"],
.slot-editor-body input[type="number"],
.slot-editor-body textarea,
.slot-editor-body select {
    width: 100%; padding: 8px; border: 1px solid var(--border);
    border-radius: var(--radius); font-size: 13px;
    font-family: 'Inter', sans-serif; background: var(--paper);
    color: var(--ink); box-sizing: border-box;
}
.slot-editor-body textarea { resize: vertical; }
.slot-editor-footer {
    padding: 12px 20px 16px; border-top: 1px solid var(--border);
    display: flex; justify-content: flex-end;
}
.se-section { }
.slot-editor-body .isbn-text-row { margin-bottom: 0; }
.slot-editor-body .isbn-text-header {
    display: flex; align-items: center; justify-content: space-between;
    margin-bottom: 4px;
}
.slot-editor-body .isbn-text-header label {
    margin: 0;
}
.slot-editor-body .revert-btn {
    border: none; background: transparent; cursor: pointer;
    color: var(--muted); padding: 2px; width: 20px; height: 20px;
}
.slot-editor-body .revert-btn:hover { color: var(--accent); }
.slot-editor-body .toggle-row {
    display: flex; align-items: center; gap: 8px;
}

/* --- Admin → Status panel --- */
.status-controls {
    display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap;
    margin-bottom: 1rem;
}
.status-overview {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 0.75rem;
}
.status-pill {
    border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px;
    background: var(--paper);
}
.status-pill-label {
    font-size: 0.75rem; color: var(--muted); text-transform: uppercase;
    letter-spacing: 0.04em; margin-bottom: 4px;
}
.status-pill-value { font-size: 0.9rem; font-weight: 600; color: var(--ink); }
.status-pill-green { border-left: 3px solid #16a34a; }
.status-pill-amber { border-left: 3px solid #d4a843; background: #fef9e7; }
.status-pill-red   { border-left: 3px solid #c44;    background: #fdecec; }

.status-grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 0.75rem;
}
.status-metric {
    border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px;
    background: var(--paper);
}
.status-metric-label {
    font-size: 0.72rem; color: var(--muted); text-transform: uppercase;
    letter-spacing: 0.04em; margin-bottom: 6px;
}
.status-metric-value {
    font-size: 1.05rem; font-weight: 600; color: var(--ink); margin-bottom: 6px;
}
.status-metric-sub {
    font-size: 0.75rem; font-weight: 400; color: var(--muted); margin-left: 4px;
}

.status-state {
    display: inline-block; padding: 2px 8px; border-radius: 10px;
    font-size: 0.7rem; font-weight: 600; letter-spacing: 0.02em;
}
.status-state-alarm                 { background: #fdecec; color: #a02822; }
.status-state-insufficient_data     { background: #fef9e7; color: #8a6d1d; }
.status-state-ok                    { background: #e7fbe9; color: #16713a; }
.status-state-dlq                   { background: #fdecec; color: #a02822; margin-left: 6px; }

.status-bad  { color: #c44; font-weight: 600; }
.status-warn { color: #8a6d1d; font-weight: 600; }

/* ── Now Trending — Clusters sub-tab (views-admin-now-trending.js) ── */

.ntc-cluster-card {
    margin-bottom: 0.75rem; padding: 1rem 1.25rem;
    transition: box-shadow 0.15s ease, transform 0.05s ease;
    cursor: pointer;
}
.ntc-cluster-card:hover { box-shadow: 0 4px 14px rgba(0,0,0,0.08); }
.ntc-cluster-card:active { transform: translateY(1px); }

.ntc-cluster-head {
    display: flex; gap: 1rem; justify-content: space-between; align-items: flex-start;
}
.ntc-cluster-titleblock { flex: 1; min-width: 0; }
.ntc-cluster-title {
    margin: 0 0 6px; font-family: 'DM Serif Display', Georgia, serif;
    font-size: 1.15rem; line-height: 1.2;
}
.ntc-cluster-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.ntc-chip {
    display: inline-block; padding: 2px 8px; border-radius: 10px;
    background: #eee; color: #444; font-size: 0.7rem; font-weight: 500;
}
.ntc-chip-anchor {
    background: #e6f0ff; color: #2a4d8c;
    font-family: 'SFMono-Regular', Menlo, monospace; font-size: 0.65rem;
}
.ntc-cluster-scoreblock {
    display: flex; align-items: center; gap: 10px; flex-shrink: 0;
}
.ntc-cluster-score {
    font-size: 1.5rem !important; font-weight: 700 !important;
    padding: 4px 12px !important; min-width: 56px; text-align: center;
}
.ntc-cluster-desc {
    margin: 8px 0 4px; color: #444; font-size: 0.9rem; line-height: 1.4;
}
.ntc-cluster-meta {
    color: var(--muted); font-size: 0.78rem; margin-top: 6px;
    display: flex; gap: 4px; align-items: center; flex-wrap: wrap;
}
.ntc-meta-sep { color: #ccc; }
.ntc-cluster-actions {
    display: flex; gap: 8px; margin-top: 10px; padding-top: 8px;
    border-top: 1px solid var(--border);
}

/* Components mini-bar — five vertical segments coloured per component */
.ntc-cbar {
    display: flex; gap: 2px; align-items: flex-end;
    height: 36px; width: 70px;
}
.ntc-cbar-seg {
    flex: 1; height: 100%; position: relative;
    background: #f0f0f0; border-radius: 2px;
    display: flex; flex-direction: column; justify-content: flex-end;
    overflow: hidden;
}
.ntc-cbar-fill {
    width: 100%; transition: height 0.3s ease;
}
.ntc-cbar-lbl {
    position: absolute; bottom: -14px; left: 0; right: 0;
    text-align: center; font-size: 0.55rem; color: var(--muted);
    font-family: 'SFMono-Regular', Menlo, monospace;
}

/* Cluster detail modal */
.ntc-detail-overlay { z-index: 9000; }
.ntc-detail-modal {
    background: #fff; max-width: 880px; width: 92%; max-height: 90vh;
    overflow-y: auto; border-radius: 10px; padding: 1.5rem 1.75rem;
    position: relative; box-shadow: 0 10px 40px rgba(0,0,0,0.18);
}
.ntc-detail-close {
    position: absolute; top: 10px; right: 12px; background: none; border: none;
    font-size: 1.6rem; line-height: 1; cursor: pointer; color: #888;
}
.ntc-detail-close:hover { color: #222; }
.ntc-detail-head {
    display: flex; gap: 1rem; justify-content: space-between; align-items: flex-start;
    padding-bottom: 12px; border-bottom: 1px solid var(--border); margin-bottom: 16px;
}
.ntc-detail-title {
    margin: 0 0 6px; font-family: 'DM Serif Display', Georgia, serif; font-size: 1.4rem;
}
.ntc-detail-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.ntc-detail-score-badge {
    font-size: 2.2rem; font-weight: 700; color: #0a5;
    padding: 4px 14px; background: #e7fbe9; border-radius: 10px;
}
.ntc-detail-desc { color: #333; font-size: 0.95rem; line-height: 1.5; margin: 0 0 16px; }
.ntc-detail-h {
    margin: 18px 0 8px; font-size: 0.85rem; font-weight: 600;
    color: #555; text-transform: uppercase; letter-spacing: 0.04em;
}
.ntc-detail-empty { color: var(--muted); font-size: 0.85rem; padding: 12px 0; }

.ntc-detail-campaign {
    background: #f6f6f3; border-radius: 8px; padding: 14px 16px;
    margin-bottom: 8px;
}
.ntc-detail-pitch { font-weight: 600; font-size: 1.05rem; margin-bottom: 4px; }
.ntc-detail-rationale { color: #555; font-size: 0.88rem; margin-bottom: 12px; }
.ntc-detail-asset-grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 10px;
}
.ntc-detail-asset {
    background: #fff; border: 1px solid var(--border); border-radius: 6px;
    padding: 10px 12px;
}
.ntc-detail-asset-h {
    font-size: 0.7rem; font-weight: 600; color: #888; text-transform: uppercase;
    letter-spacing: 0.04em; margin-bottom: 6px;
}
.ntc-detail-asset-sub { font-size: 0.8rem; font-weight: 500; margin-bottom: 4px; }
.ntc-detail-asset-body { font-size: 0.8rem; color: #444; line-height: 1.4; }
.ntc-detail-asset-empty { color: var(--muted); font-size: 0.8rem; }
.ntc-detail-ig { padding-left: 18px; margin: 0; font-size: 0.8rem; color: #444; }
.ntc-detail-ig li { margin-bottom: 3px; }

.ntc-detail-match {
    display: flex; gap: 12px; align-items: flex-start;
    padding: 10px 0; border-bottom: 1px solid #f0f0f0;
}
.ntc-detail-match:last-child { border-bottom: none; }
.ntc-detail-cover {
    width: 50px; height: 75px; object-fit: cover; border: 1px solid var(--border);
    border-radius: 3px; flex-shrink: 0;
}
.ntc-detail-cover-empty {
    background: #f5f5f5; display: flex; align-items: center; justify-content: center;
    color: #ccc; font-size: 0.7rem;
}
.ntc-detail-match-body { flex: 1; min-width: 0; }
.ntc-detail-match-title { font-weight: 600; font-size: 0.92rem; }
.ntc-detail-match-author { color: var(--muted); font-size: 0.8rem; margin-top: 2px; }
.ntc-detail-match-reason { font-size: 0.82rem; color: #444; margin-top: 4px; line-height: 1.4; }
.ntc-detail-match-actions { display: flex; flex-direction: column; gap: 4px; align-items: flex-end; flex-shrink: 0; }

.ntc-detail-signal {
    display: grid; grid-template-columns: 90px 110px 1fr 70px;
    gap: 8px; padding: 6px 0; font-size: 0.78rem; align-items: baseline;
    border-bottom: 1px solid #f7f7f7;
}
.ntc-detail-signal:last-child { border-bottom: none; }
.ntc-detail-signal-kind {
    font-family: 'SFMono-Regular', Menlo, monospace; font-size: 0.7rem;
    color: #2a4d8c; background: #e6f0ff; padding: 1px 6px; border-radius: 3px;
    text-align: center;
}
.ntc-detail-signal-source { color: var(--muted); font-size: 0.75rem; }
.ntc-detail-signal-title { color: #333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ntc-detail-signal-when { color: var(--muted); font-size: 0.72rem; text-align: right; }

@media (max-width: 720px) {
    .ntc-cluster-head { flex-direction: column; }
    .ntc-cluster-scoreblock { align-self: flex-start; }
    .ntc-detail-signal { grid-template-columns: 1fr; gap: 2px; }
}

/* ── Font Browser modal (widgets/font-browser.js) ──────────────────── */
.fbm-modal { width: 80vw; max-width: 1100px; height: 80vh; display: flex; flex-direction: column; padding: 1.25rem; }
.fbm-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.75rem; }
.fbm-toolbar { display: flex; flex-direction: column; gap: 0.55rem; margin-bottom: 0.5rem; }
.fbm-toolbar input[type="search"] { padding: 0.5rem 0.75rem; border: 1px solid var(--border); border-radius: 6px; font-size: 0.9rem; font-family: 'Inter', sans-serif; }
.fbm-categories { display: flex; gap: 1rem; flex-wrap: wrap; font-size: 0.85rem; color: var(--muted); }
.fbm-categories label { display: inline-flex; align-items: center; gap: 0.3rem; cursor: pointer; }
.fbm-themes { display: flex; flex-wrap: wrap; gap: 6px; }
.fbm-theme {
  padding: 4px 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  font-size: 13px;
  font-family: 'Inter', sans-serif;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.fbm-theme:hover { border-color: var(--accent); color: var(--accent); }
.fbm-theme.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.fbm-status { font-size: 0.8rem; color: var(--muted); margin-bottom: 0.5rem; }
.fbm-grid { flex: 1; overflow-y: auto; display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); grid-auto-rows: min-content; align-content: start; gap: 0.75rem; padding-right: 0.25rem; }
.fbm-card { border: 1px solid var(--border); border-radius: 8px; padding: 0.9rem 1rem; background: var(--paper); display: flex; flex-direction: column; gap: 0.4rem; min-height: 130px; }
.fbm-card-head { font-size: 1.5rem; line-height: 1.2; color: var(--ink); }
.fbm-card-sample { font-size: 1rem; line-height: 1.35; color: var(--ink); flex: 1; overflow: hidden; }
.fbm-card-foot { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-top: auto; }
.fbm-card-cat { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
.fbm-card-add { font-size: 0.75rem; padding: 0.25rem 0.6rem; }
.fbm-card-add.fbm-card-added { background: var(--accent-light); color: var(--accent); border-color: var(--accent); }

/* ── Settings → Fonts page (views-fonts.js) ────────────────────────── */
.vf-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }
.vf-card { border: 1px solid var(--border); border-radius: 8px; padding: 1rem 1.1rem; background: var(--paper); display: flex; flex-direction: column; gap: 0.4rem; min-height: 140px; }
.vf-card-head { font-size: 1.6rem; line-height: 1.2; color: var(--ink); }
.vf-card-sample { font-size: 1rem; line-height: 1.35; color: var(--ink); flex: 1; overflow: hidden; }
.vf-card-foot { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-top: auto; }
.vf-card-cat { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
/* Source badge: was the family added from Google or uploaded? */
.vf-badge {
    display: inline-block;
    vertical-align: middle;
    font-family: 'Inter', sans-serif;
    font-size: 0.62rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding: 1px 7px;
    border-radius: 9px;
    border: 1px solid var(--border);
    color: var(--muted);
    margin-left: 6px;
}
.vf-badge-uploaded { border-color: var(--accent); color: var(--accent); }
/* Category chip shares the badge look; badges sit together in the foot row. */
.vf-card-foot .vf-badge { margin-left: 0; }
.vf-card-catchip { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.vf-card-del { padding: 0.3rem 0.5rem; line-height: 0; }

/* ── Book Cover → Text accordion → AI Font Pairing (multi-editor-engine.js) ── */
.bcfp-intro { font-size: 0.8rem; color: var(--muted); margin-bottom: 0.65rem; line-height: 1.4; }
.bcfp-run { width: 100%; }
.bcfp-run:disabled { opacity: 0.5; cursor: not-allowed; }
.bcfp-hint { font-size: 0.72rem; color: var(--muted); margin-top: 0.5rem; font-style: italic; }
.bcfp-results { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.85rem; }
.bcfp-results:empty { margin-top: 0; }
.bcfp-empty { font-size: 0.8rem; color: var(--muted); padding: 0.5rem 0; font-style: italic; }
.bcfp-card { border: 1px solid var(--border); border-radius: 8px; padding: 0.7rem 0.85rem; background: var(--paper); display: flex; flex-direction: column; gap: 0.3rem; max-width: 400px; }
.bcfp-card-head { font-size: 1.15rem; line-height: 1.2; color: var(--ink); }
.bcfp-card-sample { font-size: 0.85rem; line-height: 1.35; color: var(--ink); }
/* Stacked + left-aligned: reason on top, +Add button on its own row below. */
.bcfp-card-foot { display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem; margin-top: 0.2rem; }
.bcfp-card-reason { font-size: 0.7rem; font-style: italic; color: var(--muted); line-height: 1.35; }
.bcfp-card-add { font-size: 0.72rem; padding: 0.25rem 0.55rem; flex-shrink: 0; align-self: flex-start; }
.bcfp-card-add.bcfp-card-added { background: var(--accent-light); color: var(--accent); border-color: var(--accent); cursor: default; }

/* ─────────────────────────────────────────────────────────────
 * Campaigns page
 * Hero gains a centre slot (search input). Lozenge cards use a
 * book-cover layout with a hover overlay mirroring the inklet-gallery-card
 * idiom — dark mute + centred "View" button + bottom-aligned title/sub.
 * ───────────────────────────────────────────────────────────── */
.page-hero-center {
    flex: 1 1 auto;
    display: flex;
    justify-content: center;
    align-items: center;
    min-width: 0;       /* allow the flex item to shrink below input min-content */
    padding: 0 1rem;
}
.hero-search {
    width: 100%; max-width: 380px;
    border-radius: 20px;
    background: rgba(255,255,255,0.15);
    border: 1px solid rgba(255,255,255,0.4);
    color: #fff;
    padding: 6px 14px;
    font-family: inherit; font-size: 0.85rem;
    -webkit-appearance: none;
    appearance: none;
}
.hero-search::placeholder { color: rgba(255,255,255,0.7); }
.hero-search:hover  { background: rgba(255,255,255,0.22); }
.hero-search:focus  { outline: none; background: rgba(255,255,255,0.28); border-color: rgba(255,255,255,0.7); }
/* Hide the WebKit clear-X (we leave the user to backspace; matches our other
 * search inputs) */
.hero-search::-webkit-search-cancel-button { display: none; }

.campaign-grid {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 1rem;
}
@media (max-width: 1200px) { .campaign-grid { grid-template-columns: repeat(4, 1fr); } }
@media (max-width: 900px)  { .campaign-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 600px)  { .campaign-grid { grid-template-columns: repeat(2, 1fr); } }

.campaign-book-card {
    position: relative;
    border: 1px solid var(--border);
    border-radius: 10px;
    overflow: hidden;
    background: var(--paper);
    cursor: pointer;
    transition: box-shadow 0.15s, transform 0.15s;
}
.campaign-book-card:hover { box-shadow: 0 4px 16px rgba(0,0,0,0.1); transform: translateY(-2px); }

.campaign-book-cover {
    aspect-ratio: 2 / 3;
    background: #f1f5f9;
    position: relative;
    overflow: hidden;
}
.campaign-book-cover img {
    width: 100%; height: 100%;
    object-fit: cover; object-position: center;
    display: block;
    opacity: 0;
    transition: opacity 0.2s;
}
.campaign-book-cover img.loaded { opacity: 1; }
.campaign-book-cover-empty {
    width: 100%; height: 100%;
    display: flex; align-items: center; justify-content: center;
    color: #94a3b8; font-size: 0.75rem;
}

.campaign-book-meta { padding: 10px 12px; }
.campaign-book-title {
    font-weight: 600;
    font-size: 0.82rem;
    line-height: 1.25;
    color: var(--ink);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
    min-height: calc(0.82rem * 1.25 * 2);   /* reserve 2 lines so cards align */
}
.campaign-book-sub  { font-size: 0.7rem;  color: var(--muted); margin-top: 4px; }
.campaign-book-date { font-size: 0.7rem;  color: var(--muted); margin-top: 2px; }

/* Hover overlay sits INSIDE .campaign-book-cover, so inset:0 confines it
 * to the cover box — the meta footer below remains readable on hover. */
.campaign-book-hover {
    position: absolute; inset: 0;
    background: rgba(20,20,30,0.62);
    opacity: 0;
    transition: opacity 0.15s;
    pointer-events: none;
}
.campaign-book-card:hover .campaign-book-hover { opacity: 1; pointer-events: auto; }

.campaign-book-view-btn {
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    border-radius: 20px;
    background: rgba(255,255,255,0.15);
    border: 1px solid rgba(255,255,255,0.55);
    color: #fff;
    padding: 6px 22px;
    font-family: inherit; font-size: 0.85rem; font-weight: 500;
    cursor: pointer;
    backdrop-filter: blur(2px);
    transition: background 0.15s, border-color 0.15s;
}
.campaign-book-view-btn:hover { background: rgba(255,255,255,0.28); border-color: #fff; }

/* ─────────────────────────────────────────────────────────────
 * Campaigns per-book modal — channel-group navigator
 * Clone of .tbm-modal at 95vw × 95vh, with a left sidebar listing
 * channel groups and a right placeholder pane (template inklets land
 * here next iteration). lib-modal.js owns lifecycle (Esc + backdrop).
 * ───────────────────────────────────────────────────────────── */
.campaign-modal {
    position: fixed; inset: 0; z-index: 99999;
    background: rgba(26,26,46,0.45);
    backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
    display: flex; align-items: center; justify-content: center;
    animation: fadeIn 0.2s ease;
}
.campaign-modal-inner {
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    width: 95vw; height: 95vh;
    overflow: hidden;
    display: flex; flex-direction: column;
}

/* Top bar — 1.5-line darker-grey strip. Title-pair sits left, X right. */
.campaign-modal-top-bar {
    background: #d4d4d4;
    padding: 6px 20px;
    display: flex; justify-content: space-between; align-items: center;
    flex-shrink: 0;
    min-height: calc(2.25rem + 5px);   /* +5px taller than the original 2.25rem */
}
.campaign-modal-top-bar .section-title-pair { display: flex; align-items: baseline; gap: 12px; }
.campaign-modal-top-bar .section-title    { font-family:'DM Serif Display', serif; font-size: 1.4rem; color: var(--ink); line-height: 1.2; }
.campaign-modal-top-bar .section-subtitle { font-family:'Inter', sans-serif;       font-size: 0.9rem; color: #64748b; font-weight: 400; line-height: 1.2; }
.campaign-modal-top-bar .modal-close-x    { background: none; border: none; font-size: 1.5rem; cursor: pointer; color: var(--muted); padding: 0 4px; line-height: 1; }
.campaign-modal-top-bar .modal-close-x:hover { color: var(--ink); }

/* Body — flex row: fixed-width sidebar + flexible right pane. */
.campaign-modal-body { display: flex; flex: 1; min-height: 0; }
.campaign-modal-sidebar {
    width: 240px; flex-shrink: 0;
    border-right: 1px solid var(--border);
    background: var(--paper);
    overflow-y: auto;
    padding: 12px 8px;
    display: flex; flex-direction: column; gap: 4px;
}
.campaign-modal-sidebar-loading,
.campaign-modal-sidebar-empty,
.campaign-modal-sidebar-error { padding: 12px; font-size: 0.85rem; color: var(--muted); }
.campaign-modal-pane { flex: 1; overflow: auto; }

/* Sidebar items — icon + name, with three modifier states:
 *   .is-active    → full colour (default; regular weight)
 *   .is-inactive  → muted text, dimmed icon
 *   .is-selected  → accent outline + bold weight
 * Only the selected group is bold; non-selected active groups are regular.
 */
.campaign-modal-group {
    display: flex; align-items: center; gap: 10px;
    padding: 10px 12px;
    border: 2px solid transparent;
    border-radius: 8px;
    background: none;
    text-align: left;
    cursor: pointer;
    color: var(--ink);
    font-family: 'Inter', sans-serif; font-size: 0.95rem; font-weight: 400;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.campaign-modal-group:hover { background: rgba(175,40,96,0.04); }
.campaign-modal-group.is-inactive { color: var(--muted); }
.campaign-modal-group.is-inactive .campaign-modal-group-icon { opacity: 0.45; }
.campaign-modal-group.is-selected { border-color: var(--accent); font-weight: 600; }
.campaign-modal-group-icon {
    width: 24px; height: 24px; flex-shrink: 0;
    display: inline-flex; align-items: center; justify-content: center;
    color: var(--ink);
    font-size: 22px; line-height: 1;
}
.campaign-modal-group-icon svg { width: 24px; height: 24px; display: block; }
.campaign-modal-group.is-inactive .campaign-modal-group-icon svg { color: var(--muted); }
.campaign-modal-group-name { line-height: 1.2; }

/* ─────────────────────────────────────────────────────────────
 * Campaigns modal — right pane (template grid)
 * Populated when a channel group is selected. 3–4 cards per row
 * via auto-fill; each card is a square thumb with channel name +
 * summary below, hover surfaces the accent on the border.
 * ───────────────────────────────────────────────────────────── */
.campaign-pane-loading,
.campaign-pane-empty,
.campaign-pane-error { padding: 24px; color: var(--muted); font-size: 0.9rem; }
.campaign-pane-error { color: #c0392b; }

.campaign-pane-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 1rem;
    padding: 20px;
}

.campaign-pane-card {
    display: flex; flex-direction: column;
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    text-align: center;
    font-family: inherit;
    color: var(--ink);
}
/* Suppress the default <button> outline so we don't get a blue ring around
 * the whole card when focus returns after closing the zoom modal via Esc
 * (X click moves focus to the X button — which then disappears with the
 * modal — so the ring only appears on the Esc path). Keep keyboard a11y by
 * mirroring the hover state on :focus-visible. */
.campaign-pane-card:focus { outline: none; }
.campaign-pane-card:focus-visible .campaign-pane-thumb {
    border-color: var(--accent);
}
.campaign-pane-thumb {
    aspect-ratio: 1 / 1;
    border: 2px solid var(--border);
    border-radius: 12px;
    background: var(--paper);
    position: relative;
    overflow: hidden;
    box-sizing: border-box;
    transition: border-color 0.12s;
}
.campaign-pane-card:hover .campaign-pane-thumb {
    border-color: var(--accent);
}
/* Loading + empty placeholders fill the whole thumb box. */
.campaign-pane-thumb-loading,
.campaign-pane-thumb-empty {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    color: var(--muted);
    font-size: 0.85rem;
}
/* Scaler: positioned absolutely by applyRenderedHtml — width/height set to
 * the channel's natural canvas size; transform/left/top set inline. */
.campaign-pane-thumb-scaler {
    position: absolute;
    transform-origin: 0 0;
    pointer-events: none;
    /* width, height set inline; transform, left, top set after measure. */
}
/* Fallback <img> path (when /inklets/batch fails or no book context). */
.campaign-pane-thumb img {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: contain;
    padding: 16px;
    box-sizing: border-box;
    display: block;
}
.campaign-pane-name {
    margin-top: 10px;
    font-family: 'Inter', sans-serif;
    font-size: 0.95rem;
    font-weight: 500;
    line-height: 1.2;
}
.campaign-pane-sub {
    margin-top: 4px;
    font-family: 'Inter', sans-serif;
    font-size: 0.8rem;
    color: var(--muted);
    line-height: 1.3;
}

/* ─────────────────────────────────────────────────────────────
 * Zoom modal body — reuses the .campaign-modal* outer shell so
 * the chrome (95vw × 95vh, grey top bar, X) is identical to the
 * parent navigator. Only the body differs: centred canvas wrap
 * with a light scrim so the inklet's white canvas stands out.
 * ───────────────────────────────────────────────────────────── */
.campaign-zoom-body {
    flex: 1;
    overflow: auto;
    background: #f5f5f5;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: 24px;
}
.campaign-zoom-canvas-wrap {
    background: #fff;
    box-shadow: 0 4px 24px rgba(0,0,0,0.12);
    max-width: 100%;
}
.campaign-zoom-canvas-wrap > * { display: block; }

/* (AI Inklet modal + Schedule modal styles deleted Phase 33 alongside the
 * ISBN Emporium "Automate" section. The .ai-inklet-modal-* + the lone
 * .schedule-frequency-detail rule lived here until the widget files were
 * removed; git history preserves them.) */

/* ─────────────────────────────────────────────────────────────
 * AI Controls — Phase 31
 * Tabbed admin page for editing AI prompts stored in ai_prompts table.
 * Replaces the old "AI Assets" search/grid layout (deleted with that
 * feature in the same phase).
 * ───────────────────────────────────────────────────────────── */
.aic-tab-bar { margin-bottom: 1rem; margin-top: -0.25rem; }

.ai-prompts-table { width: 100%; }
.ai-prompts-table th,
.ai-prompts-table td { padding: 0.6rem 0.75rem; vertical-align: top; }
.ai-prompts-table tbody tr { border-top: 1px solid var(--border); }
.ai-prompts-table .btn-sm { margin-left: 4px; }

/* Icon-only action buttons in the AI Prompts table. Square hit-targets,
   tight padding, feather SVGs constrained to 14px. */
.aic-icon-btn {
    padding: 4px 6px;
    line-height: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.aic-icon-btn svg {
    width: 14px;
    height: 14px;
    display: block;
}

/* Inline code badge under the name in the table — shows the immutable
   lookup identifier wired into Lambda code. */
.ai-prompt-code-badge {
    display: inline-block;
    margin-left: 8px;
    padding: 1px 6px;
    background: #f1f5f9;
    border: 1px solid var(--border);
    border-radius: 4px;
    font-family: 'SF Mono', Menlo, 'Courier New', monospace;
    font-size: 0.72rem;
    color: var(--muted);
    vertical-align: middle;
}

/* Placeholder token preview under the name in the AI Placeholders table
   — visually flags the curator-typed token they can paste into AI prompt
   bodies inside `{` `}` braces. */
.aic-placeholder-token {
    display: inline-block;
    margin-top: 2px;
    font-family: 'SF Mono', Menlo, 'Courier New', monospace;
    font-size: 0.8rem;
    color: var(--accent);
}

/* Small pill flagging a built-in placeholder row in the Placeholders
   tab. Built-in rows can be edited (metadata) or archived but never
   deleted — the pill signals that visually. */
.aic-builtin-pill {
    display: inline-block;
    margin-left: 8px;
    padding: 1px 6px;
    background: var(--accent-light);
    color: var(--accent);
    border-radius: 4px;
    font-size: 0.7rem;
    font-weight: 600;
    vertical-align: middle;
}

/* Read-only Code field in Edit modal — visually distinct from editable
   fields so users instinctively know not to try changing it. */
.ai-prompt-input[readonly],
.ai-prompt-input:disabled {
    background: #f8fafc;
    color: var(--muted);
    cursor: not-allowed;
}

/* Form fields inside the Add/Edit modal */
.ai-prompt-field { margin-bottom: 1.25rem; }
/* Paired-fields container — Channel Group + Channel, Model + Env Var */
.ai-prompt-field-row {
    display: flex;
    gap: 1rem;
    margin-bottom: 1.25rem;
}
.ai-prompt-field-row .ai-prompt-field { margin-bottom: 0; }
.ai-prompt-field label {
    display: block;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--ink);
    margin-bottom: 0.35rem;
}
.ai-prompt-field .req { color: #c0392b; margin-left: 2px; }
.ai-prompt-field .ai-prompt-optional {
    font-size: 0.7rem;
    font-weight: normal;
    color: var(--muted);
    margin-left: 4px;
    text-transform: lowercase;
}
/* Greyed-out state for the System Prompt field when the API URL is an OpenAI
   image endpoint (/v1/images/…) — the field is saved but not sent there, so
   we visually de-emphasise it and surface an inline "ignored" tag + hint. The
   textarea stays editable (so switching to a chat endpoint keeps the value). */
.ai-prompt-field-muted label { color: var(--muted); }
.ai-prompt-field-muted .ai-prompt-ignored-tag { color: #c0392b; text-transform: none; }
.ai-prompt-input-muted {
    opacity: 0.55;
    background: #f6f6f8;
}
.ai-prompt-input {
    width: 100%;
    box-sizing: border-box;
    padding: 0.55rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    font-family: 'Inter', sans-serif;
    font-size: 0.92rem;
    background: #fff;
}
.ai-prompt-input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(99,102,241,0.12);
}
.ai-prompt-mono {
    font-family: 'SF Mono', Menlo, 'Courier New', monospace;
    font-size: 0.82rem;
    line-height: 1.45;
}
.ai-prompt-hint {
    font-size: 0.75rem;
    color: var(--muted);
    margin-top: 4px;
}
.ai-prompt-readonly {
    background: #f8fafc;
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 0.6rem 0.75rem;
    font-family: 'SF Mono', Menlo, 'Courier New', monospace;
    font-size: 0.78rem;
    line-height: 1.45;
    white-space: pre-wrap;
    word-break: break-word;
    margin: 0;
    max-height: 280px;
    overflow-y: auto;
}

/* History modal — one row per revision */
.aic-revision-row {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    padding: 0.75rem 0;
    border-bottom: 1px solid var(--border);
}
.aic-revision-row:last-child { border-bottom: none; }

/* Overlay shell + inner card for the AI Controls modals. Mirrors the
   .modal-overlay / .tbm-modal pattern used elsewhere — fixed full-screen
   backdrop + centred card with comfortable max-width for textareas. */
.aic-modal {
    position: fixed; inset: 0; z-index: 99999;
    background: rgba(26,26,46,0.45);
    backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
    display: flex; align-items: flex-start; justify-content: center;
    padding: 4vh 1rem 6vh;
    overflow-y: auto;
    animation: fadeIn 0.2s ease;
}
.aic-modal-inner {
    width: 100%;
    max-width: 760px;
    background: var(--card-bg);
    border-radius: 12px;
    box-shadow: 0 12px 48px rgba(0,0,0,0.18);
    overflow: hidden;
}

/* ── Phase 17 — Floating Layers panel (multi-editor canvas mode) ── */
.me-layers-panel {
    position: fixed;
    top: 96px; right: 24px;
    width: 260px;
    max-height: calc(100vh - 140px);
    background: var(--paper);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 8px 32px rgba(0,0,0,0.16);
    display: flex; flex-direction: column;
    /* Above the 95% editor modal (.ie-modal-overlay, 100002) so the floating
       Layers tool is usable while editing; below the editor's child dialogs
       (100010) so a modal dialog still covers it. */
    z-index: 100005;
    font-family: 'Inter', sans-serif;
}
.me-layers-titlebar {
    display: flex; align-items: center;
    justify-content: space-between;
    padding: 8px 12px;
    background: #f4f4f6;
    border-bottom: 1px solid var(--border);
    border-radius: 8px 8px 0 0;
    cursor: move;
    user-select: none;
}
.me-layers-title { font-size: 13px; font-weight: 600; color: var(--ink); }
.me-layers-close {
    background: none; border: none;
    color: var(--muted);
    font-size: 1.5rem; line-height: 1;
    cursor: pointer; padding: 0 4px;
}
.me-layers-close:hover { color: var(--ink); }

.me-layers-list {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 4px 0;
}
.me-layers-empty {
    padding: 16px; color: var(--muted);
    font-size: 12px; text-align: center;
}

.me-layers-row {
    display: flex; align-items: center; gap: 8px;
    padding: 4px 10px;
    cursor: pointer;
    border-top: 1px solid transparent;
    border-bottom: 1px solid transparent;
}
.me-layers-row:hover { background: rgba(0,0,0,0.03); }
.me-layers-row.is-selected { background: rgba(175,40,96,0.08); }
.me-layers-row.is-hidden .me-layers-label,
.me-layers-row.is-hidden .me-layers-thumb { opacity: 0.45; }
.me-layers-row.is-dragging { opacity: 0.5; }
.me-layers-row.me-layers-drag-over { border-top-color: var(--accent); }

.me-layers-eye {
    background: none; border: none; padding: 2px;
    color: var(--ink); cursor: pointer;
    display: flex; align-items: center; justify-content: center;
}
.me-layers-eye:hover { color: var(--accent); }
.me-layers-row.is-hidden .me-layers-eye { color: var(--muted); }

.me-layers-thumb {
    width: 36px; height: 36px;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--paper);
    flex-shrink: 0;
    overflow: hidden;
    position: relative;
    display: flex; align-items: center; justify-content: center;
}
.me-layers-thumb-t {
    font-family: 'Libre Baskerville', Georgia, serif;
    font-weight: 700;
    color: var(--ink);
    font-size: 18px;
}
.me-layers-thumb-glyph {
    font-family: 'Inter', sans-serif;
    font-weight: 600;
    color: var(--muted);
    font-size: 14px;
    text-transform: uppercase;
}
.me-layers-thumb-img {
    /* Phase 17: image thumb — centre-crop the underlying cover/photo/image
     * into the 36×36 box. Filling the box keeps every row visually balanced
     * regardless of the source aspect ratio. */
    width: 100%; height: 100%;
    object-fit: cover;
    display: block;
}
.me-layers-thumb-inner {
    transform: scale(0.18);
    transform-origin: top left;
    width: 200px; height: 200px;
    pointer-events: none;
}

.me-layers-label {
    font-size: 12px; color: var(--ink);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    flex: 1 1 auto;
}

/* ── Channel name + summary in the in-canvas switcher bar ── */
/* Both name + summary use the same muted colour for visual harmony;
 * the bold weight on the name still gives it visual priority. */
.me-channel-name {
    font-weight: 400;
    color: var(--muted);
}
.me-channel-name strong {
    font-weight: 600;
    color: var(--muted);
}
.me-channel-summary {
    color: var(--muted);
    margin-left: 6px;
    font-weight: 400;
}

/* ── Image-block: Flip row + crop icon + icon buttons ── */
.me-image-flip-row {
    display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
    margin-top: 10px;
}
.me-image-flip-row > label {
    margin: 0; font-size: 12px;
    color: var(--muted);
}
.me-image-flip-buttons {
    display: flex; gap: 6px;
}
.me-btn-icon {
    background: var(--paper);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 6px;
    cursor: pointer;
    color: var(--ink);
    display: inline-flex; align-items: center; justify-content: center;
    line-height: 0;
}
.me-btn-icon:hover { border-color: var(--accent); color: var(--accent); }
.me-btn-icon.is-active {
    background: rgba(175,40,96,0.08);
    border-color: var(--accent);
    color: var(--accent);
}

/* (Image crop icon moved into .me-canvas-block-tools — no standalone CSS) */

/* ── Crop modal shell + preview + sliders ── */
.me-image-crop-overlay {
    position: fixed; inset: 0;
    background: rgba(0,0,0,0.5);
    z-index: 9500;
    display: flex; align-items: center; justify-content: center;
}
.me-image-crop-modal {
    background: var(--paper);
    border-radius: 8px;
    box-shadow: 0 12px 40px rgba(0,0,0,0.24);
    width: 480px; max-width: 90vw;
    max-height: 90vh;
    display: flex; flex-direction: column;
}
.me-image-crop-top-bar {
    display: flex; align-items: center; justify-content: space-between;
    padding: 12px 20px;
    border-bottom: 1px solid var(--border);
    font-family: 'DM Serif Display', serif;
    font-size: 1rem;
    color: var(--ink);
}
.me-image-crop-top-bar .modal-close-x {
    background: none; border: none;
    color: var(--muted);
    font-size: 1.5rem; line-height: 1;
    cursor: pointer; padding: 0 4px;
}
.me-image-crop-top-bar .modal-close-x:hover { color: var(--ink); }
.me-image-crop-body {
    padding: 16px 20px;
    overflow-y: auto;
    flex: 1 1 auto;
}
.me-image-crop-preview {
    width: 100%;
    aspect-ratio: 1 / 1;
    border: 1px solid var(--border);
    border-radius: 6px;
    overflow: hidden;
    background: #f4f4f6;
    margin-bottom: 16px;
}
.me-image-crop-preview img {
    width: 100%; height: 100%;
    display: block;
}
.me-image-crop-sliders .slider-row {
    margin-bottom: 10px;
}
.me-image-crop-sliders label {
    display: block;
    font-size: 12px;
    color: var(--muted);
    margin-bottom: 4px;
}
.me-image-crop-sliders input[type="range"] {
    width: 100%;
}
.me-image-crop-foot {
    display: flex; justify-content: flex-end; gap: 12px;
    padding: 12px 20px;
    border-top: 1px solid var(--border);
}

/* ── Phase 21 — Shared Position accordion (rotation + flip + alignment) ── */
.me-position-row {
    display: flex; align-items: flex-end; gap: 16px;
    margin-bottom: 12px;
}
.me-position-row > label {
    font-size: 12px;
    color: var(--muted);
    flex-shrink: 0;
    margin: 0;
}
/* Row variant: label stacks above its content (Flip / H Align / V Align rows). */
.me-position-row-stack {
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
}
.me-position-rotation-row {
    display: grid;
    grid-template-columns: 2fr 1fr;
    gap: 12px;
    align-items: end;
    margin-bottom: 8px;
}
/* Number input + Rotate-90° quick button sit side-by-side under the
 * "Rotation (deg)" label. The input takes the remaining width; the
 * button sizes to its text (white-space:nowrap on the button itself). */
.me-position-rotation-input-group {
    display: flex;
    align-items: stretch;
    gap: 6px;
}
.me-position-rotation-input-group input[type="number"] {
    flex: 1 1 auto;
    min-width: 0;
}
/* Rotate 90° button sits next to the CW/CCW icons in the Direction
 * column. Match .me-btn-icon's intrinsic height (18px SVG + 6px×2
 * padding = 32px box including the 1px border on each side) so the
 * three controls line up perfectly on the same horizontal row. */
.me-position-button-inline {
    background: var(--paper);
    border: 1px solid var(--border);
    border-radius: 4px;
    cursor: pointer;
    font-size: 12px;
    font-family: 'Inter', sans-serif;
    color: var(--ink);
    padding: 0 10px;
    height: 32px;
    line-height: 1;
    white-space: nowrap;
    transition: border-color 0.12s, color 0.12s;
}
.me-position-button-inline:hover {
    border-color: var(--accent);
    color: var(--accent);
}
.me-position-icon-group {
    display: inline-flex; gap: 6px;
}
.me-position-button-fullwidth {
    width: 100%; height: 32px;
    background: #f5f1ea;
    border: 1px solid var(--border);
    border-radius: 6px;
    cursor: pointer;
    font-size: 13px;
    font-family: 'Inter', sans-serif;
    color: var(--ink);
    margin-bottom: 12px;
    transition: border-color 0.12s, color 0.12s;
}
.me-position-button-fullwidth:hover {
    border-color: var(--accent);
    color: var(--accent);
}
/* Alignment buttons inherit .me-btn-icon styling (outline accent on hover /
 * active) from earlier phases — see the Flip-icon rules. */
.me-position-align-btn { /* nothing extra — pure marker class for the wireup selector */ }

/* Ingestion Runs — super-admin upload toolbar (sits below the hero) */
.runs-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
    margin: 0.75rem 0 1rem;
}
.runs-toolbar-left,
.runs-toolbar-right {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.runs-covers-status {
    font-size: 0.85rem;
    color: var(--muted);
    margin-right: 0.25rem;
}
.runs-toolbar-hint {
    font-size: 0.8rem;
    color: var(--muted);
}

/* Ingestion Runs — Pending CSV conversions card (super-admin) */
.csv-conversions-card { margin-bottom: 1.5rem; }
.csv-conversions-title {
    font-family: 'DM Serif Display', serif;
    margin: 0 0 0.75rem;
    font-size: 1.1rem;
}
.csv-conversion-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--border);
}
.csv-conversion-row:last-child { border-bottom: none; }
.csv-conversion-meta {
    font-size: 0.85rem;
    color: var(--muted);
    margin-top: 0.15rem;
}
.csv-conversion-actions {
    display: flex;
    gap: 0.5rem;
    flex-shrink: 0;
}
.csv-conversion-status-pending  { color: #c98a06; font-weight: 600; margin-left: 0.5rem; }
.csv-conversion-status-failed   { color: #c0392b; font-weight: 600; margin-left: 0.5rem; }
.csv-conversion-status-rejected { color: var(--muted); font-weight: 600; margin-left: 0.5rem; }
.csv-conversion-status-approved { color: #2e7d32; font-weight: 600; margin-left: 0.5rem; }

/* Skipped-pages details toggle on conversion rows */
.conv-skip-details { margin-top: 0.35rem; font-size: 0.82rem; }
.conv-skip-details summary { cursor: pointer; color: #c98a06; }
.conv-skip-details ul { margin: 0.35rem 0 0; padding-left: 1.25rem; color: var(--muted); }
.conv-skip-details li { margin-bottom: 0.2rem; }

/* Conversion Review modal (readable pre-approve summary) */
.conv-review-modal {
    max-width: 760px;
    width: min(760px, 94vw);
    display: flex;
    flex-direction: column;
    max-height: 86vh;
}
.conv-review-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 0.75rem;
}
.conv-review-head h3 { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.conv-review-body { overflow-y: auto; flex: 1; min-height: 0; padding-right: 0.25rem; }
.conv-review-note { font-size: 0.85rem; color: var(--muted); margin-bottom: 0.75rem; }
.conv-review-card {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 0.9rem 1rem;
    margin-bottom: 0.9rem;
}
.conv-review-title { font-weight: 600; font-size: 1.02rem; margin-bottom: 0.5rem; }
.conv-review-subtitle { font-weight: 400; color: var(--muted); }
.conv-review-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
    gap: 0.25rem 1rem;
    margin-bottom: 0.5rem;
}
.conv-review-kv { font-size: 0.85rem; }
.conv-review-k {
    text-transform: uppercase;
    font-size: 0.7rem;
    letter-spacing: 0.5px;
    color: var(--muted);
    margin-right: 0.4rem;
}
.conv-review-contrib { font-size: 0.88rem; margin-bottom: 0.25rem; }
.conv-review-chips { display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 0.4rem 0; }
.conv-review-chip {
    font-size: 0.75rem;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 0.1rem 0.6rem;
}
.conv-review-desc, .conv-review-expand p {
    font-size: 0.88rem;
    line-height: 1.45;
    margin: 0.4rem 0;
    white-space: pre-wrap;
}
.conv-review-expand { margin: 0.35rem 0; font-size: 0.85rem; }
.conv-review-expand summary { cursor: pointer; color: var(--accent); }
.conv-review-quote {
    margin: 0.5rem 0;
    padding: 0.5rem 0.85rem;
    border-left: 3px solid var(--border);
    font-size: 0.88rem;
    font-style: italic;
    color: var(--text);
}
.conv-review-quote footer { font-style: normal; color: var(--muted); margin-top: 0.3rem; font-size: 0.82rem; }
.conv-review-points { margin: 0.25rem 0 0.4rem; padding-left: 1.25rem; font-size: 0.88rem; }
.conv-review-actions { margin-top: 1rem; flex-shrink: 0; }

/* ── Rights Catalogs ─────────────────────────────────────────── */
.rc-list-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 1rem;
}
.rc-list-head h2 { margin: 0; font-family: 'DM Serif Display', serif; }
.rc-list-head h2 a { color: inherit; text-decoration: none; }
.rc-list-head h2 a:hover { text-decoration: underline; }
.rc-status { font-weight: 600; font-size: 0.85rem; text-transform: capitalize; }
.rc-status-ready { color: #2e7d32; }
.rc-status-extracting { color: #c98a06; }
.rc-status-failed { color: #c0392b; }
.rc-status-draft { color: var(--muted); }
.rc-progress {
    height: 10px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 999px;
    overflow: hidden;
}
.rc-progress-bar { height: 100%; background: var(--accent); transition: width 0.4s; }

/* Editor workspace */
/* Fill the viewport: .content pads 2rem top + bottom (64px) — 68px leaves
   4px of breathing room, the rest goes to the canvas (was -90px). */
.rc-editor { display: flex; flex-direction: column; height: calc(100vh - 68px); }
.rc-editor-head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding-bottom: 0.75rem;
    flex-wrap: wrap;
}
.rc-back { color: var(--muted); text-decoration: none; white-space: nowrap; }
.rc-back:hover { color: var(--text); }
.rc-name-input {
    font-size: 1.05rem;
    font-weight: 600;
    border: 1px solid transparent;
    background: transparent;
    padding: 0.25rem 0.4rem;
    min-width: 220px;
}
.rc-name-input:hover, .rc-name-input:focus { border-color: var(--border); background: #fff; }
.rc-save-status { font-size: 0.8rem; color: var(--muted); white-space: nowrap; }
.rc-pagecount { font-size: 0.85rem; color: var(--muted); white-space: nowrap; }
.rc-editor-main { flex: 1; min-height: 0; display: flex; gap: 0.75rem; }
.rc-canvas-outer {
    flex: 1;
    min-width: 0;
    overflow-y: auto;
    overflow-x: auto;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    gap: 0.4rem;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 1rem 0.25rem;
}
/* Emporium-style zoom controls, top-left over the canvas area. */
.rc-editor-main { position: relative; }
.rc-zoom-stack {
    position: absolute;
    top: 10px;
    left: 10px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    z-index: 6;
}
.rc-canvas-scale {
    transform-origin: top left;
    flex: 0 0 auto;
    box-shadow: 0 2px 14px rgba(0,0,0,0.18);
}
.rc-nav-arrow {
    position: sticky;
    top: 45%;
    border: 1px solid var(--border);
    background: #fff;
    border-radius: 50%;
    width: 38px;
    height: 38px;
    font-size: 1.3rem;
    cursor: pointer;
    flex: 0 0 auto;
}
.rc-nav-arrow:hover { background: var(--bg); }
.rc-field:focus {
    outline: 2px solid rgba(74,144,226,0.55);
    outline-offset: 1px;
    border-radius: 2px;
}
.rc-slot.rc-overflow { outline: 2px solid rgba(192,57,43,0.65); outline-offset: -2px; }
.rc-overflow-badge {
    position: absolute;
    right: 4px;
    bottom: 4px;
    background: #c0392b;
    color: #fff;
    font-size: 8pt;
    font-family: 'Inter', sans-serif;
    padding: 1px 7px;
    border-radius: 3px;
    pointer-events: none;
}
/* Sits on .rcpage just above its slot (top margin for the first book, over
   the middle divider for the second) so the controls never cover page text.
   Shown via .is-visible — chrome is no longer a slot child, so :hover on the
   slot cannot reach it. */
.rc-chrome {
    position: absolute;
    right: 6px;
    display: none;
    gap: 4px;
    font-family: 'Inter', sans-serif;
    z-index: 5;
}
.rc-chrome.is-visible { display: flex; }
.rc-chrome-btn, .rc-chrome-sel {
    font-size: 8.5pt;
    border: 1px solid var(--border);
    background: rgba(255,255,255,0.95);
    border-radius: 4px;
    padding: 2px 7px;
    cursor: pointer;
}
.rc-chrome-btn:hover { background: var(--bg); }
.rc-chrome-sel { max-width: 130px; }
/* Missing metadata: an empty field shows a grey example of what belongs there,
   and stays clickable instead of collapsing. Editor only — print embeds
   pageCss, never this stylesheet, and the off-screen fit hosts are not inside
   #rc-canvas, so neither the PDF nor the fit measurement can ever see these.
   Gated on .rc-ph (Settings > General) so the true printed spacing can be
   checked, and dropped for the duration of markOverflows() so placeholder
   height can't raise a false overflow badge.
   ::after, not ::before: .rc-f-rights:before already holds the "Rights: "
   label, so an empty rights field reads "Rights: World English" with only the
   example greyed. [data-ph] keeps this off fields with no example of their own
   — review_source is a .rc-field too, and display:block would break the inline
   "— reviewer" attribution line it sits on. */
#rc-canvas.rc-ph .rc-slot .rc-field[data-ph]:empty,
#rc-canvas .rc-slot.rc-ph-on .rc-field[data-ph]:empty {
    display: block;
}
#rc-canvas.rc-ph .rc-slot .rc-field[data-ph]:empty:after,
#rc-canvas .rc-slot.rc-ph-on .rc-field[data-ph]:empty:after {
    content: attr(data-ph);
    color: #c8c8c8;
}

/* The left column starts exactly at .rc-slot's content edge, so the edited
   marker's -9px outdent landed OUTSIDE the slot — and .rc-slot clips overflow
   (that clip is what makes the fit meaningful), so left-column markers were
   invisible while right-column ones showed. Widening the slot's PAINT area to
   the left while pulling it back by the same amount gives the marker somewhere
   to live: the content box keeps its width and position exactly, so nothing
   about the layout or the fit changes. Editor only — print embeds pageCss and
   never this sheet, and the off-screen fit hosts render without it. */
#rc-canvas .rc-slot {
    padding-left: 12px;
    margin-left: -12px;
}
/* A field edited by hand: a rule in the left gutter. The negative margin
   exactly cancels the border + padding, so the text itself does not move —
   the marker must not shift a layout that was fitted without it. */
#rc-canvas .rc-field.rc-edited {
    border-left: 3px solid var(--accent, #e8683a);
    margin-left: -9px;
    padding-left: 6px;
}
/* No color-mix(): html2canvas 1.4.1 — which the feedback widget and the
   preview capture both load — cannot parse it and aborts the whole capture
   with 'unsupported color function'. Pale fills use --accent-light or a
   literal rgba; this sheet's own note at .aap-dock says the same. */
/* Previewing the original under the toolbar's Original button. */
#rc-canvas .rc-field.rc-previewing-original {
    background: var(--accent-light);
}
#rc-canvas .rc-chrome-btn[disabled] { opacity: 0.45; cursor: default; }
/* Reimport is an AI round-trip: show it working. Sized for the 8.5pt chrome
   row and accent-coloured, since .btn-spinner's white-on-transparent is
   invisible against these light buttons. */
#rc-canvas .rc-chrome-spinner {
    display: inline-block;
    width: 9px;
    height: 9px;
    margin-right: 5px;
    border: 1.5px solid var(--accent-light);
    border-top-color: var(--accent, #e8683a);
    border-radius: 50%;
    animation: spin 0.6s linear infinite;
    vertical-align: -1px;
}
/* A busy row stays legible while it works. */
#rc-canvas .rc-chrome.is-busy .rc-reimport-btn[disabled] { opacity: 1; }
#rc-canvas .rc-chrome-btn.is-on {
    background: var(--accent, #e8683a);
    border-color: var(--accent, #e8683a);
    color: #fff;
}
/* Sourceless review attribution and the per-item controls are gone: quotes and
   selling points are one free-text field the curator owns. */

/* Coverless books: an "Add cover" target sitting where the cover would be —
   top of the right column, above the publisher, left aligned. Always shown,
   unlike the metadata placeholders: a missing cover is worth surfacing even
   when the hints are switched off. Editor only — print never loads this
   sheet, and the chip is added by addSlotChrome, not by the template. */
#rc-canvas .rc-cover-missing {
    display: block;
    margin: 0 0 8pt;
    border: 1px dashed #b9b9b9;
    background: rgba(255,255,255,0.92);
    color: #9a9a9a;
    font-family: 'Inter', sans-serif;
    font-size: 8.5pt;
    padding: 3px 10px;
    border-radius: 4px;
    cursor: pointer;
}
#rc-canvas .rc-cover-missing:hover {
    border-color: var(--accent, #e8683a);
    color: var(--accent, #e8683a);
}
/* The cover itself opens the picker. */
#rc-canvas .rc-cover-clickable { cursor: pointer; }
#rc-canvas .rc-cover-clickable:hover {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

/* Non-blocking overflow notice under the thumb strip. Replaces the confirm()
   that used to fire on open and offer to re-fit hand-edited descriptions —
   the editor now reports what clips and leaves the pages alone. */
.rc-fit-warning {
    flex-shrink: 0;
    text-align: center;
    padding: 5px 10px;
    margin: 0 10px;
    border-radius: 5px;
    background: rgba(192, 57, 43, 0.09);   /* --danger at 9% */
    color: var(--danger, #dc2626);
    font-family: 'Inter', sans-serif;
    font-size: 0.78rem;
}

/* ── Share Inklet ─────────────────────────────────────────────────
   The gallery card's user-plus icon opens a modal listing everyone on the
   stream's account. Chrome is the shared series-info-save-* modal; only the
   list itself is styled here. */
.share-inklet-title {
    display: -webkit-box;
    -webkit-line-clamp: 2;          /* long inklet names wrap, then clamp */
    -webkit-box-orient: vertical;
    overflow: hidden;
    word-break: break-word;
}
.share-inklet-list {
    margin-top: 0.75rem;
    max-height: 46vh;
    overflow-y: auto;
    text-align: left;
}
.share-inklet-row {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.55rem 0.5rem;
    border-top: 1px solid var(--border);
}
.share-inklet-row:first-child { border-top: none; }
.share-inklet-row.is-shared { background: var(--accent-light); }
.share-inklet-who { flex: 1; min-width: 0; }
.share-inklet-name {
    font-size: 0.88rem;
    font-weight: 500;
    color: var(--ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.share-inklet-email {
    font-size: 0.75rem;
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.share-inklet-role {
    flex: 0 0 auto;
    font-size: 0.72rem;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.4px;
}
.share-inklet-action { flex: 0 0 auto; min-width: 84px; text-align: right; }
/* The owner is stated, not actionable — they can never be removed. */
.share-inklet-owner {
    font-size: 0.75rem;
    font-weight: 500;
    color: var(--accent);
}
.share-inklet-loading, .share-inklet-empty {
    padding: 1rem 0.5rem;
    font-size: 0.85rem;
    color: var(--muted);
    text-align: center;
}
bar */
.rc-toolbar {
    position: fixed;
    z-index: 3000;
    display: flex;
    align-items: center;
    gap: 3px;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 6px;
    box-shadow: 0 3px 12px rgba(0,0,0,0.18);
    padding: 3px;
}
.rc-toolbar button {
    min-width: 26px;
    height: 24px;
    border: 1px solid transparent;
    background: transparent;
    border-radius: 4px;
    cursor: pointer;
    font-size: 0.85rem;
    line-height: 1;
    padding: 0;
}
.rc-toolbar button:hover:not([disabled]) { background: var(--bg); border-color: var(--border); }
/* Text button, so it sizes to its label instead of the icon width above. */
.rc-toolbar #rc-orig-btn {
    width: auto;
    padding: 0 8px;
    font-size: 0.75rem;
    font-family: 'Inter', sans-serif;
    white-space: nowrap;
}
.rc-toolbar #rc-orig-btn[disabled] { opacity: 0.4; cursor: default; }
/* Compact size select — the global select styling is far too tall here and
   the toolbar ends up covering the very line being edited. */
.rc-toolbar select {
    height: 24px;
    padding: 0 2px 0 6px;
    font-size: 0.75rem;
    line-height: 1;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: #fff;
    width: auto;
}

/* Thumbs strip */
/* Seasonal Catalog thumb tiles */
.rc-thumb-sea-div { display: flex; align-items: center; justify-content: center; }
.rc-thumb-sea-div span {
    color: #fff;
    writing-mode: vertical-rl;
    font-size: 6.5px;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    max-height: 90%;
    overflow: hidden;
}
.rc-thumb-sea-row { display: flex; gap: 3px; height: 100%; align-items: flex-start; }
.rc-thumb-sea-row.rc-thumb-sea-right { flex-direction: row-reverse; }
.rc-thumb-sea-cover {
    flex: 0 0 22px;
    height: 30px;
    background: #f1f5f9;
    border: 1px solid;
    border-radius: 2px;
}
.rc-thumb-sea-lines { flex: 1; min-width: 0; font-size: 6px; font-weight: 600; line-height: 1.3; overflow: hidden; }
/* Sections panel colour swatch + divider-size override (Seasonal) */
.rc-sec-row .rc-sec-color {
    flex: 0 0 26px;
    width: 26px;
    height: 24px;
    padding: 0 1px;
    border: 1px solid var(--border);
    border-radius: 4px;
    cursor: pointer;
}
.rc-sec-row .rc-sec-divsize {
    flex: 0 0 58px;
    width: 58px;
    font-size: 0.8rem;
    padding: 3px 4px;
}
/* Seasonal rows carry two extra controls — too many for one line in the
   drawer, which crushed the name input to a sliver. Two lines instead:
   the name on top, colour/size/count/buttons beneath. */
.rc-sec-row-sea { flex-wrap: wrap; }
.rc-sec-row-sea .rc-sec-name { flex: 1 1 100%; order: -1; }
.rc-sec-row-sea .rc-sec-mv:first-of-type { margin-left: auto; }

/* Catalog Templates manager (SA) — fanned sample pages */
.rc-tpl-fan { margin-top: 1rem; min-height: 190px; }
.rc-tpl-fan-row {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 12px 0 6px;
}
.rc-tpl-fan-page {
    flex: 0 0 auto;
    position: relative;
    overflow: hidden;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 3px;
    box-shadow: 0 3px 10px rgba(15, 23, 42, 0.18);
    transition: transform 0.15s ease, z-index 0s;
}
/* Overlapping fan: alternating tilts, straighten + lift on hover */
.rc-tpl-fan-page + .rc-tpl-fan-page { margin-left: -26px; }
.rc-tpl-fan-page:nth-child(1) { transform: rotate(-8deg); }
.rc-tpl-fan-page:nth-child(2) { transform: rotate(-3deg); }
.rc-tpl-fan-page:nth-child(3) { transform: rotate(3deg); }
.rc-tpl-fan-page:nth-child(4) { transform: rotate(8deg); }
.rc-tpl-fan-page:hover { transform: rotate(0deg) scale(1.06); z-index: 5; }
.rc-tpl-fan-page-inner { transform-origin: top left; pointer-events: none; }
/* Neutralise the editor page-sheet chrome inside the tiny fan tiles */
.rc-tpl-fan-page .rcpage { box-shadow: none; margin: 0; }
.rc-tpl-fan-empty {
    text-align: center;
    color: var(--muted);
    font-size: 0.85rem;
    padding: 3.5rem 0;
}
.rc-tpl-fan-caption {
    text-align: center;
    font-size: 0.78rem;
    color: var(--muted);
    margin-top: 0.5rem;
}
.rc-tpl-word-link { font-size: 0.9rem; color: var(--accent); }
.rc-tpl-word-link:hover { color: var(--accent); }

.rc-thumbs {
    flex: 0 0 auto;
    display: flex;
    gap: 0.5rem;
    overflow-x: auto;
    padding: 0.6rem 0.2rem 0.3rem;
}
.rc-thumb {
    flex: 0 0 auto;
    width: 74px;
    height: 96px;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 4px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    padding: 4px;
    font-size: 7px;
    line-height: 1.3;
}
.rc-thumb.active { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent); }
.rc-thumb { cursor: grab; }
.rc-thumb.dragging { opacity: 0.4; cursor: grabbing; }
.rc-thumb.rc-sec-drag { outline: 2px dashed var(--accent); outline-offset: 1px; }
.rc-thumb.drop-before { box-shadow: -3px 0 0 0 var(--accent); }
.rc-thumb.drop-after { box-shadow: 3px 0 0 0 var(--accent); }
.rc-thumb-banner {
    color: #fff;
    text-align: center;
    font-size: 6.5px;
    padding: 2px 1px;
    margin: -4px -4px 3px;
    white-space: nowrap;
    overflow: hidden;
}
.rc-thumb-line {
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    margin-bottom: 2px;
}
.rc-thumb-rule { border-top: 1px solid #999; margin: 3px 6px; }
.rc-thumb-num {
    position: absolute;
    bottom: 2px;
    right: 4px;
    color: var(--muted);
    font-size: 7px;
}

/* Extra-page tiles (standalone inklets) + the [+] add tiles. Deliberately
   NOT .rc-thumb — wireThumbDrag's index math must only see book tiles. */
.rc-thumb-add {
    flex: 0 0 auto;
    width: 44px;
    height: 96px;
    border: 2px dashed var(--border);
    border-radius: 4px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 20px;
    color: var(--muted);
    background: transparent;
}
.rc-thumb-add:hover { border-color: var(--accent); color: var(--accent); }
.rc-thumb-add-disabled { cursor: not-allowed; opacity: 0.45; }
.rc-thumb-add-disabled:hover { border-color: var(--border); color: var(--muted); }
.rc-thumb-x {
    flex: 0 0 auto;
    width: 74px;
    height: 96px;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 4px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
}
.rc-thumb-x.active { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent); }
.rc-thumb-x-bg {
    position: absolute;
    inset: 0;
    background-size: contain;
    background-position: center;
    background-repeat: no-repeat;
    background-color: #f8fafc;
}
.rc-thumb-x-missing .rc-thumb-x-bg { background-color: #fee2e2; }
.rc-thumb-x-remove {
    position: absolute;
    top: 2px;
    right: 2px;
    width: 16px;
    height: 16px;
    border: none;
    border-radius: 50%;
    background: rgba(20, 20, 30, 0.55);
    color: #fff;
    font-size: 11px;
    line-height: 1;
    cursor: pointer;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 0;
}
.rc-thumb-x:hover .rc-thumb-x-remove { display: flex; }
.rc-thumb-x .rc-thumb-num {
    background: rgba(255, 255, 255, 0.85);
    border-radius: 2px;
    padding: 0 2px;
}
/* Extra-page canvas: reuse the slot-chrome look, shown on page hover */
.rcpage-inklet-view:hover .rc-chrome { display: flex; }
/* Poster pages: Reimport | Remove on hover, both pages of the pair. Busy
   chrome stays visible so the spinner survives the pointer leaving. */
.rcpage-poster-cover:hover .rc-chrome,
.rcpage-poster-qr:hover .rc-chrome,
#rc-canvas .rc-chrome.is-busy { display: flex; }
.rc-poster-chrome { top: 8px; }
/* Catalog editor: the training FAB sits exactly over the thumb strip's
   end [+] tile — hide it while the editor is open. */
body.rc-editor-open #ie-training-btn { display: none; }

/* Export page-picker checkboxes: NATIVE controls, no styling here — they
   inherit the global `input[type="checkbox"] { accent-color: var(--accent) }`
   rule (widget-shared.css) like every other checkbox in the dashboard. A
   hand-drawn slate variant shipped in 167fc1d and was reverted on Mark's
   request 2026-08-17: system checkboxes in the Inkara accent, deliberately. */
#rc-exp-all { width: 17px; height: 17px; margin: 0; flex-shrink: 0; vertical-align: middle; }
#rc-exp-list input[type="checkbox"] { width: 17px; height: 17px; margin: 0; flex-shrink: 0; vertical-align: middle; }

/* Catalog Sort — added-page rename input: invisible until interaction */
.rc-bs-xname {
    /* Content-sized (size attr set from the name), NOT flex:1 — a stretched
       input shoved the "— Inklet" label to the far end of the title span,
       so the labels never lined up with the book-title rows. */
    flex: 0 1 auto;
    min-width: 0;
    border: 1px solid transparent;
    background: transparent;
    font: inherit;
    padding: 1px 4px;
    border-radius: 3px;
}
.rc-bs-xname:hover, .rc-bs-xname:focus {
    border-color: var(--border);
    background: #fff;
    outline: none;
}
/* Extra-page tile drag */
.rc-thumb-x { cursor: grab; }
.rc-thumb-x.dragging { opacity: 0.4; cursor: grabbing; }
.rc-thumb-x.drop-before { box-shadow: -3px 0 0 0 var(--accent); }
.rc-thumb-x.drop-after { box-shadow: 3px 0 0 0 var(--accent); }
.rc-thumb-add.drop-target { border-color: var(--accent); color: var(--accent); }

/* Settings drawer */
.rc-settings-drawer {
    flex: 0 0 300px;
    overflow-y: auto;
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 1rem;
    background: #fff;
}
.rc-settings-drawer h3 { margin: 0 0 0.6rem; font-size: 1rem; }
.rc-set-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.55rem;
    font-size: 0.85rem;
}
.rc-set-row label { flex: 1; }
/* input[type=color] paints its swatch in the CONTENT box, so the global
   input padding (0.5rem 0.75rem) squeezed it down to a thin line. */
.rc-set-row input[type="color"] {
    width: 46px;
    height: 26px;
    padding: 2px;
    cursor: pointer;
    flex: 0 0 auto;
}
.rc-color-hex { font-size: 0.75rem; color: var(--muted); }

/* Settings launcher — four areas, one open at a time. */
.rc-set-home {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.6rem;
}
/* Odd tile count: the last one spans the row rather than sitting stranded. */
.rc-set-home .rc-set-tile:last-child:nth-child(odd) { grid-column: 1 / -1; }
.rc-set-tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.45rem;
    padding: 1rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: #fff;
    cursor: pointer;
    text-align: center;
}
.rc-set-tile:hover { background: var(--bg); border-color: var(--accent, #a8324a); }
.rc-set-tile-title { font-size: 0.85rem; font-weight: 600; }
.rc-set-tile svg { width: 20px; height: 20px; stroke: var(--muted); }
.rc-set-tile:hover svg { stroke: var(--accent, #a8324a); }
.rc-set-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 0.6rem;
}
.rc-set-head h3 { margin: 0; }
.rc-set-close {
    border: 1px solid var(--border);
    background: #fff;
    border-radius: 4px;
    font-size: 0.8rem;
    line-height: 1;
    padding: 5px 8px;
    cursor: pointer;
}
.rc-set-close:hover { background: var(--bg); }

/* New-version banner — a slim bar at the very top.
   It PUSHES the page down (body padding + the fixed sidebar's top offset)
   rather than floating over anything, so a banner that cannot be dismissed
   can never cover the control you were reaching for. There is deliberately no
   dismiss: it stays until you reload, but it obscures nothing, so you can
   finish what you were doing first. */
.version-banner {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 40px;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    padding: 0 1rem;
    background: #1d1d1f;
    color: #fff;
    font-size: 0.85rem;
    box-shadow: 0 1px 6px rgba(0, 0, 0, 0.25);
}
.version-banner-v { color: #b9b9bd; font-size: 0.78rem; }
.version-banner-reload {
    border: none;
    border-radius: 5px;
    padding: 5px 14px;
    background: var(--accent, #a8324a);
    color: #fff;
    font-size: 0.8rem;
    font-family: 'Inter', sans-serif;
    font-weight: 500; /* match .btn (Sign in etc.) */
    cursor: pointer;
    white-space: nowrap;
}
.version-banner-reload:hover { filter: brightness(1.1); }

/* Make room for it. Only applied while the banner exists, so the normal
   layout is untouched for everyone on the current build. */
body.has-version-banner { padding-top: 40px; }
body.has-version-banner .sidebar { top: 40px; }
body.has-version-banner .sidebar-expand { top: 54px; }

/* Print Setup — explanatory notes, the live sheet readout, and the
   bleed-without-crop-marks warning. */
.rc-set-note {
    margin: 0.25rem 0 0.75rem;
    font-size: 0.78rem;
    line-height: 1.4;
    color: var(--muted);
}
.rc-set-readout {
    margin-top: 0.75rem;
    padding: 0.45rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--bg);
    font-size: 0.8rem;
}
.rc-set-warn {
    margin-top: 0.5rem;
    padding: 0.45rem 0.6rem;
    border: 1px solid #e5c07b;
    border-radius: 6px;
    background: #fdf6e3;
    font-size: 0.78rem;
    line-height: 1.4;
    color: #7a5b00;
}

/* Book Sort — books in print order, grouped under their section banners. */
.rc-bs-sec {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    margin: 0.9rem 0 0.25rem;
    padding: 0.3rem 0.5rem;
    font-size: 0.8rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--muted);
    background: var(--bg);
    border-radius: 4px;
}
.rc-bs-sec-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rc-bs-secname-input {
    flex: 1;
    border: 1px solid transparent;
    background: transparent;
    color: inherit;
    font: inherit;
    padding: 1px 4px;
    border-radius: 3px;
}
.rc-bs-secname-input:hover, .rc-bs-secname-input:focus {
    border-color: var(--border);
    background: #fff;
    outline: none;
}
.rc-bs-sec:hover .rc-bs-moves, .rc-bs-sec:focus-within .rc-bs-moves { opacity: 1; }
/* Section-move arrows are standard .btn.aic-icon-btn icon buttons (2026-08-16). */
.rc-bs-secmv:disabled { opacity: 0.3; cursor: default; }
.rc-bs-sec:first-child { margin-top: 0; }
.rc-bs-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.4rem 0.5rem;
    border-bottom: 1px solid var(--border);
    background: #fff;
    cursor: grab;
}
.rc-bs-row:hover { background: var(--bg); }
.rc-bs-grip { color: #c8c8c8; font-size: 0.9rem; cursor: grab; }
.rc-bs-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rc-bs-page { font-size: 0.75rem; color: var(--muted); white-space: nowrap; }
/* Arrows stay out of the way until the row is hovered or focused within. */
.rc-bs-moves { display: flex; gap: 3px; opacity: 0; transition: opacity 0.12s; }
.rc-bs-row:hover .rc-bs-moves,
.rc-bs-row:focus-within .rc-bs-moves { opacity: 1; }
/* Sort arrows are standard .btn.aic-icon-btn icon buttons (2026-08-16). */
.rc-bs-mv:disabled { opacity: 0.3; cursor: default; }
.rc-bs-dragging { opacity: 0.4; }
.rc-bs-over { box-shadow: inset 0 -2px 0 var(--accent, #a8324a); }
.rc-bs-sec.rc-bs-over { box-shadow: inset 0 -2px 0 var(--accent, #a8324a); }

/* Sections manager — section headers are user-managed, one row each. */
.rc-sec-row {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.35rem 0;
    border-bottom: 1px solid var(--border);
    font-size: 0.85rem;
}
.rc-sec-row:last-child { border-bottom: none; }
.rc-sec-name { flex: 1; min-width: 0; font-size: 0.85rem; padding: 3px 6px; }
.rc-sec-count { font-size: 0.75rem; color: var(--muted); white-space: nowrap; }
/* Section move/delete are standard .btn.aic-icon-btn icon buttons now
   (2026-08-16) — only the disabled dim remains as bespoke styling. */
.rc-sec-mv:disabled { opacity: 0.35; cursor: default; }

/* Cover picker modal */
.rc-cover-result {
    display: flex;
    gap: 0.6rem;
    align-items: center;
    padding: 0.4rem;
    border-radius: 6px;
    cursor: pointer;
}
.rc-cover-result:hover { background: var(--bg); }
.rc-cover-result img { width: 40px; display: block; }
.rc-cover-result-none {
    width: 40px;
    height: 56px;
    background: var(--bg);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--muted);
}

/* Ingestion Runs — Pending Covers card (super-admin) */
.pending-covers-card { margin-bottom: 1.5rem; }
.pending-covers-card .stats-row { margin-bottom: 0; }
.pc-action { margin-top: 0.6rem; min-height: 32px; font-size: 0.85rem; color: var(--muted); }
.pc-action .btn { font-size: 0.85rem; }
/* Base .btn-spinner is white-on-white outside a filled button — recolor. */
.pc-action .pc-spinner { border-color: rgba(0,0,0,0.15); border-top-color: var(--accent); }
.pc-note { font-size: 0.8rem; margin-bottom: 0.35rem; }
.pc-note-ok      { color: #2e7d32; }
.pc-note-err     { color: #c0392b; }
/* Phase 34 — amber "stopped" note (user-stopped OR auto-cap cycle-detect). */
.pc-note-stopped { color: #b45309; }
/* Phase 34 — compact Stop button next to the sweep spinner. */
.pc-sweep-stop-btn {
    margin-left: 8px;
    padding: 2px 8px;
    font-size: 11px;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--paper);
    color: var(--muted);
    cursor: pointer;
    vertical-align: middle;
    font-family: inherit;
}
.pc-sweep-stop-btn:hover:not(:disabled) { color: var(--ink); border-color: var(--accent); }
.pc-sweep-stop-btn:disabled             { opacity: 0.6; cursor: default; }

/* ── Books modal → Assets tab → sub-panels ───────────────────── */
.book-asset-panel { margin-top: 1rem; }

.book-asset-grid {
    display: grid;
    grid-template-columns: 35% 65%;
    gap: 1rem;
    align-items: start;
}
.book-asset-col-desc {
    font-size: 0.9rem;
    color: var(--muted);
    line-height: 1.45;
    text-align: left;
}
.book-asset-col-cover { text-align: left; }

/* Empty-state cover renders via the same .book-asset-cover-img as the
   populated state, so the picture is the same size + same place before
   and after the no-text variant is generated. */

/* Empty-state actions live under the description in column 1, left-aligned. */
.book-asset-desc-actions {
    margin-top: 12px;
    display: flex;
    align-items: center;
    gap: 8px;
    justify-content: flex-start;
}
.book-asset-generate-btn {
    font-size: 12px;
    padding: 4px 10px;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--paper);
    color: var(--ink);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.book-asset-generate-btn:hover { border-color: var(--accent); color: var(--accent); }
/* Usage Tips (migration 165) — same shape as Generate, outline-accent CTA. */
.book-asset-usage-tips-btn {
    background: transparent;
    border-color: var(--accent);
    color: var(--accent);
}
.book-asset-usage-tips-btn:hover { background: var(--accent-light); }
.book-asset-ai-glyph { font-size: 13px; }
/* Usage Tips info icon on generated-asset cards (migration 165) — accent
   colour so it reads as the help affordance, first in the action row. */
.aic-icon-btn.usage-tips-icon { color: var(--accent); }
.aic-icon-btn.usage-tips-icon:hover { color: var(--accent-dark); }
.book-asset-info-btn {
    border: none;
    background: transparent;
    color: var(--muted);
    cursor: pointer;
    padding: 0;
    display: inline-flex;
    align-items: center;
}
.book-asset-info-btn:hover { color: var(--accent); }

/* Populated cover: image at 150px tall with corner trash button */
.book-asset-cover-wrap {
    position: relative;
    display: inline-block;
}
.book-asset-cover-img {
    display: block;
    height: 150px;
    width: auto;
    border-radius: 4px;
    border: 1px solid var(--border);
}
/* Placeholder treatment for the Background tab's empty state — shows
   the original cover greyed out so it's visibly NOT the final asset
   yet. Same filter as .marketing-prompt-card-thumb.placeholder. */
.book-asset-cover-img-placeholder {
    filter: grayscale(100%);
    opacity: 0.5;
}
.book-asset-cover-wrap a { cursor: zoom-in; }

/* Centred eye overlay — appears on hover, signals "click to open". */
.book-asset-cover-eye {
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 48px; height: 48px;
    border-radius: 50%;
    background: rgba(0,0,0,0.55);
    color: #fff;
    display: flex; align-items: center; justify-content: center;
    opacity: 0;
    transition: opacity 0.15s;
    pointer-events: none;   /* the <a> ancestor handles the click */
    z-index: 3;
}
.book-asset-cover-wrap:hover .book-asset-cover-eye { opacity: 1; }

/* Bigger trash — lives in column 1 under the description (~2× the
   old on-image variant). Outline-style so it sits comfortably in
   a text context; danger-red on hover. */
.book-asset-delete-big {
    width: 33px; height: 33px;
    border-radius: 50%;
    border: 1.5px solid var(--border);
    background: var(--paper);
    color: var(--muted);
    cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    padding: 0;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.book-asset-delete-big:hover {
    border-color: var(--danger);
    color: var(--danger);
}

.book-asset-coming-soon {
    padding: 2rem;
    color: var(--muted);
    font-style: italic;
    text-align: center;
}

/* ── Marketing sub-tab (Books modal → Assets (AI) → Marketing) ──
   Horizontal lozenge row of channel groups (one per inklet_channel_groups
   row) above a grid of prompt cards.
   Visual mirrors the existing .badge palette so these chips line up
   stylistically with the Active / Primary status badges sitting on the
   same modal — same sizing, same soft-pastel + dark-text scheme. Each
   lozenge gets its colours via two inline CSS custom properties:
     --lz-bg  soft pastel fill (used when selected, plus on hover)
     --lz-fg  dark text + outline colour (used always when not greyed) */
.marketing-lozenge-row {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 4px 0 16px;
}
.marketing-lozenge {
    display: inline-block;
    padding: 0.2rem 0.5rem;
    border-radius: 4px;
    font-size: 0.75rem;
    font-weight: 600;
    background: transparent;
    color: var(--lz-fg);
    border: 1px solid var(--lz-fg);
    cursor: pointer;
    transition: background 0.12s;
    font-family: inherit;
    line-height: 1.3;
}
.marketing-lozenge:not(.inactive):hover {
    background: var(--lz-bg);
}
.marketing-lozenge.active {
    background: var(--lz-bg);
    color: var(--lz-fg);
    border-color: transparent;
}
.marketing-lozenge.inactive {
    color: var(--muted);
    border-color: var(--border);
    background: transparent;
    cursor: not-allowed;
    opacity: 0.6;
}
/* Card grid — three equal columns at every row. (The channel-group's
   ai_summary text is no longer surfaced on the Marketing tab; it
   remains stored on inklet_channel_groups + editable via the Edit
   Group modal, but the Marketing surface is now purely the asset
   launchpad. Removed Phase 32d.) */
.marketing-prompt-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1rem;
}
.marketing-prompt-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 18px 16px 16px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--paper);
    text-align: center;
    box-sizing: border-box;
    min-width: 0;
}
.marketing-prompt-card-name {
    font-weight: 600;
    font-size: 0.95rem;
    line-height: 1.25;
}
/* Marketing-asset card type badge: one-off Inklet / (stream) Template /
   System Template — each in a distinct Inkara-palette colour. */
.mkt-type-badge {
    display: inline-block;
    margin: 3px 0 2px;
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    line-height: 1.5;
}
/* Muted per-type tints, all drawn from tints already in this sheet:
   Inklet = processing-blue, Template = awaiting-inkara lavender,
   Inkara Template = brand accent (the "official" mark). Generated
   keeps the neutral grey. */
.mkt-type-badge--inklet { background: #d1ecf1; color: #0c5460; }
.mkt-type-badge--template { background: #ede9fe; color: #5b21b6; }
.mkt-type-badge--system { background: var(--accent-light); color: var(--accent); }
.mkt-type-badge--generated { background: #ece9e3; color: #6b6457; }
/* Add-page modal: page already placed in this catalog. Colours are
   'Slate Blue' from the inkara_colours table (id 13). */
.mkt-type-badge--in-catalog { background: #9fb0c2; color: #223C59; }
/* Shared (inklet sharing) — accent tint, and the only one that sits NEXT TO
   another badge, so it carries the gap. Shown to both sides: the recipient
   sees a card that is not theirs, the owner sees one that is out with someone. */
.mkt-type-badge--shared {
    background: var(--accent-light);
    color: var(--accent);
    margin-left: 6px;
}
/* Draft (migration 215): configured but not generated. Accent-tinted rather
   than the neutral "Generated" grey so it reads as a state, not a category. */
.mkt-type-badge--draft { background: var(--accent-light); color: var(--accent-dark); }
.marketing-prompt-card-channel {
    font-size: 0.8rem;
    color: var(--muted);
}
.marketing-prompt-card .book-asset-generate-btn {
    margin-top: auto;
}

/* Cached-asset thumbnail (rendered when book_ai_assets has a row for
   this (book, prompt) pair). Click opens the full-resolution PNG in a
   new tab. Letterboxed at 192×96 inside the card; img is object-fit
   contain so non-16:9 outputs aren't cropped. */
.marketing-prompt-card-thumb {
    display: block;
    width: 100%;
    max-width: 200px;
    aspect-ratio: 16 / 9;
    border-radius: 6px;
    overflow: hidden;
    background: #f1f5f9;
    border: 1px solid var(--border);
    cursor: pointer;
    transition: transform 0.15s, box-shadow 0.15s;
}
/* Inklet/template previews are LIVE HTML, not an opaque image, so whatever
   the card sits on shows through the canvas — and an inklet whose config has
   no container background renders transparent (bgCSS returns "" for an unset
   background; only the canvas-mode path defaults to #ffffff). The slate
   #f1f5f9 above is a loading placeholder meant to hide behind an image; here
   it leaked through and made a transparent 3D cover look like it was pasted
   on a pale blue panel. White matches both the editor canvas and the exported
   PNG, so the preview now shows what you actually get. */
.mkt-card-thumb { background: #fff; }
.marketing-prompt-card-thumb:hover {
    transform: scale(1.03);
    box-shadow: 0 4px 12px rgba(0,0,0,0.12);
}
.marketing-prompt-card-thumb img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
    background: #f8fafc;
}

/* Multi-panel composite (Amazon A+ 3-panel, tagline carousel) — a wide
   horizontal strip OR a tall vertical stack. Drop the fixed 16:9 box + 200px
   cap and centre the image at its natural aspect (no letterbox bars / squash),
   capping height so a tall vertical composite stays a reasonable card size. */
.marketing-prompt-card-thumb.mkt-thumb-auto {
    aspect-ratio: auto;
    max-width: 100%;
    max-height: 360px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.marketing-prompt-card-thumb.mkt-thumb-auto img {
    width: auto;
    height: auto;
    max-width: 100%;
    max-height: 360px;
    object-fit: contain;
}

/* Placeholder thumbnail — when there's no asset for THIS (book, prompt)
   yet, we surface the most recent asset from any OTHER book in muted
   monochrome so the user gets a feel for what the prompt produces.
   Non-interactive (no anchor wrapper) to keep it visually distinct
   from a real cached asset. */
.marketing-prompt-card-thumb.placeholder {
    cursor: pointer;
}
/* Hover lift + shadow already declared on the base
   .marketing-prompt-card-thumb:hover rule above; placeholder inherits
   them now that it's clickable. */
.marketing-prompt-card-thumb.placeholder img {
    filter: grayscale(100%);
    opacity: 0.5;
}
/* "+ New" campaign-asset wizard: show the sample preview in FULL COLOUR (not
   greyscaled) with a centred "SAMPLE" watermark — mirrors the Step-3
   .bm-step3-placeholder so a curator sees the true colours while still
   reading it as an example, not their own asset. Higher specificity
   (.placeholder.campaign-asset-sample) overrides the greyscale rule above. */
.marketing-prompt-card-thumb.campaign-asset-sample {
    position: relative;
}
.marketing-prompt-card-thumb.placeholder.campaign-asset-sample img {
    filter: none;
    opacity: 1;
}
.campaign-asset-sample-label {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    color: rgba(255, 255, 255, 0.85);
    font-size: 1.1rem;
    font-weight: 600;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6);
    pointer-events: none;
    white-space: nowrap;
}

/* Shimmer loader — while the resized thumbnail downloads from the assets
   CDN (assetThumb → assets.inkara.io), the .shimmer anchor shows the shared
   img-skeleton gradient and the <img> fades in once it fires onload. Reuses
   the exact idiom from .inklet-gallery-thumb / .campaign-book-cover. Placed
   AFTER the .placeholder img rule so .shimmer img{opacity:0} wins during load
   (equal specificity, later in source) and the .loaded rules restore each
   state. Pending/cover-pending variants opt out (no .shimmer class) so their
   campaign-spinner overlay is untouched. */
.marketing-prompt-card-thumb.shimmer {
    background: linear-gradient(110deg, #f0ede8 30%, #e8e4de 50%, #f0ede8 70%);
    background-size: 200% 100%;
    animation: img-skeleton 1.5s ease-in-out infinite;
}
.marketing-prompt-card-thumb.shimmer img {
    opacity: 0;
    transition: opacity 0.3s ease;
}
.marketing-prompt-card-thumb.shimmer img.loaded {
    opacity: 1;
}
/* keep the grayscale sample muted after it loads */
.marketing-prompt-card-thumb.shimmer.placeholder img.loaded {
    opacity: 0.5;
}

/* Stale flag — shown when the prompt has been edited since this asset
   was generated. Muted amber to match the existing .badge-pending /
   .badge-hidden palette. */
.marketing-prompt-card-stale {
    font-size: 0.72rem;
    font-style: italic;
    color: #856404;
}

/* ── Campaigns page — generated-asset cards ──
 * The Campaigns page (views-campaigns.js) reuses the Assets (AI) card classes
 * (.marketing-prompt-card / -thumb / -name / -channel) and adds an ISBN•Format
 * sub-line plus a 4-icon action bar (Download / Bookmark / Refresh / Trash).
 * Full-width responsive grid (the page isn't a modal, so fit more per row). */
.campaign-asset-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 1rem;
}
.campaign-asset-sub {
    font-size: 0.78rem;
    color: var(--muted);
    word-break: break-word;
}
/* Custom-text echo (book_multi cards) — the joined chip / freeform strings
   the curator chose. Sits between the prompt name and the ISBN • format
   line on both pending and finished cards. Same muted size as .sub but
   uppercase-friendly weight so multi-line headlines read cleanly. */
.campaign-asset-custom-text {
    font-size: 0.78rem;
    color: var(--ink);
    font-weight: 500;
    word-break: break-word;
    line-height: 1.35;
}
/* Italic date line — publication date (book assets) or event date-window
   (event assets). Sits just under the channel. */
.campaign-asset-when {
    font-size: 0.78rem;
    font-style: italic;
    color: var(--muted);
}
/* Push the action bar to the card's bottom edge regardless of title length. */
.campaign-asset-actions {
    margin-top: auto;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    padding-top: 6px;
}
/* Bookmark toggle — styled like the select buttons (outline pill): neutral by
   default, accent (icon + outline) on hover. Equal padding makes it square so
   the outline matches the select-button height; align-items:center on the row
   keeps it vertically aligned, and it stays pushed to the right. */
.campaigns-bookmark-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    padding: 0.45rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--card-bg);
    color: var(--ink);
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s, box-shadow 0.15s;
}
.campaigns-bookmark-btn svg {
    width: 16px;
    height: 16px;
    display: block;
}
.campaigns-bookmark-btn:hover,
.campaigns-bookmark-btn.active {
    border-color: var(--accent);
    color: var(--accent);
}
/* Active (bookmarks view on) also fills the glyph so the mode reads at a glance. */
.campaigns-bookmark-btn.active svg { fill: var(--accent); }
/* Head row — bookmark toggle flush left, "+ New" flush right, on the line
   ABOVE the filters. Campaigns has no page-body heading (the hero already
   names the page), so this is the card's first row and sits flush at the card
   padding; the margin below it separates it from the filter row. */
.campaigns-filter-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}
.campaigns-filter-head-left {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
}
/* Filter row — channel + date-range buttons and their chips on the left (all
   on one line), bookmark toggle pushed to the right. */
.campaigns-filter-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
}
/* Spacing lives on the (only-in-bookmarks) heading so the default view, where
   the heading is hidden, sits flush at the card padding. */
#campaigns-title-pair { margin-bottom: 1rem; }
.campaigns-filter-left {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
}
/* "+ New" create-asset modal — step-1 book picker: the same .bm-book-lozenge
   cards as the ISBN-Emporium picker, 3 per row, but COMPACT (small cover +
   tight text) to match the ISBN-Emporium search-result lozenge size. */
.camp-new-books-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
    align-items: start;
}
.camp-new-books-grid .bm-book-lozenge { padding: 8px; gap: 10px; }
.camp-new-books-grid .bm-book-lozenge img { width: 44px; height: 62px; }
.camp-new-books-grid .bm-book-lozenge .bm-meta { font-size: 0.78rem; padding-right: 0; gap: 1px; }
.camp-new-books-grid .bm-book-lozenge .bm-title { font-size: 0.8rem; line-height: 1.2; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.camp-new-books-grid .bm-book-lozenge .bm-isbn-format { font-size: 0.72rem; }

/* Multi-Book Asset modal step-1 picker — mirrors the Campaigns "+ New"
   picker (search box + Coming / Recently Released / Backlist tabs +
   3-col card grid), but with multi-select instead of single-select.
   Reuses the same .bm-book-lozenge cards in the same 3-col layout for
   visual consistency. */
.mba-picker-host { display: block; }
.mba-picker-subhead {
    margin: 0 0 10px;
    padding: 8px 12px;
    background: #f7f5fa;
    border-left: 3px solid var(--accent);
    border-radius: 4px;
    font-size: 0.82rem;
    color: var(--ink);
}
.mba-picker-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
    align-items: start;
}
.mba-picker-grid .bm-book-lozenge { padding: 8px; gap: 10px; cursor: pointer; }
.mba-picker-grid .bm-book-lozenge img { width: 44px; height: 62px; }
.mba-picker-grid .bm-book-lozenge .bm-meta { font-size: 0.78rem; padding-right: 0; gap: 1px; }
.mba-picker-grid .bm-book-lozenge .bm-title { font-size: 0.8rem; line-height: 1.2; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.mba-picker-grid .bm-book-lozenge .bm-isbn-format { font-size: 0.72rem; }

/* Book modal Author tab — "Other titles by this author" section
   heading. Serif + black to read as a content header above the table
   below, distinguishing it from the author-info card above. The table
   itself reuses the standard .card / .table-wrap chrome so it matches
   the Formats / Series tabs visually. */
.author-other-titles-heading {
    font-family: Georgia, 'Times New Roman', 'Cormorant Garamond', serif;
    color: #000;
    font-weight: 700;
    font-size: 1.15rem;
    margin: 1.5rem 0 0.75rem;
}

/* Series Info save-confirm modal (migration 100 + dashboard wiring).
   Asks the curator whether to persist the series info for THIS book
   only or for every book in the series. Reuses the existing
   .action-modal-quill PNG + meQuillMove animation defined in
   widget-shared.css so the modal feels visually consistent with the
   other long-running-write modals already in the dashboard. */
.series-info-save-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
    backdrop-filter: blur(2px);
    display: flex;
    align-items: center;
    justify-content: center;
    /* Must sit above the Book modal (99999) and lightboxes (100000)
       since this confirm modal is opened FROM inside the Book modal.
       Toast (2147483647) still wins so error/success feedback shows
       on top of this dialog. */
    z-index: 100001;
}
.series-info-save-modal {
    position: relative;
    background: #fff;
    border-radius: 10px;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.22);
    padding: 28px 32px 24px;
    width: 90%;
    max-width: 420px;
    text-align: center;
    animation: sisave-pop-in 200ms ease-out;
}
@keyframes sisave-pop-in {
    from { opacity: 0; transform: translateY(8px) scale(0.97); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}
.series-info-save-close {
    position: absolute;
    top: 8px;
    right: 12px;
    background: none;
    border: none;
    font-size: 1.5rem;
    line-height: 1;
    color: var(--muted);
    cursor: pointer;
    padding: 4px 8px;
}
.series-info-save-close:hover { color: var(--ink); }
.series-info-save-quill-wrap {
    display: flex;
    justify-content: center;
    margin-bottom: 8px;
    overflow: visible;
}
.series-info-save-title {
    font-family: Georgia, 'Times New Roman', 'Cormorant Garamond', serif;
    font-weight: 700;
    font-size: 1.35rem;
    color: var(--ink);
    margin: 4px 0 8px;
    text-align: center;
}
.series-info-save-sub {
    color: var(--muted);
    font-size: 0.92rem;
    line-height: 1.4;
    margin: 0 0 22px;
    text-align: center;
}
.series-info-save-actions {
    display: flex;
    justify-content: center;
    gap: 12px;
    flex-wrap: wrap;
}
.series-info-save-actions .btn { min-width: 150px; }

/* Export Options modal — clones the .series-info-save-* chrome but widens it to
   fit the 3-card chooser (reuses the .me-bg-picker-* cards from widget-shared.css).
   Compound selectors keep these overrides specificity-safe regardless of which
   stylesheet loads last. */
.export-options-modal { max-width: 600px; }
.me-bg-picker-grid.eo-grid-3 { grid-template-columns: repeat(3, 1fr); }
.me-bg-picker-grid.eo-grid-2 { grid-template-columns: repeat(2, 1fr); }
/* .cc-featured shares this treatment but is NOT a state — it marks the
   recommended card in the create chooser, which is why it gets its own
   class rather than being handed `selected`. */
.me-bg-picker-item.selected,
.me-bg-picker-item.cc-featured {
    border-color: var(--accent);
    background: var(--accent-light);
}
.me-bg-picker-item.selected svg { color: var(--accent); }

/* ── Create chooser (Campaigns "+ Create") ──────────────────────────────
   Reuses the .series-info-save-* shell and the .me-bg-picker-* cards,
   flipped from a centred column to a row with a tinted icon circle — the
   dashboard's only icon-on-the-left card. Same override discipline as the
   Export Options block above: compound selectors, because widget-shared.css
   loads AFTER style.css, so a bare .cc-row would lose to .me-bg-picker-item.

   GEOMETRY ONLY. Every string in this modal carries a named house style
   (.hs-display / .hs-heading / .hs-body / .hs-caption / .hs-label), so
   there is deliberately no font-size, font-family or font-weight below. */
.cc-modal { max-width: 560px; }
.cc-modal > .hs-display { margin: 4px 0 8px; }
.cc-modal > .hs-caption { margin: 0 0 22px; }
.me-bg-picker-grid.cc-grid-1 { grid-template-columns: 1fr; }
.me-bg-picker-item.cc-row {
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
    gap: 20px;
    padding: 22px 24px;
    text-align: left;
}
/* Card 1 already sits at --accent-light, so the shared :hover (which sets
   exactly that background) is invisible on it — without this the featured
   card is the one card with no click affordance. */
.me-bg-picker-item.cc-featured:hover { border-color: var(--accent-dark); }
.me-bg-picker-item.cc-row .cc-icon {
    flex: 0 0 52px;
    height: 52px;
    border-radius: 50%;
    background: var(--accent-light);
    color: var(--accent);
    display: flex;
    align-items: center;
    justify-content: center;
}
/* On the featured card the tint IS --accent-light, so an --accent-light
   circle disappears into it. Drop to the card surface so the well still
   reads. */
.me-bg-picker-item.cc-featured .cc-icon { background: var(--card-bg); }
/* Required, not cosmetic: widget-shared.css's `.me-bg-picker-item svg` sets
   50px + --muted + margin-bottom on EVERY descendant svg, which would blow
   the icon out of its circle and grey it. Three classes to beat it. */
.me-bg-picker-item.cc-row .cc-icon svg {
    width: 26px;
    height: 26px;
    margin: 0;
    color: inherit;
}
.me-bg-picker-item.cc-row .cc-text { min-width: 0; }   /* wrap, don't overflow */
/* The global `* { margin: 0 }` reset zeroes the house styles' own margins. */
.me-bg-picker-item.cc-row .cc-text > * + * { margin-top: 4px; }
.hs-label.cc-pill {
    display: inline-block;
    margin-left: 8px;
    padding: 1px 8px;
    border: 1px solid var(--accent);
    border-radius: 999px;
    color: var(--accent);
    vertical-align: middle;
}

.channel-filter-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.45rem 0.9rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--card-bg);
    color: var(--ink);
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.15s, box-shadow 0.15s;
    white-space: nowrap;
}
.channel-filter-btn:hover {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent);
}
.channel-filter-btn svg { width: 16px; height: 16px; }
/* Category button — a category is always selected, so it reads as active
   (accent outline + text); hover drops the outline to neutral to signal you're
   just switching the current selection. */
.group-filter-btn {
    border-color: var(--accent);
    color: var(--accent);
}
.group-filter-btn:hover {
    border-color: var(--border);
    box-shadow: none;
}
.channel-chip-row {
    display: flex;
    /* Centre the chips horizontally — applies to both the Campaigns page and
       the Book modal > Marketing Assets tab. */
    justify-content: center;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
}
/* Chips sit on their own line below the button row, so wrapping never pushes
   the right-aligned "+ New" button. A solid top gap so chips don't collide
   with the filter buttons. No top gap when there are no chips.
   Selector matches both the legacy #campaigns-chips id (now retired in the
   mountAssetSurface refactor) and the new shared [data-role="chips"] host
   that the factory paints — works in Campaigns AND the Book modal. */
[data-role="chips"].channel-chip-row { margin-top: 1rem; }
[data-role="chips"].channel-chip-row:empty { margin-top: 0; }
/* Inline variant — chips sit on the SAME row as the filter buttons (left-
   aligned, just after the date-range button). Override the centred /
   wrapping defaults so the row reads "Social Content, Filter channels,
   Square (1:1)" left-to-right. The wrapping parent (.campaigns-filter-left)
   handles overflow via its own flex-wrap. */
.channel-chip-row.channel-chip-row-inline {
    justify-content: flex-start;
    margin-top: 0;
    margin-left: 0.4rem;
}
[data-role="chips"].channel-chip-row.channel-chip-row-inline { margin-top: 0; }
.channel-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.3rem 0.35rem 0.3rem 0.7rem;
    border-radius: 999px;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--ink);
    white-space: nowrap;
}
.channel-chip-x, .date-chip-x {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border: none;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.12);
    color: var(--ink);
    font-size: 0.95rem;
    line-height: 1;
    cursor: pointer;
    padding: 0;
}
.channel-chip-x:hover, .date-chip-x:hover { background: rgba(0, 0, 0, 0.25); }
/* Date-range option cards carry only a label — dead-center it and keep them
   compact. line-height:1 trims the empty descender space so the descenderless
   labels read as vertically centred rather than sitting high. */
.date-option-card { justify-content: center; align-items: center; min-height: 64px; }
.date-option-card .ie-channel-card-name { margin: 0; line-height: 1; }

/* Channel-filter modal — the card grid scrolls inside a flex column so the
   "Add (n)" footer stays pinned and visible regardless of channel count. */
.channel-filter-modal { display: flex; flex-direction: column; overflow: hidden; }
.channel-filter-modal .ie-channel-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    margin-top: 1rem;
    /* Inner padding so the hover lift (translateY) + box-shadow + the selected
       2px border aren't clipped by the scroll container's overflow. */
    padding: 6px;
}
.channel-filter-modal .ie-channel-scroll .ie-channel-grid { margin-top: 0; }
.channel-filter-footer {
    flex: 0 0 auto;
    display: flex;
    justify-content: flex-end;
    margin-top: 1.5rem;
}
/* Bookmarked state — fill the Feather outline so saved assets read at a glance. */
.campaign-bookmark-btn.active svg {
    fill: var(--accent);
    color: var(--accent);
}
/* Trash goes red on hover (no destructive chrome until intent is shown). */
.campaign-trash-btn:hover svg {
    color: #c0392b;
}
/* Spin the refresh glyph while a regeneration is in flight. */
.campaign-refreshing svg {
    animation: campaign-spin 0.8s linear infinite;
}
@keyframes campaign-spin {
    to { transform: rotate(360deg); }
}

/* ── "Generating…" pending placeholder card ──
 * Background-generation jobs (asset-jobs.js, migration 076) surface a
 * placeholder tile while the renderer works, so navigating away and coming
 * back still shows progress that resolves into the finished asset. Reuses the
 * .marketing-prompt-card chrome; adds a spinner overlay on the thumb + a
 * dimmed "Generating…" label in place of the action bar. */
.campaign-asset-pending {
    opacity: 0.95;
}
.campaign-asset-pending-thumb {
    position: relative;
    cursor: default;
}
/* Suppress the lift/zoom hover (inherited from .marketing-prompt-card-thumb)
   — a placeholder isn't interactive. */
.campaign-asset-pending-thumb:hover {
    transform: none;
    box-shadow: none;
}
/* Cover-shaped in-flight placeholder — shows the BOUND book's cover
   (or first picked book's for multi-book) greyscaled + centre-cropped
   to the channel's aspect ratio. The OUTER container inherits the
   standard `.marketing-prompt-card-thumb` 16:9 ≈ 200×110 rectangle so
   the chrome / outline / size match a finished asset exactly. The
   INNER frame is sized to the channel's aspect ratio (inline) and
   centred inside the rectangle — a Square (1:1) cover shows as a
   ~110×110 centred square inside the 200×110 rectangle with light
   letterbox strips, a Story (9:16) cover as a tall narrow strip, etc.
   Used on the Campaigns "Generating…" tile + the Book modal Marketing
   tab pending state. */
.campaign-asset-pending-cover,
.marketing-prompt-card-cover-pending {
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: default;
}
.marketing-prompt-card-cover-pending:hover {
    transform: none;
    box-shadow: none;
}
/* Inner frame — height fills the outer rectangle; width derives from
   the inline `aspect-ratio`. `max-width: 100%` caps super-wide channels
   (e.g. 3:1 banners) so they stay within the rectangle. */
.campaign-asset-pending-cover-frame,
.marketing-prompt-card-cover-pending-frame {
    height: 100%;
    max-width: 100%;
    overflow: hidden;
    /* aspect-ratio set inline (per channel). */
}
.campaign-asset-pending-cover-frame img,
.marketing-prompt-card-cover-pending-frame img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
    display: block;
    filter: grayscale(100%);
    opacity: 0.6;
}
.campaign-asset-pending-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(241, 245, 249, 0.55);
}
.campaign-spinner {
    display: inline-block;
    width: 26px;
    height: 26px;
    border: 3px solid rgba(99, 102, 241, 0.25);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: campaign-spin 0.8s linear infinite;
}
.campaign-asset-pending-label {
    margin-top: auto;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--accent);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding-top: 6px;
}

/* ── Campaigns per-channel rails (mountAssetSurface opts.sections) ──
 * One horizontal scroller per channel, titled "Group > Channel". Tiles are
 * the SAME .cm-tile markup the masonry wall uses — only the container and the
 * sizing differ, so the hover overlay/actions below apply unchanged. Every
 * asset in a rail shares one channel aspect, so each tile takes a fixed
 * height + aspect-ratio and there is no packing pass at all. */
/* Breathing room between the filter row and the first rail — the rails are a
   browsing surface, not a dense grid, and the titles need air to read as
   section headings rather than another row of controls. */
.cs-sections { display: flex; flex-direction: column; gap: 2.25rem; padding-top: 1.25rem; }
.cs-section-head {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 1rem; margin-bottom: 0.75rem;
}
/* Byte-for-byte the "Your bookmarked assets" note on this page
   (views-campaigns.js) — same family, size, weight and slate colour. */
/* House style: Section */
.cs-section-title {
    font-family: var(--font-sans); font-size: var(--text-section); font-weight: 400;
    color: var(--muted); line-height: 1.2;
}
.cs-see-all {
    background: none; border: none; padding: 0; cursor: pointer;
    font-family: 'Inter', sans-serif; font-size: 13px; color: var(--accent);
    flex-shrink: 0;
}
.cs-see-all:hover { text-decoration: underline; }
/* The rail fills the section, so every rail starts at the same x as its own
   title and as every other section — arrows are overlaid, never in the flow. */
.cs-rail-wrap { position: relative; }
.cs-rail {
    width: 100%; display: flex; gap: 12px;
    overflow-x: auto; overflow-y: hidden; scroll-behavior: smooth;
    scroll-snap-type: x proximity;
    /* The arrows are the affordance; a scrollbar under every rail is noise. */
    scrollbar-width: none; -ms-overflow-style: none;
}
.cs-rail::-webkit-scrollbar { display: none; }
/* The [+] tile MUST be a snap point too. Without it, `scroll-snap-type:
   x proximity` finds its nearest snap position at the first asset tile and
   silently scrolls the rail past the [+] on load — it was only reachable
   via the left arrow. */
.cs-rail > .cs-tile,
.cs-rail > .cs-new-tile { scroll-snap-align: start; }
/* .rc-nav-arrow is position:sticky/top:45% for page-level rails — override to
   absolute so the arrow floats over the tiles rather than reserving a column
   beside them. The shadow keeps it legible against arbitrary artwork. */
.cs-rail-arrow {
    position: absolute; top: 50%; transform: translateY(-50%);
    width: 36px; height: 36px; font-size: 1.1rem; z-index: 3;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.18);
}
.cs-rail-arrow-prev { left: 8px; }
.cs-rail-arrow-next { right: 8px; }
.cs-section-loading .cs-rail { opacity: 0.35; }
/* "See all" drill-down: back link + the channel you're inside. */
.cs-see-all-bar {
    display: flex; align-items: baseline; gap: 14px;
    padding-top: 1.25rem; margin-bottom: 1.25rem;
}
/* Mounted inline in the head row's left cluster (same line as "+ New") —
   the row's own alignment applies, so the standalone spacing must not. */
.cs-see-all-bar-inline { padding-top: 0; margin-bottom: 0; }
/* Styled as the ISBN Emporium's .section-title-pair: the back link takes
   the DM Serif ink title role, the channel title beside it is already the
   grey Section subtitle. Same 0.5rem baseline gap as the pair. */
.cs-see-all-bar { gap: 0.5rem; }
.cs-back-link {
    background: none; border: none; padding: 0; cursor: pointer;
    font-family: 'DM Serif Display', serif; font-size: 1.5rem; font-weight: 400;
    letter-spacing: -0.3px; color: var(--ink); line-height: 1.2;
}
.cs-back-link:hover { text-decoration: underline; }

/* Rail [+] tile — "create one of these" at the head of every channel rail.
   Mirrors the ISBN Emporium's New Inklet card (hero photo, scrim, big plus
   glyph) so the two creation affordances read as the same gesture.
   Deliberately SQUARE whatever the rail's aspect: it's a control, not a
   sample of the format — a 9:16 rail would otherwise open with a sliver.
   The dimensions caption carries the format instead. */
.cs-new-tile {
    position: relative; overflow: hidden; cursor: pointer; padding: 0;
    border: 1px solid var(--border); border-radius: 10px; background: none;
    display: block;
}
.cs-new-tile-bg {
    position: absolute; inset: 0;
    background-size: cover; background-position: center; background-repeat: no-repeat;
}
.cs-new-tile-bg::after {
    content: ''; position: absolute; inset: 0;
    background: rgba(0, 0, 0, 0.28); pointer-events: none;
}
.cs-new-tile-plus {
    position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: 56px; height: 56px; color: #fff; z-index: 1; pointer-events: none;
    transition: color 0.15s ease;
}
.cs-new-tile-plus svg { width: 56px; height: 56px; stroke-width: 1.2; }
.cs-new-tile:hover .cs-new-tile-plus { color: #d4d4d8; }
/* Format caption sits low, like the Emporium card's name row but overlaid
   because the rail gives a tile no space beneath it. */
.cs-new-tile-dims {
    position: absolute; left: 0; right: 0; bottom: 10px; z-index: 1;
    text-align: center; color: #fff; pointer-events: none;
    font-family: var(--font-sans); font-size: var(--text-caption); font-weight: 500;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}

/* ── Campaigns masonry wall (mountAssetSurface opts.masonry) ──
 * Pinterest-style packing without a library: fixed columns +
 * grid-auto-rows quantum; each tile's JS-computed `grid-row: span N`
 * encodes its aspect-ratio height (layoutMasonry, views-shared.js).
 * CM_ROW/CM_GAP there MUST match grid-auto-rows/gap here. Chrome
 * (title/channel/actions) lives on a hover scrim, per the
 * .inklet-gallery hover pattern above. */
.campaign-masonry-grid {
    display: grid;
    /* Column counts must stay EVEN at every breakpoint: landscape tiles
       span 2 columns, and on an odd grid a run of them packs 2+2 and
       permanently strands the last column (dense can only backfill when
       square tiles exist nearby). 6 → 3 landscape per row; 4 → 2; 2 → 1. */
    grid-template-columns: repeat(6, 1fr);
    grid-auto-rows: 8px;            /* CM_ROW — span quantum */
    gap: 12px;                      /* CM_GAP — part of the span formula */
    /* dense: landscape tiles span 2 columns; without dense a 2-wide tile
       that can't fit the current row leaves a permanent hole behind it. */
    grid-auto-flow: row dense;
}
@media (max-width: 1500px) { .campaign-masonry-grid { grid-template-columns: repeat(4, 1fr); } }
@media (max-width: 900px)  { .campaign-masonry-grid { grid-template-columns: repeat(2, 1fr); } }

.cm-tile {
    position: relative;
    border-radius: 10px;
    overflow: hidden;
    background: #f1f5f9;
    border: 1px solid var(--border);
}
.cm-tile-img { display: block; width: 100%; height: 100%; }
.cm-tile-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Shimmer mirror of .marketing-prompt-card-thumb.shimmer, tile-scoped. */
.cm-tile-img.shimmer {
    background: linear-gradient(110deg, #f0ede8 30%, #e8e4de 50%, #f0ede8 70%);
    background-size: 200% 100%;
    animation: img-skeleton 1.5s ease-in-out infinite;
}
.cm-tile-img.shimmer img { opacity: 0; transition: opacity 0.3s ease; }
.cm-tile-img.shimmer img.loaded { opacity: 1; }
.cm-tile-noimg {
    display: flex; align-items: center; justify-content: center;
    color: var(--muted); font-size: 0.8rem;
}

/* Panel-count badge (multi-panel assets show panel 1) — the hero
   banner's see-through glass chip (.page-hero .btn) rather than a
   solid dark pill. */
.cm-tile-badge {
    position: absolute; top: 8px; right: 8px; z-index: 2;
    display: inline-flex; align-items: center; gap: 4px;
    padding: 2px 8px; border-radius: 999px;
    background: rgba(255, 255, 255, 0.15); color: #fff;
    border: 1.5px solid rgba(255, 255, 255, 0.6);
    backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
    font-size: 0.72rem; font-weight: 600;
    pointer-events: none;
}
/* Solid accent, not the glass treatment the panel-count badge wears — DRAFT
   has to survive whatever background the thumbnail happens to be. */
.cm-tile-badge-draft {
    background: var(--accent); color: #fff;
    border-color: rgba(255, 255, 255, 0.85);
    letter-spacing: 0.5px;
}

/* Hover chrome. pointer-events:none on the scrim keeps the tile's
 * full-size <a> click-through live; only the buttons re-enable events. */
.cm-tile-overlay {
    position: absolute; inset: 0; z-index: 3;
    display: flex; flex-direction: column; justify-content: space-between;
    padding: 10px;
    /* The 45% stop used to be 0.12 — nearly clear, which was fine when nothing
       sat there. The Edit and eye CTAs now do, and white-on-white measured
       1.37:1 against a light cover (WCAG wants 3.0:1 for UI). 0.55 takes that
       worst case to 3.38:1 while staying lighter than the ISBN Emporium
       gallery's flat 0.62, so the artwork still reads through. */
    background: linear-gradient(rgba(20,20,30,0.78), rgba(20,20,30,0.55) 45%, rgba(20,20,30,0.66));
    opacity: 0; transition: opacity 0.15s ease;
    pointer-events: none;
}
.cm-tile:hover .cm-tile-overlay { opacity: 1; }
.cm-tile-overlay-title {
    color: #fff; font-size: 0.82rem; font-weight: 600; line-height: 1.25;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden;
}
.cm-tile-overlay-channel { color: rgba(255,255,255,0.75); font-size: 0.7rem; margin-top: 2px; }
/* The negative inline margin claws back 4px of each side of the overlay's
   10px padding — the title above wants that breathing room, this row wants
   the width. flex-wrap is a backstop for a tile narrower than anything in use
   today: it wraps rather than letting the circles deform. */
.cm-tile-overlay-actions {
    display: flex; justify-content: center; flex-wrap: wrap;
    gap: 4px; margin: 0 -4px;
}
/* Recently Generated rail: NARROW mixed-aspect tiles (a 9:16 slot is
   ~112px wide) made the icon row wrap 3+1 — cap those to a tidy 2×2
   block. Scoped to .cm-tile-narrow (railTileHtml adds it under ~180px
   computed width) so wide tiles keep the single-row icon layout instead
   of wrapping. */
.cs-section[data-recent-rail] .cm-tile-narrow .cm-tile-overlay-actions {
    max-width: 52px; margin-left: auto; margin-right: auto;
}
/* Centre CTAs — the ISBN Emporium gallery's .inklet-gallery-hover-actions
   treatment (translucent white pill on the dimmed art, matching the
   hero-stream-switcher), with two differences forced by the tiles:

   - width is 100% up to 150px, not a flat 150px. The gallery card is always
     wide enough for 150; a 9:16 rail tile has 144px of content box, so a
     fixed width would overflow it.
   - an explicit height, so the labelled Edit and the icon-only eye below it
     come out exactly the same size rather than one being text-height and the
     other icon-height. */
.cm-tile-overlay-mid {
    display: flex; flex-direction: column; align-items: center;
    gap: 8px; pointer-events: none;
}
.cm-tile-cta {
    width: 100%; max-width: 150px; height: 30px; padding: 0 14px;
    display: flex; align-items: center; justify-content: center;
    font-family: inherit; font-size: 0.85rem; font-weight: 500; color: #fff;
    background: rgba(255, 255, 255, 0.15);
    border: 1px solid rgba(255, 255, 255, 0.4);
    border-radius: 20px;
    cursor: pointer; pointer-events: auto;
    transition: background 0.15s, border-color 0.15s;
}
.cm-tile-cta:hover {
    background: rgba(255, 255, 255, 0.25);
    border-color: rgba(255, 255, 255, 0.5);
    color: #fff;
}
.cm-tile-cta svg { width: 16px; height: 16px; display: block; }
.cm-tile-btn {
    width: 22px; height: 22px; border-radius: 50%; border: none;
    /* Without this the circles are the flex items that give: at 9:16 they were
       squashed to 10x26 ellipses rather than the row overflowing or wrapping.
       Keep it even though the row now fits — it is what stops a circle ever
       rendering as an oval again. */
    flex: 0 0 auto;
    background: rgba(0, 0, 0, 0.5); color: #fff; cursor: pointer; padding: 0;
    display: flex; align-items: center; justify-content: center;
    pointer-events: auto; transition: background 0.15s;
}
.cm-tile-btn:hover { background: var(--accent); }
/* Panel-viewer composite/slideshow toggle. Icon buttons on a dark stage, so it
   takes .cm-tile-btn's treatment directly — translucent white at rest, Inkara
   accent on hover — and the selected segment holds that accent, the same one
   the viewer's active dot uses a few pixels below. Rounded ends with a square
   join so the pair reads as one control rather than two buttons.
   Was inline-styled in views-shared.js with a plain white active state and no
   hover at all, which is what made it look foreign. */
.apv-toggle { display: flex; justify-content: center; margin-bottom: 18px; }
.apv-toggle-btn {
    width: 38px; height: 32px; padding: 0;
    display: flex; align-items: center; justify-content: center;
    border: 1px solid rgba(255, 255, 255, 0.4);
    background: rgba(255, 255, 255, 0.15);
    color: #fff; cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
}
.apv-toggle-btn:first-child { border-radius: 16px 0 0 16px; border-right-width: 0; }
.apv-toggle-btn:last-child  { border-radius: 0 16px 16px 0; }
.apv-toggle-btn:hover,
.apv-toggle-btn.active { background: var(--accent); border-color: var(--accent); }
/* The join: whichever side is accent owns the seam, so the two never show a
   translucent hairline between them. */
.apv-toggle-btn.active:first-child + .apv-toggle-btn { border-left-color: var(--accent); }
.apv-toggle-btn svg { width: 16px; height: 16px; display: block; }
.apv-toggle-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cm-tile-btn.active svg { fill: currentColor; }
.cm-tile-btn-danger:hover { background: #c0392b; }
.cm-tile-btn svg { width: 12px; height: 12px; flex: 0 0 auto; }
/* The classic card paints the trash GLYPH red on hover and fills the
   bookmark glyph accent when active — on the tile's coloured circles that
   becomes red-on-red / accent-on-accent. Keep tile glyphs white; the
   CIRCLE carries the state colour here. */
.cm-tile-btn.campaign-trash-btn:hover svg { color: #fff; }
.cm-tile-btn.campaign-bookmark-btn.active svg { fill: #fff; color: #fff; }

/* ── Social Carousel form v3 (views-carousel-form.js) ──
 * Thumbnail screen tabs, icon chip segments, pill background tabs. */
.cf-scr-tile {
    position: relative;
    width: 76px; height: 76px;
    border-radius: 8px;
    cursor: grab;
    flex: 0 0 auto;
}
.cf-scr-tile.active { outline: 2px solid var(--accent); outline-offset: 2px; }
.cf-scr-num {
    position: absolute; top: -6px; left: -6px; z-index: 2;
    width: 20px; height: 20px; border-radius: 50%;
    background: var(--accent); color: #fff;
    font-size: 11px; font-weight: 600;
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 0 0 2px #fff;
    pointer-events: none;
}
.cf-scr-ind {
    position: absolute; top: 0; bottom: 0; left: -7px; width: 3px;
    background: var(--accent); border-radius: 2px;
    opacity: 0; pointer-events: none; z-index: 3;
    transition: opacity 0.1s;
}
.cf-scr-ind-r { left: auto; right: -7px; }
.cf-scr-add {
    width: 76px; height: 76px; flex: 0 0 auto;
    border: 2px dashed var(--border); border-radius: 8px;
    background: none; cursor: pointer; color: var(--muted);
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 2px; font-family: 'Inter', sans-serif;
}
.cf-scr-add:hover { border-color: var(--accent); color: var(--accent); }

/* ── 2026-08 Emporium-style relayout ── */
/* Top bar: three regions — title+size | Fill-from-book | action cluster. */
.cf-topbar {
    display: flex; align-items: center; justify-content: space-between;
    gap: 16px; min-width: 0;
}
.cf-topbar-left { display: flex; align-items: center; gap: 10px; min-width: 0; }
.cf-topbar-center { flex: 0 1 auto; }
.cf-topbar-right { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
/* Title + size inside the format-dropdown control (one boxed line). */
.cf-title-btn {
    display: inline-flex; align-items: center; gap: 10px;
    padding: 6px 12px; border: 1px solid var(--border); border-radius: 8px;
    background: none; cursor: pointer; min-width: 0; max-width: 100%;
}
.cf-title-btn:hover { border-color: var(--accent); }
.cf-title-text {
    font-family: 'DM Serif Display', serif; font-size: 1.15rem;
    color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cf-title-caret { color: var(--muted); font-size: 12px; line-height: 1; }
/* Emporium-parity header buttons: the editor's exact idiom — neutral
   outline, muted colour, compact 30px height; Generate wears the accent
   fill (Inter 500 — house style: never 600). */
.cf-top-btn {
    height: 30px; padding: 0 10px;
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    border: 1px solid var(--border); border-radius: 6px;
    background: none; color: var(--muted); cursor: pointer;
    font-family: 'Inter', sans-serif; font-size: 13px; font-weight: 500;
    white-space: nowrap;
}
.cf-top-btn:hover { border-color: var(--accent); color: var(--accent); }
.cf-top-btn:disabled { opacity: 0.55; cursor: default; }
.cf-top-btn svg { flex-shrink: 0; }
.cf-top-btn-accent { background: var(--accent); border-color: var(--accent); color: #fff; }
.cf-top-btn-accent:hover { color: #fff; opacity: 0.92; border-color: var(--accent); }
.cf-top-btn-muted { color: var(--muted); opacity: 0.55; }
.cf-top-btn-muted:hover { border-color: var(--border); color: var(--muted); }
/* Boxed square × (Emporium modal-header parity). */
.cf-top-btn-square { width: 30px; padding: 0; font-size: 1.1rem; line-height: 1; }
/* Background-settings cog over the main preview's bottom-left. */
.cf-bg-cog {
    position: absolute; bottom: 8px; left: 8px; z-index: 6;
    width: 25px; height: 25px; padding: 0;
    border: 1px solid var(--border); border-radius: 50%;
    background: rgba(255,255,255,0.88); color: var(--ink); cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 1px 4px rgba(0,0,0,0.22);
}
.cf-bg-cog:hover { color: var(--accent); border-color: var(--accent); }
.cf-bg-cog svg { display: block; }
/* Split-background chrome: paper top bar + left column, white right
   column (Emporium parity). Scoped by .cf-card so the shared .bm-header /
   .bm-modal-inner stay untouched elsewhere. */
.cf-card .bm-header { background: #FAF9F5; }
.cf-card .cf-title-btn { background: #fff; }
.cf-card .cf-top-btn:not(.cf-top-btn-accent) { background: #fff; }
.cf-size-menu {
    position: absolute; top: calc(100% + 4px); left: 0; z-index: 30;
    min-width: 180px; background: #fff; border: 1px solid var(--border);
    border-radius: 8px; box-shadow: 0 8px 24px rgba(26,26,46,0.14);
    padding: 4px; display: flex; flex-direction: column;
}
.cf-size-item {
    text-align: left; padding: 8px 12px; border: none; border-radius: 6px;
    background: none; cursor: pointer; color: var(--ink);
    font-family: 'Inter', sans-serif; font-size: 13px;
    white-space: nowrap;
}
/* Channel-label rows ("Square (1:1) Instagram • Facebook • X") need the
   width; the label spans reuse the Emporium's .me-channel-name idiom. */
.cf-size-menu { min-width: 320px; }
.cf-title-btn .me-channel-name strong,
.cf-size-item .me-channel-name strong { color: var(--ink); font-weight: 600; }
/* Colour tab: swatch + compact hex + Apply on one row. The extra
   .color-hex-picker in the selector out-specifies widget-shared.css's
   `.color-hex-picker .hex-input { flex: 1 }` (same-specificity rules there
   win by load order, which is why a bare .cf-colour-row .hex-input lost). */
.cf-colour-row .color-hex-picker { flex: 0 1 auto; min-width: 0; }
.cf-colour-row .color-hex-picker .hex-input { flex: 0 0 auto; width: 120px; min-width: 0; }
.cf-size-item:hover { background: var(--paper); color: var(--accent); }
.cf-size-item.active { color: var(--muted); cursor: default; }
/* Body: split-background columns (NOT the shared .aap-cols — those belong
   to the A+ form). Paper left half (no keyline card — Emporium parity),
   white right half; #cf-body carries padding:0 so both run edge-to-edge.
   50/50 with no gap, matching the Settings overlay's width:50%. */
.cf-cols { display: flex; gap: 0; flex: 1; min-height: 0; }
.cf-col-left {
    flex: 1 1 50%; min-width: 0; min-height: 0;
    display: flex; flex-direction: column;
    background: #FAF9F5;
    overflow: hidden;
}
.cf-left-pane {
    flex: 1; min-height: 0; overflow-y: auto;
    padding: 14px 20px;
    display: flex; flex-direction: column;
}
.cf-chip-scroll { flex: 1 1 auto; min-height: 60px; overflow-y: auto; }
.cf-col-right {
    flex: 1 1 50%; min-width: 0; min-height: 0;
    display: flex; flex-direction: column;
    background: #fff; padding: 12px 20px 8px;
}
.cf-scr-strip {
    /* Top padding leaves room for the number badge + the corner buttons
       that straddle the tiles' top edge. */
    display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
    flex-shrink: 0; padding: 10px 0 2px 8px;
}
/* margin-top works with the reserved 18px credit row above (screen +
   credit + this ≈ the old screen + 22px), so the controls sit still
   whether or not a photo credit is showing. */
.cf-bg-zone { flex: 1; min-height: 0; overflow-y: auto; padding-right: 4px; margin-top: 13px; }
/* Chips inserted on this screen wear the light Inkara fill (still
   clickable — used, not disabled). Plain fallback first for browsers
   without color-mix. */
.mba-preset-chip.cf-chip-used {
    background: #F6E9F0;
    background: color-mix(in srgb, var(--accent) 8%, #fff);
    border-color: #E3C4D4;
    border-color: color-mix(in srgb, var(--accent) 25%, #fff);
}
/* Hover corner buttons on the screen tiles (inklet-gallery pattern,
   shrunk to tile scale). Both straddle the tile's TOP edge (half
   overflowing, like the number badge): copy first, X rightmost. */
.cf-tile-btn {
    position: absolute; top: -9px; z-index: 4;
    width: 18px; height: 18px; padding: 0; border: none; border-radius: 50%;
    background: rgba(0,0,0,0.55); color: #fff; cursor: pointer;
    box-shadow: 0 0 0 2px #fff;
    display: flex; align-items: center; justify-content: center;
    opacity: 0; transition: opacity 0.12s, background 0.12s;
}
.cf-tile-btn svg { display: block; }
.cf-tile-del { right: -4px; }
.cf-tile-dup { right: 18px; }
.cf-scr-tile:hover .cf-tile-btn { opacity: 1; }
.cf-tile-del:hover { background: #dc2626; }
.cf-tile-dup:hover { background: var(--accent); }
/* Export Options modal (AI assets): embed-code box + params fine print. */
.eo-embed-code {
    text-align: left; font-family: ui-monospace, Menlo, monospace;
    font-size: 11.5px; line-height: 1.5; color: var(--ink);
    background: var(--paper); border: 1px solid var(--border); border-radius: 8px;
    padding: 12px 14px; margin: 14px 0 0;
    overflow-x: auto; white-space: pre; max-height: 160px;
}
.eo-embed-params {
    text-align: left; font-size: 11px; color: var(--muted);
    line-height: 1.6; margin-top: 12px;
}
/* Copy-button starburst: eight rays (box-shadow dots on ::after) fly
   outward and fade on success, alongside the "Copied ✓" label swap. */
.eo-copy-btn { position: relative; }
.eo-copy-btn:disabled { opacity: 1; }
.eo-copy-btn.eo-burst::after {
    content: ''; position: absolute; left: 50%; top: 50%;
    width: 6px; height: 6px; margin: -3px 0 0 -3px; border-radius: 50%;
    background: transparent; pointer-events: none;
    animation: eo-burst 0.7s ease-out forwards;
}
@keyframes eo-burst {
    0% {
        opacity: 1;
        box-shadow: 0 0 0 0 var(--accent), 0 0 0 0 #E8B23A, 0 0 0 0 var(--accent), 0 0 0 0 #E8B23A,
                    0 0 0 0 var(--accent), 0 0 0 0 #E8B23A, 0 0 0 0 var(--accent), 0 0 0 0 #E8B23A;
    }
    70% { opacity: 1; }
    100% {
        opacity: 0;
        box-shadow: 0 -36px 0 1px var(--accent), 26px -26px 0 0 #E8B23A, 38px 0 0 1px var(--accent), 26px 26px 0 0 #E8B23A,
                    0 36px 0 1px var(--accent), -26px 26px 0 0 #E8B23A, -38px 0 0 1px var(--accent), -26px -26px 0 0 #E8B23A;
    }
}
/* Preview modal (Instagram-style pager over the form). */
.cf-preview-overlay { z-index: 100000; }
.cf-preview-stage {
    position: relative; background: #FAF9F5; border-radius: 10px;
    padding: 40px 48px 28px;
    display: flex; flex-direction: column; align-items: center;
    max-width: 92vw; max-height: 92vh; overflow: auto;
}
.cf-preview-close {
    position: absolute; top: 8px; right: 12px;
    background: none; border: none; cursor: pointer;
    font-size: 1.6rem; line-height: 1; color: var(--muted); padding: 4px;
}
.cf-preview-close:hover { color: var(--ink); }
.cf-chip-btn {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 7px 12px; border: 1px solid var(--border); border-radius: 8px;
    background: var(--paper); color: var(--ink); cursor: pointer;
    font-family: 'Inter', sans-serif; font-size: 12.5px; font-weight: 500;
    transition: all 0.12s;
}
.cf-chip-btn svg { width: 14px; height: 14px; }
.cf-chip-btn:hover { border-color: var(--accent); color: var(--accent); }
.cf-chip-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.cf-chip-btn.active:hover { color: #fff; }
.cf-bg-btn {
    padding: 6px 14px; border: 1px solid var(--border); border-radius: 8px;
    background: var(--paper); color: var(--ink); cursor: pointer;
    font-family: 'Inter', sans-serif; font-size: 12.5px; font-weight: 500;
    transition: all 0.12s;
}
.cf-bg-btn:hover { border-color: var(--accent); color: var(--accent); }
.cf-bg-btn:disabled,
.cf-bg-btn:disabled:hover { opacity: 0.45; cursor: not-allowed; border-color: var(--border); color: var(--muted); }
.cf-bg-btn.active { border-color: var(--accent); color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }

/* Recent photo picks (migration 207) — thumb strips in the photo pickers
   (Asset Library > Photo Library, carousel form PHOTO tab). Reuses the
   Marketing Assets panel X (.branding-aplus-del); its hover-reveal is
   parent-scoped, so each host cell class re-declares it. */
.al-recent-cell,
.cf-recent-cell {
    position: relative;
    border-radius: 6px;
    overflow: hidden;
    background: #f1f5f9;
}
.al-recent-cell { aspect-ratio: 4 / 3; }
.cf-recent-cell { aspect-ratio: 1 / 1; }
.al-recent-cell a, .al-recent-cell img,
.cf-recent-cell img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.cf-recent-cell img { cursor: pointer; border: 1px solid var(--border); border-radius: 6px; box-sizing: border-box; }
.al-recent-cell:hover .branding-aplus-del,
.cf-recent-cell:hover .branding-aplus-del { opacity: 1; }
/* Eye (open full size) sits left of the ×; same circle, accent hover
   instead of the delete red. Feather svg centred inside. */
.al-recent-eye { right: 26px; }
.al-recent-eye:hover { background: var(--accent); }
.al-recent-eye svg { width: 12px; height: 12px; vertical-align: -2px; }

/* Pending (Generating…) masonry tile */
.cm-tile-pending .campaign-asset-pending-overlay { z-index: 2; }
.cm-tile-pending .cm-tile-img img { filter: grayscale(100%); opacity: 0.6; }
.cm-tile-pending-label {
    position: absolute; left: 10px; bottom: 8px; z-index: 3;
    font-size: 0.75rem; font-weight: 600; color: #fff;
    text-shadow: 0 1px 3px rgba(0,0,0,0.7);
}

/* ── Marketing modal (channel previews as cards) ──
 * Replaces the older per-channel sub-tab pattern: one card per
 * (template × channel) pair, three-column grid, each card lazy-
 * loads its widget-renderer preview into a fixed-size thumbnail
 * via the transform:scale pattern. Click a card to open the full-
 * size preview through the shared inklet-preview-shell. The grid
 * lives inside .tbm-modal-inner alongside .tbm-top-bar — flex:1 so
 * it fills the remaining vertical space, overflow:auto so long
 * lists scroll within the modal rather than the page. */
.mkt-cards-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1rem;
    padding: 20px;
    flex: 1;
    overflow: auto;
    align-content: start;
}
/* Inherits .marketing-prompt-card's column flex + padding + border;
 * .mkt-card just adds the click affordance + hover lift. */
.mkt-cards-grid .mkt-card {
    cursor: pointer;
    transition: box-shadow 0.15s, transform 0.15s;
}
.mkt-cards-grid .mkt-card:hover {
    box-shadow: 0 4px 12px rgba(0,0,0,0.10);
    transform: translateY(-2px);
}
/* Thumbnail box — uses the same 16:9 / max-200px sizing as the
 * Assets (AI) card thumbnails (inherited from .marketing-prompt-card-
 * thumb on the same element). We only override display/centring so
 * the scaled-down preview wrap can flex-centre inside the box.
 * Applies in the dedicated `.mkt-cards-grid` AND in the campaign-asset
 * grid (Book modal Marketing Assets tab, where inklet cards are
 * interleaved with AI prompt cards). */
.mkt-cards-grid .mkt-card-thumb,
.campaign-asset-card .mkt-card-thumb {
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    /* width / max-width / aspect-ratio / border / background all
       come from .marketing-prompt-card-thumb. */
}
.mkt-thumb-scaler-wrap {
    overflow: hidden;
    /* width/height set inline by fitScalerToThumb so the scaled
       footprint flex-centres inside .mkt-card-thumb. */
}
.mkt-thumb-scaler-inner {
    transform-origin: top left;
    /* width/height/transform set inline by fitScalerToThumb. */
}
.mkt-card-loading,
.mkt-card-error {
    font-size: 0.85rem;
    color: var(--muted);
    padding: 16px;
    text-align: center;
}
.mkt-card-error { color: #b91c1c; }

/* ── Matching Fonts shared panel (Books modal + Book Cover Text accordion) ── */
.font-pair-grid {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 1rem;
    align-items: start;
}
.font-pair-col-desc {
    font-size: 0.9rem;
    color: var(--muted);
    line-height: 1.45;
}
/* PRIMARY + SECONDARY columns — let the inner lozenge fill the column
   width rather than capping at .bcfp-card's default max-width:400px. */
.font-pair-col-primary .bcfp-card,
.font-pair-col-secondary .bcfp-card {
    max-width: 100%;
}
.font-pair-section { display: flex; flex-direction: column; gap: 4px; }
.font-pair-section .me-label {
    /* Same as canvas-panel labels (e.g. "Cover Height (px)"). */
    display: block;
    font-size: 12px;
    font-weight: 500;
    color: var(--muted);
    margin-bottom: 4px;
    letter-spacing: 0;
    text-transform: none;
    font-family: 'Inter', sans-serif;
}

/* ─── BISAC/Thema "Suggest" pick modal ──────────────────────────────
   Each suggestion is rendered as a clickable row with a checkbox, the
   canonical code + description (accent rose), and a short LLM-supplied
   reason underneath. Confirming the modal appends the checked codes
   to the BISAC/Thema text input on the event-edit form.
*/
.suggest-row {
    display: flex;
    gap: 0.75rem;
    align-items: flex-start;
    padding: 0.75rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    cursor: pointer;
    transition: background 0.1s;
    margin-bottom: 0.5rem;
}
.suggest-row:hover { background: #faf7f9; }
.suggest-row input[type=checkbox] { margin-top: 3px; flex-shrink: 0; }
.suggest-row-code {
    font-weight: 600;
    color: var(--accent);
    font-size: 0.9rem;
    margin-bottom: 0.25rem;
}
.suggest-row-reason { color: var(--muted); font-size: 0.85rem; line-height: 1.4; }

/* ─── Shared book-grid helper (views-book-grid.js) ──────────────────
   The helper emits a tab bar with a right-pinned "View all >" link
   (Overview only). Same accent rose as the rest of the dashboard;
   subtle hover underline so it reads as a link without competing with
   the tab buttons. */
.book-grid-view-all {
    color: var(--accent);
    font-size: 0.85rem;
    text-decoration: none;
    font-weight: 500;
    white-space: nowrap;
}
.book-grid-view-all:hover { text-decoration: underline; color: var(--accent-dark); }
/* Stream total, flush right on the tab bar. Same family/size as .tab-btn so it
   sits on the same optical line, but italic and one weight lighter so it reads
   as a caption rather than a fifth tab. margin-left:auto does the pushing when
   no View-all spacer is present (the Books page never renders one). */
.book-grid-total {
    margin-left: auto;
    padding: 0.6rem 0.4rem 0.6rem 1.2rem;
    font-family: 'Inter', sans-serif;
    font-size: 0.85rem;
    font-style: italic;
    font-weight: 400;
    color: var(--muted);
    white-space: nowrap;
}
/* Optional section title above the tab bar (Overview passes title:'Books').
   Black DM Serif, in line with .card-header h3 / page hero conventions. */
.book-grid-title {
    font-family: 'DM Serif Display', serif;
    font-size: 1.5rem;
    font-weight: 400;
    color: var(--ink);
    margin: 0 0 0.75rem 0;
    letter-spacing: -0.3px;
}
/* Match the Overview right-rail card headers (Events, Trending) to the
   book-grid-title above. Scoped to .overview-mid-right-col so other
   .card-header h3 instances elsewhere keep the global 1.1rem sizing. */
.overview-mid-right-col .card-header h3 {
    font-size: 1.5rem;
    font-weight: 400;
}
/* Bookmark tab button — matches the Campaigns page's bookmark toggle
   (.campaigns-bookmark-btn): circle outline, neutral by default,
   accent on hover/active, glyph filled when this tab is the live view. */
.book-grid-bookmarks-tab {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    align-self: center;       /* don't stretch to .tab-bar's tallest child */
    flex: 0 0 auto;
    width: 32px;              /* explicit square so border-radius:999px = circle */
    height: 32px;
    padding: 0;               /* size driven by width/height, not padding */
    box-sizing: border-box;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--card-bg);
    color: var(--ink);
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s, box-shadow 0.15s;
}
.book-grid-bookmarks-tab svg {
    width: 16px;
    height: 16px;
    display: block;
}
.book-grid-bookmarks-tab:hover,
.book-grid-bookmarks-tab.active {
    border-color: var(--accent);
    color: var(--accent);
}
.book-grid-bookmarks-tab.active svg { fill: var(--accent); }
/* 20px gap between the bookmark and the first text tab — the helper
   uses gap:0 on .tab-bar, so a margin-right here is what produces the
   indent. */
.book-grid-tab-bar .book-grid-bookmarks-tab { margin-right: 20px; }
/* The standard .tab-bar paints a 2px grey baseline that .tab-btn.active
   overrides with accent. On the book-grid we want that baseline to start
   AFTER the bookmark button — drop it from the tab-bar itself and apply
   it to each .tab-btn instead. With gap:0 the per-button borders abut
   into a continuous line; active still flips to accent. Bookmark is NOT
   a .tab-btn, so no baseline under it. */
.tab-bar.book-grid-tab-bar { border-bottom: none; }
.tab-bar.book-grid-tab-bar .tab-btn {
    border-bottom: 2px solid var(--border);
    margin-bottom: 0;
}
.tab-bar.book-grid-tab-bar .tab-btn.active { border-bottom-color: var(--accent); }

/* ── Asset Library upload progress rows ─────────────────────────────
   Per-file status row rendered in the upload modal while files are
   uploading. One row per file, stacked vertically. States:
     • In flight  → fill bar at N% + percentage text
     • Done       → .al-upload-row-done shows ✓
     • Error/skip → .al-upload-row-error shows ✗ + reason
   Inherits theme tokens (accent/border) so it tracks the rest of the
   admin UI. */
.al-upload-row { display: flex; gap: 8px; align-items: center; margin: 4px 0; font-size: 13px; }
.al-upload-row-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.al-upload-row-bar { width: 160px; height: 8px; background: var(--border); border-radius: 4px; overflow: hidden; }
.al-upload-row-bar-fill { height: 100%; background: var(--accent); transition: width 200ms ease; width: 0%; }
.al-upload-row-pct { flex: 0 0 auto; color: var(--muted); min-width: 36px; text-align: right; }
.al-upload-row-done { flex: 0 0 auto; color: #16a34a; font-weight: 600; }
.al-upload-row-error { flex: 0 0 auto; color: #dc2626; font-weight: 600; }

/* --- Content Pages (CMS) --- */
/* Listing-table preview snippet under the page title (2-3 lines, muted). */
.cp-preview {
    font-size: 0.8rem;
    color: var(--muted);
    margin-top: 4px;
    max-width: 540px;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.cp-public-badge {
    display: inline-block;
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.5px;
    color: #15803d;
    background: #dcfce7;
    border: 1px solid #bbf7d0;
    border-radius: 4px;
    padding: 1px 5px;
    vertical-align: middle;
    margin-left: 6px;
}
/* Edit modal: is_public inline checkbox + multi-stream checklist + Quill. */
.cp-inline-check {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-weight: 400;
    cursor: pointer;
}
/* Stream picker in the Content Pages modal — the shared account-grouped
   renderer (renderStreamAccessCheckboxes), same control and same account
   headings as the AI-prompt modal's #ap-streams-list. */
#cp-streams-list .ust-acct-head { color: var(--muted); font-size: 0.72rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; }
#cp-streams-list > div + div .ust-acct-head { margin-top: 0.35rem; }
.cp-editor {
    background: #fff;
}
.cp-editor .ql-container {
    min-height: 220px;
    font-size: 0.95rem;
    font-family: 'Inter', sans-serif;
}
.cp-editor .ql-editor {
    min-height: 220px;
}
/* Revision-preview rendered HTML box. */
.cp-preview-html {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 12px 14px;
    background: #fafafa;
    max-height: 50vh;
    overflow-y: auto;
}
.cp-preview-html h1, .cp-preview-html h2, .cp-preview-html h3,
.cp-preview-html h4, .cp-preview-html h5 { margin: 0.6em 0 0.3em; line-height: 1.25; }
.cp-preview-html p { margin: 0 0 0.6em; }
.cp-preview-html ul, .cp-preview-html ol { margin: 0 0 0.6em 1.2em; }

/* Content Pages add/edit modal — fills ~95% of the viewport (overrides the
   760px / auto-height aic-modal-inner) so the rich-text editor has real room.
   Flex column: fixed header + footer, scrollable body in between. */
.cp-edit-modal-inner {
    width: 95vw;
    max-width: 95vw;
    height: 95vh;
    max-height: 95vh;
    display: flex;
    flex-direction: column;
}
.cp-edit-modal-inner .cp-edit-body {
    flex: 1 1 auto;
    min-height: 0;
}
.cp-edit-modal-inner .cp-editor .ql-container,
.cp-edit-modal-inner .cp-editor .ql-editor {
    min-height: 48vh;
}

/* --- Content Pages: category + visibility window --- */
.cp-date-row {
    display: flex;
    gap: 12px;
}
.cp-date-row > div { flex: 1; }
.cp-sub-label {
    display: block;
    font-size: 0.78rem;
    color: var(--muted);
    margin-bottom: 3px;
}
.cp-window-badge {
    display: inline-block;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 2px 7px;
    border-radius: 10px;
}
.cp-window-always   { background: #eef0f4; color: #55607a; }
.cp-window-live     { background: #e3f5ea; color: #1d7a45; }
.cp-window-scheduled{ background: #fff3d9; color: #8a6212; }
.cp-window-expired  { background: #f2f2f4; color: #8b8b96; }
.cp-window-range {
    margin-top: 3px;
    font-size: 0.72rem;
    color: var(--muted);
}

/* --- Content Pages: images in the body ---
   Every rule is namespaced under .cp-editor. A bare `.ql-editor img` would
   reach the 8 other Quill instances in this app (multi-editor + inklet
   engines), whose images are inline by design. */

/* Cap the editor's text column so a pixel width here means the same thing on
   the surfaces that render the saved HTML (the notification modal below is
   sized to match). The whole block — label, toolbar and editor — is capped
   and centred together, so it reads as a document column rather than a
   narrow strip of text floating in a 95vw box. */
.cp-pe-pane[data-cp-pane="content"] .ai-prompt-field {
    max-width: 680px;
    margin: 0 auto;
}
.cp-editor .ql-editor {
    max-width: 620px;
    margin: 0 auto;
}
.cp-editor .ql-editor img.cp-img {
    display: block;
    max-width: 100%;
    /* height:auto is load-bearing everywhere an image renders: without it the
       intrinsic height sticks and the locked aspect ratio breaks at exactly
       the moment it is being relied on. */
    height: auto;
    margin: 0.75rem auto;
    cursor: pointer;
}
.cp-editor .ql-editor img.cp-img.cp-img-selected {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
/* Two stacked images necessarily get an empty <p><br></p> between them — a
   BlockEmbed has length 1 with no implicit newline, so Quill needs an
   addressable position there. Keep that gap tight rather than a full para. */
.cp-editor .ql-editor p:empty,
.cp-preview-html p:empty,
.cp-notif-body p:empty { margin: 0; min-height: 0.4em; }

/* Resize overlay. position:fixed so it tracks getBoundingClientRect()
   directly and survives .cp-edit-body scrolling with one reposition call.
   Lives in the modal, never inside .ql-editor. */
.cp-resize-overlay {
    position: fixed;
    display: none;
    pointer-events: none;
    z-index: 100002;          /* above .series-info-save-overlay (100001) */
    border: 1px solid var(--accent);
}
.cp-resize-handle {
    position: absolute;
    bottom: -6px;
    width: 12px;
    height: 12px;
    background: var(--accent);
    border: 2px solid #fff;
    border-radius: 2px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
    pointer-events: auto;
}
.cp-rh-left  { left: -6px;  cursor: nesw-resize; }
.cp-rh-right { right: -6px; cursor: nwse-resize; }

/* Images on the read-only surfaces. */
.cp-preview-html img,
.login-terms-box img {
    max-width: 100%;
    height: auto;
    display: block;
    margin: 0.5rem 0;
}

/* --- Overview notification modal ---
   Reuses the .series-info-save-* chrome (which brings Escape/backdrop/LIFO
   stacking via lib-modal) but widens it and left-aligns the body, since this
   renders a whole content page rather than a one-line confirm. */
.cp-notif-modal {
    max-width: 684px;         /* 620px of content inside the 32px padding */
    width: 92%;
}
.cp-notif-quill {
    /* Deliberately NOT .action-modal-quill — that class carries the
       meQuillMove animation and is shared by ~10 live dialogs. */
    width: 80px;
    height: auto;
}
.cp-notif-body {
    text-align: left;
    max-height: 58vh;
    overflow-y: auto;
    margin: 0 0 18px;
    font-size: 0.95rem;
    line-height: 1.5;
    color: var(--ink);
}
.cp-notif-body h1, .cp-notif-body h2, .cp-notif-body h3,
.cp-notif-body h4, .cp-notif-body h5 { margin: 0.7em 0 0.35em; line-height: 1.25; }
.cp-notif-body p { margin: 0 0 0.7em; }
.cp-notif-body ul, .cp-notif-body ol { margin: 0 0 0.7em 1.2em; }
.cp-notif-body img {
    max-width: 100%;
    height: auto;
    display: block;
    margin: 0.75rem auto;
}
.cp-notif-count {
    font-size: 0.75rem;
    color: var(--muted);
    margin: 0 0 10px;
}
/* inline-flex, NOT flex. A block-level <label> stretches to the full modal
   width, which left ~224px of invisible click target either side of the
   words — 17px above the Close button. Clicking there silently ticked the
   box on the way to Close, permanently dismissing a notification the user
   only meant to close. The label must hug its own content; the parent's
   text-align:center still centres it. */
.cp-notif-again {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 0.85rem;
    color: var(--muted);
    margin: 0 0 18px;
    cursor: pointer;
}
.cp-notif-again input { cursor: pointer; }

/* --- Terms of Use on the new-password (invite) form --- */
/* ~4-line scrollable box rendering the published Terms HTML pre-login. */
.login-terms-box {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.5rem 0.75rem;
    max-height: 6.5em;          /* ≈ 4 lines */
    overflow-y: auto;
    font-size: 0.8rem;
    line-height: 1.4;
    color: var(--ink);
    background: #fafafa;
    text-align: left;
}
.login-terms-box h1, .login-terms-box h2, .login-terms-box h3,
.login-terms-box h4, .login-terms-box h5 { font-size: 0.85rem; margin: 0.4em 0 0.2em; }
.login-terms-box p { margin: 0 0 0.4em; }
.login-terms-box ul, .login-terms-box ol { margin: 0 0 0.4em 1.1em; padding: 0; }
.login-agree {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.8rem;
    color: var(--ink);
    margin: 0 0 1rem;
    cursor: pointer;
    text-align: left;
}
.login-agree input { flex: 0 0 auto; }

/* ─── Training Sessions ─────────────────────────────────────────────
   Floating "Book a Training Session" FAB (calendar), stacked ABOVE the
   feedback bubble but at a LOWER z-index (900 < the 99999+ modal overlays)
   so it hides under the Books modal / Canvas editor. */
.ie-training-btn {
    position: fixed;
    bottom: 76px;              /* above the 44px feedback bubble at bottom:24px */
    right: 24px;
    z-index: 900;              /* below modal overlays (>=99999) → hidden under modals */
    width: 44px; height: 44px;
    border-radius: 50%;
    border: none;
    background: var(--accent);
    color: #fff;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 2px 12px rgba(0,0,0,0.2);
    transition: background 0.15s, transform 0.15s;
}
.ie-training-btn:hover { background: #8a1f3c; transform: scale(1.05); }

/* Booking / manage modal — reuses .series-info-save-overlay/modal, wider. */
.training-modal { max-width: 560px; text-align: left; }
.training-modal .series-info-save-title,
.training-modal .series-info-save-sub { text-align: center; }
.training-manage { max-width: 720px; display: flex; flex-direction: column; max-height: 88vh; }
.training-manage-assign { margin: 0 0 0.6rem; }
.training-warn { background:#fdecea; color:#a12734; border:1px solid #f5c6cb; border-radius:6px; padding:8px 12px; font-size:0.82rem; margin:0 0 0.6rem; }
.training-manage-footer { padding-top: 0.9rem; margin-top: 0.5rem; border-top: 1px solid var(--border); text-align: right; }

.training-grid-scroll { max-height: 55vh; overflow-y: auto; padding: 2px; }
.training-grid-tall { max-height: 60vh; }
.training-week { margin-bottom: 0.85rem; }
.training-week + .training-week { border-top: 1px dashed var(--border); padding-top: 0.75rem; }
.training-grid { display: grid; gap: 6px; }
.training-col-head { font-size: 0.72rem; font-weight: 600; text-align: center; color: var(--muted); padding: 2px 0 4px; }

.training-cell {
    border: 1px solid var(--border); border-radius: 6px;
    padding: 5px 3px; min-height: 34px; font-size: 0.78rem; line-height: 1.15;
    text-align: center; background: #f4f4f5; color: #9aa0a6;
}
.training-cell-time { display: block; }
.training-cell-sa { display: block; font-size: 0.62rem; font-weight: 400; opacity: 0.9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.training-cell.is-available { background: #fff; border-color: var(--accent); color: var(--accent); cursor: pointer; font-weight: 600; }
.training-cell.is-available:hover { background: var(--accent); color: #fff; }
.training-cell.is-unavailable { opacity: 0.5; }
.training-cell-manage { cursor: pointer; }
.training-cell-manage.is-selected { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
.training-cell-manage.is-booked.is-selected { background: #8a1f3c; border-color: #8a1f3c; }
.training-cell-manage.is-booked:not(.is-selected) { background: #fff; border: 1px dashed #c0392b; color: #c0392b; text-decoration: line-through; }
.training-cell.is-past { opacity: 0.38; cursor: not-allowed; }

/* Booked state (booking modal) */
.training-booked { text-align: center; padding: 0.4rem 0 0.2rem; }
.training-booked-line { margin: 0 0 0.75rem; }
.training-join { margin: 0.25rem 0; }
.training-hint { font-size: 0.75rem; color: var(--muted); margin: 0.6rem auto; max-width: 380px; }
.training-cal-links { font-size: 0.82rem; color: var(--muted); margin: 0.85rem 0 0.25rem; }
.training-cal-links a { color: var(--accent); }
.training-change { margin-top: 0.9rem; }
.training-change a { color: var(--muted); font-size: 0.85rem; }

/* List status badges */
.ts-badge { display: inline-block; padding: 2px 9px; border-radius: 10px; font-size: 0.75rem; font-weight: 600; }
.ts-badge-available { background: #e8f5e9; color: #1a7f37; }
.ts-badge-booked { background: #fdecef; color: #af2860; }

/* ─── Staged Amazon A+ — Branding sub-tab cards (migration 171) ───── */
/* The A+ Content pane stacks two 2-column sections — 970×300 above,
   300×300 below. This rule is the divider on the second one. */
.branding-aplus-section {
    margin-top: 1.5rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--border);
}
.branding-aplus-thumbs { display: flex; flex-direction: column; gap: 6px; max-width: 280px; }
.branding-aplus-thumb {
    display: block; width: 100%; height: auto; border: 1px solid var(--border);
    border-radius: 4px; aspect-ratio: 1940 / 600; object-fit: cover;
}
/* A+ generation history (2026-08-02) — swapped into #branding-aplus-host
   over the static active-3 column. SELECTED column (the stacked
   Top/Middle/Bottom set the composed assembly uses, accent-outlined) +
   a 2-up pool of every other generated panel, newest first. Replace a
   selected panel by dragging a pool thumb onto it. */
.branding-aplus-history { display: flex; gap: 18px; align-items: flex-start; }
.branding-aplus-selected {
    display: flex; flex-direction: column; gap: 6px;
    width: 218px; flex-shrink: 0;
}
.branding-aplus-col-label {
    font-size: 0.8rem; font-weight: 600; color: var(--muted);
    text-transform: uppercase; letter-spacing: 0.04em;
}
.branding-aplus-drop { position: relative; }
.branding-aplus-drop a { display: block; cursor: zoom-in; }
.branding-aplus-drop .branding-aplus-thumb {
    outline: 2px solid var(--accent); outline-offset: -2px;
}
.branding-aplus-drop.drag-over .branding-aplus-thumb {
    outline-width: 3px; filter: brightness(0.85);
}
.branding-aplus-pool { flex: 1 1 auto; max-width: 452px; }
.branding-aplus-pool-hint {
    font-size: 0.8rem; font-style: italic; font-weight: 400;
    color: var(--muted); margin-bottom: 6px;
}
.branding-aplus-pool-grid {
    display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px;
}
.branding-aplus-cell { position: relative; cursor: grab; }
.branding-aplus-cell.dragging { opacity: 0.5; }
.branding-aplus-cell a { display: block; cursor: zoom-in; }
.branding-aplus-del {
    position: absolute; top: 2px; right: 2px; width: 20px; height: 20px;
    border: none; border-radius: 50%; background: rgba(0, 0, 0, 0.55);
    color: #fff; font-size: 13px; line-height: 20px; text-align: center;
    cursor: pointer; padding: 0; opacity: 0; transition: opacity 0.15s;
}
.branding-aplus-cell:hover .branding-aplus-del { opacity: 1; }
.branding-aplus-del:hover { background: #c0392b; }

/* A+ (300x300px) — square panels (migration 191). Own class family rather
   than reusing .branding-aplus-thumb, which hard-codes aspect-ratio 1940/600
   and would squash these. Layout differs too: the SELECTED three sit in a
   horizontal row ABOVE a 4-up pool, where the wide set is a vertical column
   BESIDE a 2-up pool. The size-agnostic bits (.branding-aplus-col-label,
   -pool-hint, -del) are shared. */
.branding-aplussq-history { display: flex; flex-direction: column; gap: 14px; }
.branding-aplussq-selected-row,
.branding-aplussq-pool-grid { display: grid; gap: 6px; max-width: 452px; }
.branding-aplussq-selected-row { grid-template-columns: repeat(3, 1fr); }
.branding-aplussq-pool-grid { grid-template-columns: repeat(4, 1fr); }
.branding-aplussq-thumb {
    display: block; width: 100%; height: auto; aspect-ratio: 1 / 1;
    object-fit: cover; border: 1px solid var(--border); border-radius: 4px;
}
/* Landscape (1200×630) + vertical (1080×1920) family galleries (migration
   209) reuse the square gallery's structure classes; only the thumb aspect
   and pool column count differ per family. The vertical pool packs 6-up so
   the tall thumbs stay compact. */
.branding-aplusland-thumb {
    display: block; width: 100%; height: auto; aspect-ratio: 1200 / 630;
    object-fit: cover; border: 1px solid var(--border); border-radius: 4px;
}
.branding-aplusvert-thumb {
    display: block; width: 100%; height: auto; aspect-ratio: 1080 / 1920;
    object-fit: cover; border: 1px solid var(--border); border-radius: 4px;
}
/* ── Marketing Assets > Backgrounds: the panel bank ──────────────────
   One section per background format: a plain left-aligned wrap of equal
   cells, oldest first, closed by the dashed [+ Generate] tile. Every cell in
   a section — thumbs and tile alike — takes its width + aspect from the same
   bg-cell-* modifier, so the row stays uniform whatever the format. */
.bg-fam-section { margin-bottom: 1.75rem; }
.bg-fam-section:last-child { margin-bottom: 0; }
.bg-fam-head {
    font-size: 0.8rem; font-weight: 600; letter-spacing: 0.02em;
    color: var(--ink); margin-bottom: 0.6rem;
}
.bg-fam-grid { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-start; }
.bg-cell { position: relative; }
.bg-cell a { display: block; width: 100%; height: 100%; cursor: zoom-in; }
.bg-cell img {
    display: block; width: 100%; height: 100%; object-fit: cover;
    border: 1px solid var(--border); border-radius: 6px;
}
.bg-cell:hover .branding-aplus-del { opacity: 1; }
/* Per-format cell geometry. Widths are chosen so each format's row reads at a
   comfortable size rather than to one shared width — a 3.2:1 banner and a
   9:16 story need very different footprints to be legible as thumbnails. */
.bg-cell-wide { width: 268px; aspect-ratio: 970 / 300; }
.bg-cell-sq   { width: 124px; aspect-ratio: 1 / 1; }
.bg-cell-land { width: 200px; aspect-ratio: 1200 / 630; }
.bg-cell-vert { width: 104px; aspect-ratio: 1080 / 1920; }
.bg-cell-port { width: 124px; aspect-ratio: 1080 / 1350; }
/* The [+ Generate] tile — dashed, muted, and the same box as its siblings. */
.bg-add {
    display: flex; flex-direction: column; align-items: center;
    justify-content: center; gap: 4px;
    background: none; border: 2px dashed var(--border); border-radius: 6px;
    color: var(--muted, #8a8477); cursor: pointer; padding: 4px;
    font-family: inherit; text-align: center;
    transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.bg-add:hover:not(:disabled) {
    border-color: var(--accent); color: var(--accent);
    background: rgba(175, 40, 96, 0.04);
}
.bg-add:disabled { cursor: default; opacity: 0.55; }
.bg-add-plus { font-size: 1.4rem; line-height: 1; font-weight: 300; }
.bg-add-label { font-size: 0.72rem; line-height: 1.2; }
/* The narrow formats can't fit the word at full size. */
.bg-cell-vert.bg-add .bg-add-label { font-size: 0.62rem; }

/* Portrait 4:5 (migration 210) is CROPPED from the vertical set rather than
   generated, but its gallery is identical — the derivation is invisible here. */
.branding-aplusport-thumb {
    display: block; width: 100%; height: auto; aspect-ratio: 1080 / 1350;
    object-fit: cover; border: 1px solid var(--border); border-radius: 4px;
}
.branding-aplusland-pool-grid,
.branding-aplusvert-pool-grid,
.branding-aplusport-pool-grid { display: grid; gap: 6px; max-width: 452px; }
.branding-aplusland-pool-grid { grid-template-columns: repeat(3, 1fr); }
.branding-aplusvert-pool-grid { grid-template-columns: repeat(6, 1fr); }
.branding-aplusport-pool-grid { grid-template-columns: repeat(5, 1fr); }
.branding-aplussq-drop { position: relative; }
.branding-aplussq-drop a { display: block; cursor: zoom-in; }
.branding-aplussq-drop .branding-aplussq-thumb,
.branding-aplussq-drop .branding-aplusland-thumb,
.branding-aplussq-drop .branding-aplusvert-thumb,
.branding-aplussq-drop .branding-aplusport-thumb {
    outline: 2px solid var(--accent); outline-offset: -2px;
}
.branding-aplussq-drop.drag-over .branding-aplussq-thumb,
.branding-aplussq-drop.drag-over .branding-aplusland-thumb,
.branding-aplussq-drop.drag-over .branding-aplusvert-thumb,
.branding-aplussq-drop.drag-over .branding-aplusport-thumb {
    outline-width: 3px; filter: brightness(0.85);
}
.branding-aplussq-cell { position: relative; cursor: grab; }
.branding-aplussq-cell.dragging { opacity: 0.5; }
.branding-aplussq-cell a { display: block; cursor: zoom-in; }
.branding-aplussq-cell:hover .branding-aplus-del { opacity: 1; }
/* Interior-derived squares are padded crops of the book's own pages, not AI
   artwork — worth telling apart at a glance in a mixed pool. */
.branding-aplussq-badge {
    position: absolute; bottom: 2px; left: 2px; padding: 1px 5px;
    border-radius: 3px; background: rgba(0, 0, 0, 0.55); color: #fff;
    font-size: 0.65rem; letter-spacing: 0.03em; pointer-events: none;
}
/* Branding > Interior Images (migration 186) — source-spread thumb row with
   checkbox selection, plus the extracted-crops gallery below it. */
/* Cross-format fallback note (2026-08-02) — names the sibling format whose
   images are being shown when this format has none of its own. */
.interior-fallback-note {
    background: #eef4fd; color: #2b5a9e; border: 1px solid #cfe0f7;
    border-radius: 6px; padding: 6px 10px; font-size: 0.8rem;
    margin-bottom: 8px;
}
.interior-src-row { display: flex; flex-wrap: wrap; gap: 8px; }
.interior-src-thumb {
    position: relative; width: 132px;
    border: 2px solid transparent; border-radius: 6px; padding: 2px;
    transition: border-color 0.15s;
}
.interior-src-thumb.selected { border-color: var(--accent); }
.interior-src-thumb a { display: block; cursor: zoom-in; }
.interior-src-thumb img {
    display: block; width: 100%; height: 92px; object-fit: cover;
    border: 1px solid var(--border); border-radius: 4px; background: #f4f4f6;
}
.interior-src-check {
    position: absolute; top: 7px; left: 7px; width: 18px; height: 18px;
    border: 1px solid var(--border); border-radius: 4px;
    background: rgba(255, 255, 255, 0.92); cursor: pointer; padding: 0;
    line-height: 1; font-size: 12px; color: #fff;
}
.interior-src-check:hover { border-color: var(--accent); }
.interior-src-thumb.selected .interior-src-check {
    background: var(--accent); border-color: var(--accent);
}
.interior-src-thumb.selected .interior-src-check::after { content: '\2713'; }
.interior-src-thumb.generating { opacity: 0.55; }
.interior-src-badge {
    position: absolute; bottom: 8px; left: 50%; transform: translateX(-50%);
    background: rgba(0, 0, 0, 0.65); color: #fff; font-size: 0.68rem;
    padding: 2px 8px; border-radius: 10px; white-space: nowrap;
}
.interior-extract-title {
    margin: 1rem 0 0.5rem; font-size: 0.8rem; font-weight: 600;
    color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em;
}
.interior-extract-grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 8px;
}
.interior-extract-card { position: relative; }
.interior-extract-card a { display: block; cursor: zoom-in; }
.interior-extract-card img {
    display: block; width: 100%; height: 120px; object-fit: cover;
    border: 1px solid var(--border); border-radius: 4px; background: #f4f4f6;
}
/* Square (600x600) derivatives — contain-fit so the padded letterbox reads as
   part of the image, rather than being cropped straight back off by the base
   rule's object-fit: cover. Slightly shorter tile: this is a reference strip,
   not the originals. */
.interior-extract-card--sq img {
    height: 96px;
    object-fit: contain;
    background: #f4f4f6;
}
/* Wide (1940x600) derivatives — the A+ 970x300 slot's shape. Its own grid with
   much wider columns: at the base grid's 140px a 3.23:1 tile is only 43px tall,
   too small to judge the crop by. aspect-ratio rather than the --sq rule's
   fixed height + contain, so the tile is EXACTLY the asset's shape and the
   browser adds no second set of bars on top of the baked-in padding. Same idiom
   as .aap-hist-thumb img, which displays the same 1940x600 asset.
   height:auto is required to override the base rule's height:120px. */
.interior-extract-grid--wide {
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
}
.interior-extract-card--wide img {
    height: auto;
    aspect-ratio: 1940 / 600;
    object-fit: contain;
    background: #f4f4f6;
}
.interior-extract-del {
    position: absolute; top: 4px; right: 4px; width: 20px; height: 20px;
    border: none; border-radius: 50%; background: rgba(0, 0, 0, 0.55);
    color: #fff; font-size: 13px; line-height: 20px; text-align: center;
    cursor: pointer; padding: 0; opacity: 0; transition: opacity 0.15s;
}
.interior-extract-card:hover .interior-extract-del { opacity: 1; }
.interior-extract-del:hover { background: #c0392b; }
/* Checkerboard behind the transparent 3D-cover PNG so alpha is visible. */
.branding-3d-checker {
    background-image:
        linear-gradient(45deg, #e8e8e8 25%, transparent 25%, transparent 75%, #e8e8e8 75%),
        linear-gradient(45deg, #e8e8e8 25%, transparent 25%, transparent 75%, #e8e8e8 75%);
    background-size: 16px 16px;
    background-position: 0 0, 8px 8px;
    border-radius: 6px;
    padding: 6px;
}

/* ── Feedback attachment control (paperclip → Capture/Upload, max 5) ──
   Shared by the widget's composers + the admin Feedback modal
   (markup/wiring exported from widgets/feedback-widget.js). */
.fb-attach { position: relative; display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0; }
.fb-attach-btn {
    background: none; border: 1px solid var(--border); border-radius: 6px;
    padding: 4px 7px; cursor: pointer; color: var(--muted);
    display: inline-flex; align-items: center; flex: none;
}
.fb-attach-btn:hover { color: var(--accent); border-color: var(--accent); }
.fb-attach-btn svg { width: 15px; height: 15px; }
.fb-attach--has-files .fb-attach-btn { color: var(--accent); border-color: var(--accent); }
.fb-attach-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.fb-attach--busy .fb-attach-btn svg { animation: spin 0.9s linear infinite; }
.fb-attach-menu {
    position: absolute; bottom: calc(100% + 6px); left: 0; z-index: 30;
    background: var(--paper, #fff); border: 1px solid var(--border);
    border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.14);
    padding: 4px; min-width: 215px;
}
.fb-attach-menu button {
    display: flex; align-items: center; gap: 8px; width: 100%;
    background: none; border: none; padding: 7px 10px; font-size: 0.82rem;
    color: var(--ink); cursor: pointer; border-radius: 6px; text-align: left;
    font-family: inherit;
}
.fb-attach-menu button:hover { background: rgba(0,0,0,0.05); }
.fb-attach-menu button svg { width: 14px; height: 14px; color: var(--muted); flex: none; }
.fb-attach-chips { display: inline-flex; gap: 4px; flex-wrap: wrap; }
.fb-attach-chip {
    position: relative; width: 34px; height: 34px; border-radius: 6px;
    overflow: hidden; border: 1px solid var(--border); flex: none;
}
.fb-attach-chip img { width: 100%; height: 100%; object-fit: cover; display: block; }
.fb-attach-remove {
    position: absolute; top: 0; right: 0; width: 14px; height: 14px;
    line-height: 12px; font-size: 11px; padding: 0; border: none;
    border-radius: 0 0 0 6px; background: rgba(0,0,0,0.55); color: #fff; cursor: pointer;
}
/* Multi-image rows in thread bubbles (widget + admin modal). */
.fb-msg-screenshots { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 4px; }
.fb-msg-screenshots img {
    max-height: 110px; max-width: 48%; border-radius: 4px;
    cursor: pointer; object-fit: cover;
}

/* ── Composed A+ form: "Generate more" under the Backgrounds strip ──
   Base look comes from .book-asset-generate-btn (the AI-style button). */
.aap-genmore {
    margin-top: 8px;
    align-self: flex-start;
}

/* ── 3D Cover tab: STANDARD / SPINE render pair (migration 196) ──
   Plain link-thumbs — no selection here (the render an asset uses is picked
   in the AI form or the Emporium); clicking opens the PNG in a new tab. */
.branding-3d-pair { display: flex; gap: 14px; align-items: flex-start; flex-wrap: wrap; }
.branding-3d-pair-thumb {
    display: flex; flex-direction: column; align-items: center; gap: 6px;
    border: none; cursor: pointer; padding: 6px;
    text-decoration: none;
}
.branding-3d-pair-thumb img { max-height: 220px; max-width: 170px; display: block; }
.branding-3d-pair-label { font-size: 11px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
.branding-3d-pair-thumb:hover .branding-3d-pair-label { color: var(--accent); }

/* ── Composed A+ form: per-panel Book Cover picker (STANDARD / SPINE) ── */
.aap-cover-strip { display: flex; gap: 12px; align-items: flex-start; flex-wrap: wrap; }
.aap-cover-thumb {
    display: flex; flex-direction: column; align-items: center; gap: 5px;
    border: none; cursor: pointer; padding: 6px; flex: 0 0 auto;
}
.aap-cover-thumb img { max-height: 130px; max-width: 110px; display: block; }
.aap-cover-thumb.active {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}
.aap-cover-thumb.active .branding-3d-pair-label { color: var(--accent); }

/* ── Multi-editor book-cover block: Display Options picker (migration 196) ──
   The checker box (and the active accent outline) wraps only the image;
   the label sits below the box. */
.me-aicover-strip { display: flex; gap: 10px; margin-top: 6px; }
.me-aicover-thumb {
    display: flex; flex-direction: column; align-items: center; gap: 5px;
    border: none; cursor: pointer; padding: 0; background: none; flex: 0 0 auto;
}
.me-aicover-box { display: block; }
.me-aicover-thumb img { max-height: 96px; max-width: 84px; display: block; }
.me-aicover-thumb.active .me-aicover-box {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}
.me-aicover-label { font-size: 10px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.03em; }
.me-aicover-thumb.active .me-aicover-label { color: var(--accent); }

/* ── Multi-editor book-cover block: Effects preset strip ──
   Sibling of the Display Options strip above and borrows its active-outline
   + uppercase-label treatment, but WRAPS (six tiles, three per row) where
   .me-aicover-strip is a single flex row.

   Each tile is an abstract swatch — a pale "cover" rectangle carrying the
   real effect CSS at 1.5x — not a cover thumbnail: at this size the
   difference between Soft Drop and Lift on a real cover is invisible, the
   cream ground demonstrates the exact problem being solved, and there is no
   image to fetch. The Halo tile sits on a dark box because that is the
   ground it exists for; on cream it would correctly show nothing.

   Gated tiles are DIMMED IN PLACE, never hidden — silently hiding controls
   on mode switch is how the old Shadow Blur field became invisible. */
.me-fx-strip { display: flex; flex-wrap: wrap; gap: 10px 12px; margin-top: 6px; }
.me-fx-thumb {
    display: flex; flex-direction: column; align-items: center; gap: 5px;
    border: none; cursor: pointer; padding: 0; background: none;
    flex: 0 0 calc(33.333% - 8px);
}
.me-fx-box {
    display: flex; align-items: center; justify-content: center;
    width: 100%; height: 60px; background: #f3ead9; border-radius: 3px;
    outline: 2px solid transparent; outline-offset: -2px;
    /* Contain the deliberately-exaggerated swatch shadow so it can't bleed
       onto the neighbouring tile. */
    overflow: hidden;
}
.me-fx-box.is-dark { background: #1e293b; }
.me-fx-swatch { display: block; width: 24px; height: 34px; }
.me-fx-thumb.active .me-fx-box { outline-color: var(--accent); }
.me-fx-label { font-size: 10px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.03em; }
.me-fx-thumb.active .me-fx-label { color: var(--accent); }
.me-fx-thumb.is-disabled { opacity: 0.4; cursor: not-allowed; }
.me-fx-note { font-size: 11px; color: var(--muted); margin-top: 8px; line-height: 1.4; }
/* Controls that don't apply in the current cover mode — same dim-in-place
   treatment as a gated tile. */
.me-field.is-dimmed { opacity: 0.4; }
.me-field.is-dimmed input { cursor: not-allowed; }

/* ── User Activity (Activity > User Activity) ─────────────────────
   First list/detail page in the dashboard: fixed-width user list left,
   scrolling activity feed right (grid shape modelled on
   .widget-editor-layout). Panes scroll independently so a long feed
   never strands the user list off-screen. */
.ua-layout { display: grid; grid-template-columns: 340px 1fr; gap: 20px; align-items: start; }
.ua-users { padding: 0.5rem; max-height: calc(100vh - 220px); overflow-y: auto; }
.ua-feed { padding: 1rem 1.25rem; max-height: calc(100vh - 220px); overflow-y: auto; }
.ua-user { padding: 0.6rem 0.75rem; border-radius: 6px; cursor: pointer;
  border: 1px solid transparent; }
.ua-user:hover { background: rgba(175,40,96,0.04); }
.ua-user.selected { background: rgba(175,40,96,0.06); border-color: var(--accent); }
.ua-user-name { font-weight: 600; font-size: 0.9rem; }
.ua-user-email { color: var(--muted); font-size: 0.8rem; margin: 1px 0 4px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 900px) { .ua-layout { grid-template-columns: 1fr; } }

/* ── Email Signature form (views-email-signature-form.js) ─────────────
   The signature itself is inline-styled email HTML (no classes — Gmail
   strips them); these classes style only the FORM chrome around it. */
.esig-stage { background: #f0f1f3; border-radius: 8px;
  padding: 22px; display: flex; justify-content: center; }
/* The dashboard's GLOBAL table rules (style.css `table {width:100%}` +
   `td {padding:.75rem; border-bottom:1px solid}`) bleed into the preview's
   email tables — phantom dividers under rows and padding that shoves text
   off its icon. The png-renderer resets these, so the copied PNG was always
   clean; this makes the preview match it. */
.esig-stage table { width: auto; font-size: inherit; }
.esig-stage td { padding: 0; border-bottom: none; }
.esig-stage-card { background: #fff; border-radius: 4px; padding: 18px 22px;
  box-shadow: 0 1px 4px rgba(0,0,0,0.08); max-width: 100%; overflow-x: auto;
  /* Preview shown at 85% — display-only scaling; the copied PNG renders
     server-side at full logical size regardless. */
  zoom: 0.85; }
/* Inline-edit affordance — dashed accent outline on hover/focus. Scoped to
   the stage so it can never ride along into the copied signature HTML. */
.esig-stage [data-sig-block] { outline: 1px dashed transparent; outline-offset: 2px;
  cursor: text; border-radius: 2px; transition: outline-color 0.12s ease; }
.esig-stage [data-sig-block]:hover { outline-color: var(--accent); }
.esig-stage [data-sig-block]:focus { outline: 1px dashed var(--accent);
  background: rgba(175,40,96,0.04); }

.esig-template-card { display: flex; flex-direction: column; align-items: center; gap: 8px;
  background: var(--paper); border: 2px solid var(--border); border-radius: 10px;
  padding: 12px; cursor: pointer; }
.esig-template-card.active { border-color: var(--accent); }
.esig-template-thumb { display: block; width: 200px; height: 120px; overflow: hidden;
  background: #fff; border-radius: 4px; position: relative; pointer-events: none; }
.esig-template-thumb > span { position: absolute; left: 50%; top: 50%; width: 380px;
  transform: translate(-50%,-50%) scale(0.45); transform-origin: center; display: block; }
.esig-template-name { font-size: 0.85rem; font-weight: 500; color: var(--ink); }

.esig-author-dd { display: flex; flex-direction: column; margin-top: 4px; }
.esig-author-row { display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  width: 100%; text-align: left; background: var(--paper); border: 1px solid var(--border);
  border-top: none; padding: 8px 10px; cursor: pointer; font-size: 0.9rem; }
.esig-author-row:first-child { border-top: 1px solid var(--border);
  border-radius: 6px 6px 0 0; }
.esig-author-row:last-child { border-radius: 0 0 6px 6px; }
.esig-author-row:hover { background: rgba(175,40,96,0.06); }

.esig-book-grid { display: flex; gap: 10px; flex-wrap: wrap; }
.esig-book-tile { position: relative; width: 84px; height: 122px; border: 2px solid var(--border);
  border-radius: 6px; overflow: hidden; cursor: pointer; background: #fff; flex-shrink: 0; }
.esig-book-tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
.esig-book-tile.featured { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(175,40,96,0.25); }
.esig-book-tile.alsoby { border-color: var(--accent); border-style: dashed; }
.esig-book-tile-noimg { display: flex; align-items: center; justify-content: center; height: 100%;
  padding: 6px; font-size: 0.65rem; color: var(--muted); text-align: center; }
.esig-book-badge { position: absolute; left: 0; right: 0; bottom: 0; background: var(--accent);
  color: #fff; font-size: 0.6rem; font-weight: 600; text-align: center; padding: 2px 0;
  text-transform: uppercase; letter-spacing: 0.4px; }
.esig-book-badge--also { background: rgba(175,40,96,0.75); }
.esig-book-plus { position: absolute; top: 4px; right: 4px; width: 20px; height: 20px;
  border-radius: 50%; border: none; background: rgba(26,26,46,0.65); color: #fff;
  font-size: 0.85rem; line-height: 1; cursor: pointer; display: flex; align-items: center;
  justify-content: center; }
.esig-book-plus:hover { background: var(--accent); }

/* ── HelpHub — the consolidated bottom-right launcher (help-hub.js) ──
   ONE help-circle FAB at the feedback bubble's old spot + z-tier
   (100020, visible over editor modals), toggling the "How can we help?"
   menu. Replaced the three stacked FABs 2026-09-01. */
.help-hub-btn {
    position: fixed;
    bottom: 24px;
    right: 24px;
    z-index: 100020;
    width: 48px;
    height: 48px;
    border-radius: 50%;
    border: none;
    background: var(--accent);
    color: #fff;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 12px rgba(0,0,0,0.2);
    transition: background 0.15s, transform 0.15s;
}
.help-hub-btn:hover { background: #8a1f3c; transform: scale(1.05); }
/* Both icons live in the DOM; .is-open flips which shows (rebuilding
 * innerHTML on toggle detached the clicked node and broke the
 * outside-close containment check). */
.help-hub-btn .hh-ic { display: inline-flex; align-items: center; justify-content: center; }
.help-hub-btn .hh-ic-x { display: none; }
.help-hub-btn.is-open .hh-ic-help { display: none; }
.help-hub-btn.is-open .hh-ic-x { display: inline-flex; }
.help-hub-btn .hh-dot {
    position: absolute; top: 2px; right: 2px;
    width: 12px; height: 12px; border-radius: 50%;
    background: #E8B23A; border: 2px solid var(--paper, #fff);
    pointer-events: none;
}
.help-hub-menu {
    position: fixed;
    bottom: 84px;
    right: 24px;
    z-index: 100021;
    width: 340px;
    max-width: calc(100vw - 48px);
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 16px;
    box-shadow: 0 12px 40px rgba(0,0,0,0.18);
    padding: 18px 8px 10px;
    animation: fadeIn 0.15s ease;
}
.hh-label {
    font-size: 0.7rem; font-weight: 600; letter-spacing: 0.1em;
    text-transform: uppercase; color: var(--muted);
    padding: 0 14px 6px;
}
.hh-item {
    display: flex; align-items: flex-start; gap: 14px; width: 100%;
    background: none; border: none; text-align: left; cursor: pointer;
    padding: 12px 14px; border-radius: 10px;
    font-family: inherit;
    transition: background 0.12s;
}
.hh-item:hover { background: var(--paper, #FAF9F5); }
.hh-item-icon { color: var(--accent); flex: 0 0 24px; margin-top: 2px; display: inline-flex; }
.hh-item-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.hh-item-title {
    font-size: 0.92rem; font-weight: 700; color: var(--ink); line-height: 1.25;
    display: inline-flex; align-items: center; gap: 8px;
}
/* BETA pill after a title — mockup style: accent on accent-light, caps. */
.hh-item-badge {
    background: var(--accent-light); color: var(--accent);
    font-size: 0.62rem; font-weight: 700; letter-spacing: 0.08em;
    text-transform: uppercase; padding: 2px 8px; border-radius: 6px;
}
.hh-item-dot {
    width: 9px; height: 9px; border-radius: 50%;
    background: #E8B23A; display: inline-block;
}
.hh-item-sub { font-size: 0.86rem; color: var(--muted); line-height: 1.35; }

/* ── Inkbot help chat (widgets/help-chat.js) ──────────────────────────
   (FAB retired — the HelpHub launcher will open the panel when Inkbot
   is un-hidden. Panel styles below still used.) */
.hc-btn {
    position: fixed;
    bottom: 128px;
    right: 24px;
    z-index: 100020;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: none;
    background: var(--accent);
    color: #fff;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 12px rgba(0,0,0,0.2);
    transition: background 0.15s, transform 0.15s;
}
.hc-btn:hover { background: #8a1f3c; transform: scale(1.05); }

.hc-panel {
    position: fixed;
    bottom: 128px;
    right: 80px;
    z-index: 100020;
    width: 380px;
    max-width: calc(100vw - 100px);
    height: 520px;
    max-height: calc(100vh - 160px);
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: 0 8px 32px rgba(0,0,0,0.18);
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.hc-header {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}
.hc-header-icon { color: var(--accent); display: inline-flex; }
.hc-header-title {
    font-family: 'DM Serif Display', serif;
    font-size: 1.05rem;
    color: var(--ink);
    flex: 1;
}
.hc-transcript {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 12px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    background: var(--paper);
}
.hc-empty { color: var(--muted); font-size: 0.85rem; line-height: 1.5; padding: 8px 4px; }
.hc-bubble {
    max-width: 88%;
    padding: 8px 11px;
    border-radius: 10px;
    font-size: 0.85rem;
    line-height: 1.45;
    word-wrap: break-word;
}
.hc-user {
    align-self: flex-end;
    background: var(--accent);
    color: #fff;
    border-bottom-right-radius: 3px;
    white-space: pre-wrap;
}
.hc-bot {
    align-self: flex-start;
    background: #fff;
    border: 1px solid var(--border);
    color: var(--ink);
    border-bottom-left-radius: 3px;
}
.hc-bot p { margin: 0 0 6px; }
.hc-bot p:last-child { margin-bottom: 0; }
.hc-bot ul, .hc-bot ol { margin: 4px 0 6px; padding-left: 18px; }
.hc-bot li { margin: 2px 0; }
.hc-bot code { background: rgba(175,40,96,0.08); padding: 0 4px; border-radius: 3px; font-size: 0.8rem; }
.hc-bot a { color: var(--accent); font-weight: 600; }
.hc-error { align-self: flex-start; background: #fdecea; border: 1px solid #f5c6c0; color: #7a1f14; }
.hc-error a { color: #7a1f14; font-weight: 600; }
.hc-info { align-self: center; background: var(--paper); border: 1px dashed var(--border); color: var(--muted); }
.hc-typing { display: inline-flex; gap: 4px; padding: 12px 14px; }
.hc-typing span {
    width: 6px; height: 6px; border-radius: 50%;
    background: var(--muted);
    animation: hc-blink 1.2s infinite both;
}
.hc-typing span:nth-child(2) { animation-delay: 0.2s; }
.hc-typing span:nth-child(3) { animation-delay: 0.4s; }
@keyframes hc-blink { 0%, 80%, 100% { opacity: 0.25; } 40% { opacity: 1; } }
.hc-composer {
    display: flex;
    gap: 8px;
    padding: 10px 12px;
    border-top: 1px solid var(--border);
    align-items: flex-end;
    flex-shrink: 0;
}
.hc-input {
    flex: 1;
    resize: none;
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 7px 10px;
    font-family: 'Inter', sans-serif;
    font-size: 0.85rem;
    line-height: 1.4;
}
.hc-input:focus { outline: none; border-color: var(--accent); }

/* --- Email Updates: template editor --- */
/* Big edit modal (overrides the 760px aic-modal-inner): fixed header/tabs/
   footer, scrollable body — same shape as .cp-edit-modal-inner. */
.eu-edit-modal-inner {
    width: min(980px, 95vw);
    max-width: none;
    height: 92vh;
    max-height: 92vh;
    display: flex;
    flex-direction: column;
}
.eu-edit-modal-inner .eu-edit-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}
/* The editor ground (grey) + the email "page" (always white — it previews an
   email, not the dashboard theme). position:relative anchors the link popover. */
.eu-tpl-ground {
    position: relative;
    background: #eef0f2;
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 24px 12px;
}
.eu-tpl-page {
    max-width: 640px;
    margin: 0 auto;
    background: #fff;
    border-radius: 8px;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
    padding: 24px;
    font-family: Arial, Helvetica, sans-serif;
    font-size: 15px;
    line-height: 1.55;
    color: #1a1a1a;
}
.eu-tpl [contenteditable] {
    outline: none;
    border-radius: 4px;
}
.eu-tpl [contenteditable]:hover { box-shadow: 0 0 0 1px #e3bfd0 inset; }
.eu-tpl [contenteditable]:focus { box-shadow: 0 0 0 2px #af2860 inset; background: #fff7fa; }
.eu-tpl [contenteditable]:empty::before { content: attr(data-eu-ph); color: #b6b6b6; }
.eu-img-slot { position: relative; cursor: pointer; }
.eu-img-slot .eu-img-btn {
    position: absolute;
    top: 8px;
    right: 8px;
    background: rgba(20, 20, 20, 0.72);
    color: #fff;
    font-size: 12px;
    padding: 4px 10px;
    border-radius: 999px;
    opacity: 0;
    transition: opacity 0.15s;
    pointer-events: none;
}
.eu-img-slot:hover .eu-img-btn { opacity: 1; }
.eu-img-empty {
    border: 2px dashed #c9ced6;
    border-radius: 10px;
    padding: 38px 12px;
    text-align: center;
    color: #8a8a8a;
    font-size: 13px;
    background: #fafbfc;
}
.eu-link-pop {
    position: absolute;
    z-index: 10;
    background: var(--card-bg, #fff);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
    padding: 10px;
    width: 300px;
}
.eu-bullet-x {
    display: inline-block;
    color: #b6b6b6;
    cursor: pointer;
    font-weight: bold;
    padding: 0 4px;
}
.eu-bullet-x:hover { color: #b42318; }

/* --- Rights Catalogs: Poster (Book Size) thumb tiles --- */
.rc-thumb-p {
    position: relative;
    flex: 0 0 46px;
    height: 64px;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: #fff;
    cursor: pointer;
    overflow: hidden;
}
.rc-thumb-p.active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.rc-thumb-p .rc-thumb-x-bg { position: absolute; inset: 0; background-size: cover; background-position: center; }
.rc-thumb-qr {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    font-weight: 700;
    color: #666;
    background: #f5f5f5;
}

/* ═══ Inkara HQ (views-hq.js) — .hq-* namespace ═══════════════════ */

.hq-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1.5rem;
    align-items: start;
}
@media (max-width: 1100px) {
    .hq-grid { grid-template-columns: 1fr; }
}
.hq-card .card-header h3 { display: flex; align-items: center; gap: 8px; }
.hq-card .card-header h3 svg { width: 18px; height: 18px; color: var(--accent); }

/* Tabs */
.hq-tabs {
    display: flex;
    gap: 1.25rem;
    border-bottom: 1px solid var(--border);
    margin: 0.75rem 0 0.25rem;
}
.hq-tab {
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    padding: 0.45rem 0.1rem 0.55rem;
    font: 500 0.92rem var(--font-sans);
    color: var(--muted);
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 6px;
}
.hq-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.hq-count {
    font-size: 0.72rem;
    font-weight: 600;
    background: #f1f0ec;
    color: var(--muted);
    border-radius: 999px;
    padding: 1px 7px;
}
.hq-tab.active .hq-count { background: var(--accent-light); color: var(--accent-dark); }

/* Rows (widget panels) */
.hq-list {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: 24rem;
    overflow-y: auto;
}
.hq-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.55rem 0.15rem;
    border-bottom: 1px solid #f1f0ec;
}
.hq-row:last-child { border-bottom: none; }
.hq-title {
    font-size: 0.95rem;
    color: var(--ink);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.hq-title.done { text-decoration: line-through; color: var(--muted); }
.hq-empty { padding: 1.25rem 0; color: var(--muted); font-size: 0.9rem; list-style: none; }

/* Completion circle */
.hq-check {
    flex: 0 0 auto;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    border: 2px solid #cfc9bf;
    background: #fff;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    color: #fff;
}
.hq-check:hover:not(:disabled) { border-color: var(--accent); }
.hq-check.done { background: var(--success); border-color: var(--success); }
.hq-check:disabled { cursor: not-allowed; opacity: 0.55; }

/* Chips */
.hq-chip {
    flex: 0 0 auto;
    font-size: 0.75rem;
    font-weight: 500;
    padding: 2px 9px;
    border-radius: 999px;
    background: #f1f0ec;
    color: var(--muted);
    white-space: nowrap;
}
.hq-chip-slate { background: #f1f0ec; color: var(--muted); }
.hq-chip-amber { background: #fef5e7; color: #b9770e; }
.hq-chip-overdue { background: #fdecea; color: var(--danger); }
.hq-pri-critical { background: var(--danger); color: #fff; }
.hq-pri-high { background: #fef0e7; color: #ca6f1e; }
.hq-pri-low { background: #f6f5f2; color: #b0aa9e; }
.hq-assignees { margin-left: auto; display: inline-flex; gap: 5px; flex: 0 0 auto; }
.hq-legacy { font-size: 0.78rem; color: var(--muted); }

/* Hover actions */
.hq-row-actions {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    opacity: 0;
    transition: opacity 0.12s;
    flex: 0 0 auto;
}
.hq-row:hover .hq-row-actions,
.hq-full-card:hover .hq-row-actions { opacity: 1; }
.hq-icon-btn {
    background: none;
    border: none;
    padding: 2px;
    cursor: pointer;
    color: var(--muted);
    display: inline-flex;
}
.hq-icon-btn svg { width: 15px; height: 15px; }
.hq-icon-btn:hover:not(:disabled) { color: var(--accent); }
.hq-icon-btn:disabled { opacity: 0.3; cursor: default; }
.hq-link-btn {
    background: none;
    border: none;
    cursor: pointer;
    font: 500 0.85rem var(--font-sans);
    color: var(--ink);
    padding: 2px 6px;
}
.hq-link-btn:hover { color: var(--accent); }
.hq-link-btn.hq-danger:hover { color: var(--danger); }

.hq-viewall {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-top: 0.85rem;
    font: 600 0.92rem var(--font-sans);
    color: var(--accent-dark);
    text-decoration: none;
}
.hq-viewall svg { width: 15px; height: 15px; }
.hq-viewall:hover { color: var(--accent); }

/* My to-do quick add */
.hq-quickadd { display: flex; gap: 0.5rem; margin: 0.75rem 0 0.25rem; }
.hq-quickadd input {
    flex: 1;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.45rem 0.7rem;
    font: 400 0.9rem var(--font-sans);
}

/* Full "View all tasks" page */
.hq-full-head {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    margin-bottom: 1.25rem;
}
.hq-back {
    background: none;
    border: none;
    cursor: pointer;
    padding: 6px 4px 0 0;
    color: var(--ink);
}
.hq-back svg { width: 22px; height: 22px; }
.hq-full-controls {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
}
.hq-full-controls select {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.5rem 0.7rem;
    font: 400 0.9rem var(--font-sans);
    background: #fff;
    color: var(--ink);
}
.hq-full-list { list-style: none; margin: 0.75rem 0 0; padding: 0; }
.hq-full-card {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 0.9rem 1rem;
    margin-bottom: 0.7rem;
    box-shadow: 0 1px 2px rgba(26, 26, 46, 0.04);
}
.hq-full-card.hq-dragging { opacity: 0.5; }
.hq-full-body { flex: 1; min-width: 0; }
.hq-full-title-row { display: flex; align-items: center; gap: 0.5rem; min-width: 0; }
.hq-full-chips { margin-top: 0.35rem; display: flex; gap: 5px; }
.hq-full-chips .hq-assignees { margin-left: 0; }
.hq-drag {
    cursor: grab;
    color: #cfc9bf;
    display: inline-flex;
    opacity: 0;
    transition: opacity 0.12s;
    margin-right: -6px;
}
.hq-full-card:hover .hq-drag { opacity: 1; }
.hq-drag svg { width: 13px; height: 13px; margin: 0 -4px; }

/* Assignee-filter popover */
.hq-popover-wrap { position: relative; }
.hq-popover {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 500;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 8px 24px rgba(26, 26, 46, 0.12);
    padding: 0.4rem;
    min-width: 200px;
}
.hq-pop-row {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 0.45rem 0.6rem;
    border: none;
    background: none;
    border-radius: 6px;
    font: 400 0.9rem var(--font-sans);
    color: var(--ink);
    cursor: pointer;
    text-align: left;
}
.hq-pop-row:hover { background: #f6f5f2; }

/* Modal bits */
.hq-modal .hq-modal-eyebrow {
    margin: 0.4rem 0 0;
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--muted);
}
.hq-pill-row { display: flex; flex-wrap: wrap; gap: 0.45rem; align-items: center; }
.hq-pill {
    border: 1.5px solid var(--border);
    background: #fff;
    border-radius: 999px;
    padding: 0.35rem 0.9rem;
    font: 500 0.85rem var(--font-sans);
    color: var(--ink);
    cursor: pointer;
}
.hq-pill:hover { border-color: var(--accent); }
.hq-pill.active {
    background: var(--accent-light);
    border-color: var(--accent);
    color: var(--accent-dark);
}
.hq-pill-row input[type=date] {
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 0.3rem 0.8rem;
    font: 400 0.85rem var(--font-sans);
    color: var(--ink);
}
.hq-hint { margin: 0.45rem 0 0; font-size: 0.8rem; color: var(--muted); }
.hq-error { margin-top: 0.6rem; font-size: 0.85rem; color: var(--danger); }
