/* ============================================================
   Edit — portal.css
   Mobile-first. Tokens + utilities + components.
   Phase 0.5: waitlist landing + admin pages.
   Phase 1A onward will extend this stylesheet — never inline
   styles, never duplicate patterns. Add classes here first.
   ============================================================ */

/* ---------- DESIGN TOKENS ---------- */
:root {
    /* Brand */
    --brand:            #0a0a0a;
    --brand-soft:       #1a1a1a;
    --brand-accent:        #ff6b35;   /* warm accent — fills, icons, focus rings */
    --brand-accent-strong: #c2410c;   /* accent for TEXT on white (AA contrast) */
    --brand-accent-tint:   #fff2ec;   /* light accent surface — active rows/tabs */
    --ink:              #0a0a0a;   /* darkest ink — fills, outlines, cover borders */

    /* Text */
    --text:             #0a0a0a;
    --text-med:         #4a4a4a;
    --text-light:       #777;
    --text-inverse:     #fff;

    /* Surfaces */
    --bg:               #fff;
    --bg-soft:          #fafafa;
    --bg-muted:         #f3f3f3;
    --bg-card:          #fff;
    --bg-dark:          #0a0a0a;

    /* Borders */
    --border:           #e5e5e5;
    --border-soft:      #f0f0f0;
    --border-strong:    #c8c8c8;

    /* Status */
    --success:          #15803d;
    --success-bg:       #f0fdf4;
    --success-border:   #bbf7d0;
    --warn:             #92400e;
    --warn-bg:          #fffaf0;
    --warn-border:      #fed7aa;
    --danger:           #991b1b;
    --danger-bg:        #fef2f2;
    --danger-border:    #fecaca;
    --info:             #1e40af;
    --info-bg:          #eff6ff;
    --info-border:      #bfdbfe;

    /* Typography */
    --font-sans:        "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
    --font-display:     "Inter", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
    --font-mono:        ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, monospace;

    /* Spacing scale */
    --sp-1: .25rem;  --sp-2: .5rem;   --sp-3: .75rem;
    --sp-4: 1rem;    --sp-5: 1.25rem; --sp-6: 1.5rem;
    --sp-7: 2rem;    --sp-8: 2.5rem;  --sp-9: 3rem;
    --sp-10: 4rem;   --sp-11: 5rem;   --sp-12: 6rem;

    /* Radius */
    --radius-sm: 4px;
    --radius:    8px;
    --radius-lg: 12px;
    --radius-pill: 999px;

    /* Shadow */
    --shadow-sm: 0 1px 2px rgba(0,0,0,.04);
    --shadow:    0 2px 8px rgba(0,0,0,.06);
    --shadow-lg: 0 8px 24px rgba(0,0,0,.08);
}

/* ---------- RESET ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
    font-family: var(--font-sans);
    color: var(--text);
    background: var(--bg);
    line-height: 1.5;
    font-size: 16px; /* iOS: prevents auto-zoom on input focus */
    -webkit-text-size-adjust: 100%;
    -webkit-font-smoothing: antialiased;
}
a { color: inherit; }
img, svg { max-width: 100%; display: block; }

/* ---------- TYPE UTILITIES (port from TP CLAUDE) ---------- */
.t-xs   { font-size: .78rem; }
.t-sm   { font-size: .85rem; }
.t-md   { font-size: .92rem; }
.t-lg   { font-size: 1.05rem; }
.t-muted   { color: var(--text-light); }
.t-med     { color: var(--text-med); }
.t-success { color: var(--success); }
.t-danger  { color: var(--danger); }
.t-mono    { font-family: var(--font-mono); }
.t-wrap    { white-space: pre-wrap; word-break: break-word; }
.text-center { text-align: center; }
.text-right  { text-align: right; }

/* ---------- SPACING UTILITIES ---------- */
.mt-0 { margin-top: 0; }
.mt-2 { margin-top: var(--sp-2); }
.mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }
.mt-5 { margin-top: var(--sp-5); }
.mt-6 { margin-top: var(--sp-6); }
.mt-7 { margin-top: var(--sp-7); }
.mb-0 { margin-bottom: 0; }
.mb-2 { margin-bottom: var(--sp-2); }
.mb-3 { margin-bottom: var(--sp-3); }
.mb-4 { margin-bottom: var(--sp-4); }
.mb-5 { margin-bottom: var(--sp-5); }
.mb-6 { margin-bottom: var(--sp-6); }
.mb-7 { margin-bottom: var(--sp-7); }

/* ---------- PADDING UTILITIES ---------- */
.p-0 { padding: 0; }
.p-3 { padding: var(--sp-3); }
.p-4 { padding: var(--sp-4); }
.p-5 { padding: var(--sp-5); }
.p-6 { padding: var(--sp-6); }
.pt-0 { padding-top: 0; }
.mb-1 { margin-bottom: var(--sp-1); }

/* ---------- LAYOUT UTILITIES ---------- */
.action-row {
    display: flex;
    gap: var(--sp-3);
    flex-wrap: wrap;
    align-items: center;
}
.action-row-end     { justify-content: flex-end; }
.action-row-between { justify-content: space-between; }
.action-row > form  { display: contents; } /* let nested <form> not break flex layout */
.stack-col {
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
}
.stack-col-tight { gap: var(--sp-2); }
.stack-col-loose { gap: var(--sp-6); }

/* ---------- BRAND ----------
   Mark and wordmark both use Inter weight 900. The mark is just the
   letter "E" in a dark rounded square so it pairs visually with the
   wordmark and works as a favicon. */

.brand {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3);
    color: var(--brand);
    text-decoration: none;
}

/* Mark: letter "E" in a dark square, white-on-dark.
   Size variants scale font-size proportionally to the square. */
.brand-mark,
.brand-mark-lg,
.brand-mark-xl {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--brand-accent);
    color: #fff;
    font-family: var(--font-display);
    font-weight: 900;
    line-height: 1;
    text-align: center;
    flex-shrink: 0;
    user-select: none;
    /* nudge the letter optically — sans-serif caps sit slightly low in their box */
    padding-bottom: .05em;
}
.brand-mark {
    width: 1.6rem;
    height: 1.6rem;
    font-size: 1.1rem;
    letter-spacing: -.04em;
    border-radius: 4px;
}
.brand-mark-lg {
    width: 3rem;
    height: 3rem;
    font-size: 2rem;
    letter-spacing: -.05em;
    border-radius: 8px;
    margin: 0 auto;
}
.brand-mark-xl {
    width: 4rem;
    height: 4rem;
    font-size: 2.75rem;
    letter-spacing: -.05em;
    border-radius: 10px;
}

/* Wordmark: "Edit" text in matching Inter 900. */
.brand-wordmark {
    font-family: var(--font-display);
    font-weight: 900;
    font-size: 1.4rem;
    letter-spacing: -.04em;
    color: var(--brand);
    line-height: 1;
}
.brand-wordmark-lg {
    font-family: var(--font-display);
    font-weight: 900;
    font-size: 2.25rem;
    letter-spacing: -.045em;
    color: var(--brand);
    line-height: 1;
    margin: 0;
}
/* White-label sign-in: studio logo above a subtle company name. */
.login-brand-logo { display: block; max-height: 64px; max-width: 240px; width: auto; margin: 0 auto; object-fit: contain; }
/* When a logo is present the name is a quiet subtitle, not a big wordmark. */
.login-brand-name { font-size: 1.05rem; font-weight: 700; color: var(--text); margin: 0; line-height: 1.2; }
.brand-wordmark-xl {
    font-family: var(--font-display);
    font-weight: 900;
    font-size: 3rem;
    letter-spacing: -.05em;
    color: var(--brand);
    line-height: .95;
    margin: 0;
}
.brand-lockup-xl {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-4);
    margin: 0 auto;
}

/* Brand toggle: E shown by default, "Edit" wordmark slides out from behind
   it on hover or keyboard focus. Used on marketing surfaces only. */
.brand-toggle {
    display: inline-flex;
    align-items: center;
    color: var(--brand);
    text-decoration: none;
}
.brand-toggle .brand-wordmark {
    max-width: 0;
    margin-left: 0;
    opacity: 0;
    overflow: hidden;
    white-space: nowrap;
    transition:
        max-width 280ms cubic-bezier(.4, 0, .2, 1),
        margin-left 280ms cubic-bezier(.4, 0, .2, 1),
        opacity 180ms ease;
}
.brand-toggle:hover .brand-wordmark,
.brand-toggle:focus-visible .brand-wordmark,
.brand-toggle:focus-within .brand-wordmark {
    max-width: 6rem;
    margin-left: var(--sp-2);
    opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
    .brand-toggle .brand-wordmark {
        max-width: 6rem;
        margin-left: var(--sp-2);
        opacity: 1;
        transition: none;
    }
}

.brand-sublabel {
    font-family: var(--font-mono);
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: .12em;
    font-weight: 600;
    color: var(--text-med);
}

/* Logo export page — preview tile + action buttons */
.logo-export {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-6);
}
.logo-preview {
    width: 192px;
    height: 192px;
    background: var(--brand);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-display);
    font-weight: 900;
    font-size: 134px;
    letter-spacing: -.05em;
    line-height: 1;
    user-select: none;
}
.logo-export-actions {
    display: flex;
    gap: var(--sp-3);
    flex-wrap: wrap;
    justify-content: center;
}

/* ---------- BUTTONS ---------- */
/* The [hidden] attribute must beat .btn's display so JS-toggled buttons hide. */
.btn[hidden] { display: none; }
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    padding: .75rem 1.25rem;
    min-height: 44px; /* mobile tap target */
    border-radius: var(--radius);
    border: 1px solid transparent;
    font-family: inherit;
    font-size: 1rem;
    font-weight: 500;
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
    transition: background .15s, border-color .15s, color .15s, box-shadow .15s, transform .1s;
    -webkit-appearance: none;
    appearance: none;
}
/* Primary CTA = Edit orange — the single pop of brand color on each page
   (New project / client / invoice / agreement, subscribe, confirms, etc.). */
.btn-primary {
    background: var(--brand-accent);
    color: var(--text-inverse);
}
/* Tactile hover: deepen to the strong accent + lift so it clearly responds. */
.btn-primary:hover {
    background: var(--brand-accent-strong);
    box-shadow: 0 4px 14px rgba(255, 107, 53, .30);
    transform: translateY(-1px);
}
.btn-primary:active {
    transform: translateY(0);
    box-shadow: 0 2px 6px rgba(255, 107, 53, .22);
}
/* In-drawer pay buttons: a clear disabled look while a payment is processing, so
   the orange CTA never reads as a half-broken "stuck" button. They're enabled
   whenever idle (see stripe-payment.js / square-payment.js). */
#stripe-pay-btn:disabled, #sq-pay-btn:disabled {
    opacity: .55;
    box-shadow: none;
    transform: none;
    cursor: default;
}
/* Auth / entry shell (sign-in, signup, checkout) keeps the INK primary button —
   orange is reserved as the in-app per-page pop. */
.admin-login-card .btn-primary { background: var(--brand); }
.admin-login-card .btn-primary:hover { background: var(--brand-soft); box-shadow: 0 4px 14px rgba(10, 10, 10, .22); }
.admin-login-card .btn-primary:active { box-shadow: 0 2px 6px rgba(10, 10, 10, .16); }

/* Segmented control — the canonical Active/Archived toggle on list pages.
   A muted track with the active segment lifted on white. */
.segmented {
    display: inline-flex;
    gap: 2px;
    padding: 3px;
    background: var(--bg-muted);
    border-radius: var(--radius);
}
.segmented-item {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: .4rem .8rem;
    min-height: 34px;
    border-radius: calc(var(--radius) - 3px);
    font-size: .85rem;
    font-weight: 500;
    font-family: inherit;
    color: var(--text-med);
    text-decoration: none;
    white-space: nowrap;
    /* Reset native <button> chrome (the control is used as both <a> and <button>). */
    background: none;
    border: 0;
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
    transition: background .12s, color .12s, box-shadow .12s;
}
.segmented-item:hover { color: var(--text); }
.segmented-item.is-active {
    background: #fff;
    color: var(--text);
    box-shadow: var(--shadow-sm);
}
.segmented-count {
    color: var(--text-light);
    font-variant-numeric: tabular-nums;
    font-size: .8rem;
}
.segmented-item.is-active .segmented-count { color: var(--text-med); }
/* Compact button. Used inline in table rows + cards where the default
   44px tap target overwhelms the surrounding density. Still meets a
   reasonable touch target on mobile (32px) while looking right on
   desktop. */
.btn-sm {
    padding: .375rem .75rem;
    min-height: 32px;
    font-size: .875rem;
    gap: .375rem;
}
/* Action buttons grouped inline at the end of a table row (e.g. View
   + Pay on client invoices). Right-aligned by default — sits inside a
   .text-right td. */
.invoice-row-actions {
    display: inline-flex;
    gap: var(--sp-2);
    justify-content: flex-end;
}
.btn-secondary {
    background: var(--bg);
    color: var(--text);
    border-color: var(--border-strong);
}
.btn-secondary:hover { background: var(--bg-muted); }
.btn-tertiary {
    color: var(--text);
    background: var(--bg-muted);
    border-color: var(--bg-muted);
}
.btn-tertiary:hover {
    background: #e8e8e8;
    border-color: #e8e8e8;
}
.btn-ghost {
    background: transparent;
    color: var(--text-med);
}
.btn-ghost:hover { color: var(--text); background: var(--bg-muted); }
.btn-ghost.t-danger { color: var(--danger); }
.btn-ghost.t-danger:hover { color: var(--danger); background: var(--danger-bg); }
.btn-full { width: 100%; }

/* ---------- FORM CONTROLS ---------- */
.form-control {
    width: 100%;
    padding: .75rem .875rem;
    min-height: 44px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--bg);
    font-family: inherit;
    font-size: 1rem;
    line-height: 1.4;
    color: var(--text);
    -webkit-appearance: none;
    appearance: none;
}
.form-control:focus {
    outline: 2px solid var(--brand-accent);
    outline-offset: -1px;
    border-color: var(--brand-accent);
}
/* Client-side validation invalid state — applied by form-validate.js
   after blur if the field fails its data-validate rules. Matches the
   server-side .form-control + .form-error pairing so the visual is the
   same whether the error came from JS or PHP. */
.form-control.is-invalid {
    border-color: var(--danger);
    background: #fef7f7;
}
.form-control.is-invalid:focus {
    outline-color: var(--danger);
    border-color: var(--danger);
}

/* Dropdown chevron — the global appearance:none reset strips the native
   arrow off <select>s, so draw our own. Applies to every select.form-control
   across BOTH the studio and client portals (shared stylesheet). */
select.form-control {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%2364748b' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.875rem center;
    background-size: 12px 8px;
    padding-right: 2.5rem;
    cursor: pointer;
}
/* The .is-invalid background shorthand would wipe the chevron — restore it. */
select.form-control.is-invalid {
    background-color: #fef7f7;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%2364748b' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.form-error {
    display: block;
    color: var(--danger);
    font-size: .8rem;
    margin-top: 6px;
    line-height: 1.4;
}
.form-label {
    display: block;
    font-size: .92rem;
    font-weight: 500;
    color: var(--text);
    margin-bottom: var(--sp-2);
}
/* Label row with a right-aligned inline action (e.g. "+ New client"). */
.form-label-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-2);
    margin-bottom: var(--sp-2);
}
.form-label-row .form-label { margin-bottom: 0; }
.form-label-action {
    border: none;
    background: transparent;
    padding: 0;
    cursor: pointer;
    color: var(--brand-accent-strong);
    font-size: .8rem;
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    white-space: nowrap;
}
.form-label-action:hover { text-decoration: underline; }
.form-label-action i { font-size: .7rem; }
/* Inline field group (e.g. shoot date + optional time side by side). */
.field-inline { display: flex; gap: var(--sp-2); }
.field-inline > input[type="date"] { flex: 1 1 auto; min-width: 0; }
.field-inline-time { flex: 0 0 auto; width: auto; min-width: 6.5rem; }

/* Auto-archive day count (Settings → Preferences) */
.auto-archive-days-field .field-inline { align-items: center; }
.auto-archive-days-field #auto_archive_days { flex: 0 0 auto; width: 6rem; }
.auto-archive-days-field.is-disabled { opacity: .55; }
.late-fee-pct-field { max-width: 14rem; }
.field-note {
    display: flex;
    gap: var(--sp-2);
    margin: var(--sp-4) 0 0;
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--radius);
    font-size: .82rem;
    line-height: 1.5;
    color: var(--text-med);
    background: var(--bg-soft);
    border: 1px solid var(--border-soft);
}
.field-note i { margin-top: 2px; flex: 0 0 auto; }
.field-note-info { border-left: 3px solid var(--brand-accent); }
.field-note-info i { color: var(--brand-accent); }

/* Studio URL slug editor */
/* One combined field: the editable slug + a grayed ".tryedit.co" suffix inside a
   single bordered box, so it reads as a studio subdomain (not the user's own
   website URL). Stays inline on mobile (no wrap). */
.slug-input-group {
    display: flex;
    align-items: center;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--bg);
    overflow: hidden;
}
.slug-input-group:focus-within {
    border-color: var(--brand-accent);
    box-shadow: 0 0 0 3px var(--brand-accent-tint, rgba(255,107,53,.18));
}
.slug-input.form-control {
    flex: 1 1 auto;
    min-width: 0;
    border: none;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
}
.slug-input.form-control:focus { border: none; box-shadow: none; outline: none; }
.slug-suffix {
    flex: 0 0 auto;
    padding-right: .875rem;
    font-family: var(--font-mono);
    color: var(--text-light);   /* grayed, clearly not editable */
    white-space: nowrap;
}
.slug-status { font-size: .8rem; margin: 6px 0 0; min-height: 1.1em; }
.slug-status-checking { color: var(--text-light); }
.slug-status-ok  { color: var(--success, #15803d); font-weight: 600; }
.slug-status-bad { color: var(--danger); font-weight: 600; }
.slug-status-warn { color: var(--brand-accent-strong, #c2410c); font-weight: 600; }

/* Feature gating — locked premium features (visible but grayed) + upgrade modal */
.feature-locked { opacity: .62; }
.feature-lock-row { cursor: pointer; }
.feature-lock-row [disabled] { cursor: pointer; pointer-events: none; }
.feature-lock-badge {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: .7rem; font-weight: 600; color: var(--text-med);
    background: var(--bg-soft); border: 1px solid var(--border);
    border-radius: 999px; padding: 2px 9px; white-space: nowrap;
}
.feature-lock-badge i { color: var(--brand-accent); }
.upgrade-modal-icon {
    width: 48px; height: 48px; margin: 0 auto var(--sp-3);
    display: flex; align-items: center; justify-content: center;
    border-radius: 50%; background: var(--brand-accent-tint, #fff2ec);
    color: var(--brand-accent); font-size: 1.2rem;
}
.upgrade-modal-lead { text-align: center; font-size: 1.02rem; margin: 0 0 var(--sp-2); }
.upgrade-modal .settings-modal-body { text-align: center; }
.upgrade-modal-error { color: var(--danger, #c2410c); margin: var(--sp-3) 0 0; }
/* Single, prominent action — no easy "out" alongside it; cost note beneath. */
.upgrade-modal-foot { flex-direction: column; align-items: stretch; gap: var(--sp-2); }
.upgrade-modal-foot .btn-full { width: 100%; }
.upgrade-modal-cost { margin: 0; text-align: center; font-size: .85rem; color: var(--text-med); }

/* Billing & Plan page */
.billing-summary {
    display: flex; flex-wrap: wrap; gap: var(--sp-5); align-items: center;
    background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-lg);
    padding: var(--sp-5) var(--sp-6); margin-bottom: var(--sp-7);
}
.billing-summary-main { display: flex; flex-direction: column; }
.billing-summary-eyebrow { font-size: .72rem; text-transform: uppercase; letter-spacing: .05em; color: var(--text-light); }
.billing-summary-tier { font-size: 1.5rem; font-weight: 800; }
.billing-summary-meter { flex: 1 1 240px; min-width: 200px; }
.billing-summary-meter-head { display: flex; justify-content: space-between; font-size: .8rem; color: var(--text-med); margin-bottom: 6px; }
.billing-meter-track { height: 8px; border-radius: 999px; background: var(--bg-soft); overflow: hidden; }
.billing-meter-fill { height: 100%; background: var(--brand-accent); border-radius: 999px; min-width: 4px; }
.billing-summary-meta { font-size: .82rem; color: var(--text-med); margin-top: 4px; }

/* Subscription status chip */
.billing-status-chip {
    display: inline-block; vertical-align: middle; margin-left: 8px;
    font-size: .7rem; font-weight: 700; letter-spacing: .02em; text-transform: uppercase;
    padding: 3px 9px; border-radius: 999px; line-height: 1.4;
}
.billing-status-chip.is-active    { background: #e7f6ec; color: #1c7a3f; }
.billing-status-chip.is-trial     { background: var(--brand-accent-tint, #fff2ec); color: var(--brand-accent-strong, #c2410c); }
.billing-status-chip.is-canceling,
.billing-status-chip.is-paused    { background: #fef3e2; color: #9a5b00; }
.billing-status-chip.is-pastdue   { background: #fde8e8; color: #b42318; }
.billing-status-chip.is-canceled  { background: var(--bg-soft); color: var(--text-med); }

/* Management panel */
.billing-manage {
    border: 1px solid var(--border); border-radius: var(--radius-lg);
    background: var(--bg); margin-bottom: var(--sp-7); overflow: hidden;
}
.billing-manage-row {
    display: flex; flex-direction: column; gap: var(--sp-3);
    padding: var(--sp-5) var(--sp-6); border-bottom: 1px solid var(--border);
}
.billing-manage-row:last-child { border-bottom: 0; }
.billing-manage-label { display: flex; flex-direction: column; gap: 3px; }
.billing-manage-title { font-weight: 700; font-size: .98rem; }
.billing-manage-title-row { display: flex; align-items: baseline; gap: var(--sp-2) var(--sp-3); flex-wrap: wrap; }
.billing-pauses-left { font-size: .8rem; color: var(--text-light); }
.billing-pauses-left strong { color: var(--text); font-weight: 700; }
.billing-manage-sub { font-size: .85rem; color: var(--text-med); }
@media (min-width: 600px) {
    .billing-manage-row { flex-direction: row; align-items: center; justify-content: space-between; gap: var(--sp-5); }
    .billing-manage-row .btn, .billing-manage-row .storage-stepper { flex: 0 0 auto; }
}

/* Storage +/- stepper */
.storage-stepper { display: inline-flex; align-items: center; gap: 10px; }
.storage-step-btn {
    width: 36px; height: 36px; border-radius: 50%; border: 1px solid var(--border);
    background: var(--bg); font-size: 1.1rem; line-height: 1; cursor: pointer; color: var(--text);
    display: inline-flex; align-items: center; justify-content: center; transition: border-color .15s;
}
.storage-step-btn:hover { border-color: var(--brand-accent); }
.storage-step-val { min-width: 24px; text-align: center; font-weight: 700; }
.storage-step-apply:disabled { opacity: .45; cursor: default; }

/* Danger ghost button (cancel) */
.btn-danger-ghost { color: #b42318; }
.btn-danger-ghost:hover { background: #fde8e8; color: #911e12; }

/* Billing history */
.billing-invoices { padding: var(--sp-5) var(--sp-6); }
.billing-invoices-title { font-size: .9rem; font-weight: 700; margin: 0 0 var(--sp-3); }
.billing-invoice-list { list-style: none; margin: 0; padding: 0; }
.billing-invoice-item {
    display: flex; align-items: center; gap: var(--sp-4); padding: 8px 0;
    border-top: 1px solid var(--border); font-size: .85rem;
}
.billing-invoice-item:first-child { border-top: 0; }
.billing-invoice-date { flex: 1 1 auto; color: var(--text-med); }
.billing-invoice-amt { font-weight: 700; }
.billing-invoice-status { font-size: .72rem; text-transform: uppercase; letter-spacing: .02em; color: var(--text-light); }
.billing-invoice-status-paid { color: #1c7a3f; }
.billing-invoice-link { font-weight: 600; }

/* Payment modal */
/* Narrow the dialog itself (not just the form) so the card form fills it edge
   to edge — no dead backdrop gap inside that looked off-center + closed on click. */
.pay-modal { width: min(440px, calc(100vw - 2rem)); }
.pay-element { margin: var(--sp-3) 0 0; }
/* Promo / discount code field in the subscribe modal */
.pay-promo { margin-top: var(--sp-3); }
.pay-promo-row { display: flex; gap: var(--sp-2); }
.pay-promo-row .form-control { flex: 1 1 auto; }
.pay-promo-msg { margin: 6px 0 0; }
.pay-promo-msg.is-ok  { color: #1c7a3f; }
.pay-promo-msg.is-err { color: #b42318; }

/* The Stripe Card Element chrome */
#payment-element {
    border: 1px solid var(--border); border-radius: 10px;
    padding: 14px var(--sp-4); background: var(--bg);
}

/* Cancellation flow */
.cancel-modal { width: min(460px, calc(100vw - 2rem)); }
.cancel-reasons { display: flex; flex-direction: column; gap: var(--sp-2); }
.cancel-reason {
    display: flex; align-items: center; gap: var(--sp-3); cursor: pointer;
    padding: var(--sp-3) var(--sp-4); border: 1px solid var(--border); border-radius: 10px;
    transition: border-color .12s, background .12s;
}
.cancel-reason:hover { border-color: var(--brand-accent); }
.cancel-reason:has(input:checked) { border-color: var(--brand-accent); background: var(--brand-accent-tint, #fff2ec); }
.cancel-reason input { accent-color: var(--brand-accent); }
.cancel-foot { display: flex; flex: 1 1 100%; justify-content: flex-end; gap: var(--sp-3); }
.cancel-foot[hidden] { display: none; }
.cancel-offer { text-align: center; padding: var(--sp-3) 0 var(--sp-2); }
.cancel-offer-badge {
    display: inline-block; font-weight: 800; font-size: .82rem; letter-spacing: .04em;
    color: #fff; background: var(--brand-accent); padding: 5px 12px; border-radius: 999px; margin-bottom: var(--sp-3);
}
.cancel-offer-title { margin: 0 0 var(--sp-2); font-size: 1.3rem; font-weight: 800; }

/* Change-plan itemized breakdown */
.change-plan-modal { width: min(480px, calc(100vw - 2rem)); }
.cp-lines { list-style: none; margin: 0 0 var(--sp-3); padding: 0; }
.cp-line {
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-4);
    padding: 8px 0; border-bottom: 1px solid var(--border); font-size: .9rem;
}
.cp-line:last-child { border-bottom: 0; }
.cp-line-desc { color: var(--text-med); }
.cp-line-amt { font-weight: 700; white-space: nowrap; font-variant-numeric: tabular-nums; }
.cp-line-credit .cp-line-amt { color: #1c7a3f; }   /* credits in green */
.cp-total {
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-4);
    padding-top: var(--sp-3); border-top: 2px solid var(--border); font-size: 1rem;
}
.cp-total strong { font-size: 1.25rem; font-weight: 800; font-variant-numeric: tabular-nums; }

/* Field note — warning variant */
.field-note-warn { color: #9a5b00; }

/* Team page */
.team-seatbar {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-2) var(--sp-4);
    background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-lg);
    padding: var(--sp-5) var(--sp-6); margin-bottom: var(--sp-5);
}
.team-seatbar-count { font-size: 1.6rem; font-weight: 800; }
.team-seatbar-of { font-weight: 600; color: var(--text-light); font-size: 1.1rem; }
.team-seatbar-label { font-size: .9rem; color: var(--text-med); }
.team-seatbar-note { margin-left: auto; font-size: .85rem; color: var(--text-med); }
.team-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.team-member {
    display: flex; align-items: center; gap: var(--sp-4);
    background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-lg);
    padding: var(--sp-4) var(--sp-5);
}
.team-avatar {
    flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%;
    background: var(--brand-accent-tint, #fff2ec); color: var(--brand-accent-strong, #c2410c);
    display: inline-flex; align-items: center; justify-content: center; font-weight: 800;
}
.team-member-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.team-member-name { font-weight: 700; display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.team-member-email { font-size: .85rem; color: var(--text-med); overflow: hidden; text-overflow: ellipsis; }
.team-member-meta { font-size: .8rem; color: var(--text-light); white-space: nowrap; }
.team-chip { font-size: .68rem; font-weight: 700; text-transform: uppercase; letter-spacing: .02em; padding: 2px 8px; border-radius: 999px; background: var(--bg-subtle, #f1f1f0); color: var(--text-med); }
.team-chip-owner { background: #e7f6ec; color: #1c7a3f; }
.team-chip-pending { background: #fef3e2; color: #9a5b00; }
.team-chip-you { background: #eef2ff; color: #4338ca; }

/* Member-row actions: role select + remove */
.team-member-actions { display: flex; align-items: center; gap: var(--sp-2); flex: 0 0 auto; }
.team-member-actions form { margin: 0; }
.form-control-sm { padding: 4px 26px 4px 10px; font-size: .82rem; height: auto; min-height: 34px; }

/* Pending invites */
.team-list-pending { margin-top: var(--sp-2); }
.team-member-pending { border-style: dashed; }
.team-avatar-pending { background: #fef3e2; color: #9a5b00; }

/* Invite modal close affordance */
.settings-modal-x {
    margin-left: auto; background: transparent; border: 0; cursor: pointer;
    width: 36px; height: 36px; border-radius: var(--radius-md, 8px);
    color: var(--text-med); font-size: 1rem; line-height: 1;
}
.settings-modal-x:hover { background: var(--bg-subtle, #f1f1f0); color: var(--text); }

/* Small section heading shared by the team page */
.section-label { font-size: .75rem; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; color: var(--text-light); }

/* Visually-hidden (screen-reader-only) labels */
.sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

@media (max-width: 599px) {
    .team-member-meta { display: none; }
    .team-member { flex-wrap: wrap; }
    .team-member-actions { width: 100%; justify-content: flex-end; }
}

/* Billing-state banner (top of every admin page) */
.portal-billing-banner {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-4);
    padding: var(--sp-3) var(--sp-4); border-radius: var(--radius-md, 10px);
    margin-bottom: var(--sp-5); font-size: .9rem; border: 1px solid transparent;
}
.portal-billing-banner.is-info   { background: var(--brand-accent-tint, #fff2ec); color: var(--brand-accent-strong, #c2410c); border-color: #ffd9c7; }
.portal-billing-banner.is-warn   { background: #fef3e2; color: #9a5b00; border-color: #f6d9a8; }
.portal-billing-banner.is-danger { background: #fde8e8; color: #b42318; border-color: #f6c4c0; }
.portal-billing-banner > span { flex: 1 1 240px; }
.portal-billing-banner-cta { font-weight: 700; white-space: nowrap; color: inherit; text-decoration: underline; }

/* Plans header + monthly/annual toggle */
.plans-head { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--sp-3); margin-bottom: var(--sp-5); }
.plans-title { font-size: 1.35rem; font-weight: 800; margin: 0; }
.billing-toggle { display: inline-flex; background: var(--bg-soft); border: 1px solid var(--border); border-radius: 999px; padding: 3px; }
.billing-toggle-opt {
    display: inline-flex; align-items: center; gap: 8px; border: 0; background: transparent; cursor: pointer;
    font-family: inherit; font-size: .85rem; font-weight: 600; color: var(--text-med);
    padding: 7px 16px; border-radius: 999px; transition: background .15s, color .15s;
}
.billing-toggle-opt.is-active { background: var(--bg); color: var(--text); box-shadow: 0 1px 3px rgba(10,10,10,.08); }
/* "2 months free" only shows when Annual is the selected option, so no one
   reads it as applying to a Monthly selection. */
.billing-toggle-save { font-size: .66rem; font-weight: 700; color: var(--brand-accent); background: var(--brand-accent-tint, #fff2ec); border-radius: 999px; padding: 2px 8px; display: none; }
.billing-toggle-opt.is-active .billing-toggle-save { display: inline-block; }

/* Plan cards — HoneyBook-style: header band + full feature list w/ checks+dashes */
.plans-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(248px, 1fr)); gap: var(--sp-5); align-items: stretch; }
.plan-card {
    position: relative; background: var(--bg); border: 1px solid var(--border);
    border-radius: var(--radius-lg); overflow: hidden;
    display: flex; flex-direction: column;
    transition: box-shadow .15s, border-color .15s;
}
.plan-card:hover { box-shadow: 0 12px 30px rgba(10, 10, 10, .09); }
/* Popular: persistent orange emphasis. No transform — so it never shrinks on hover. */
.plan-card-popular { border-color: var(--brand-accent); box-shadow: 0 12px 30px rgba(255, 107, 53, .16); }
.plan-card-popular:hover { box-shadow: 0 16px 38px rgba(255, 107, 53, .22); }

/* Header band (flush to the top edges) */
.plan-band {
    text-align: center; font-size: .68rem; font-weight: 800; text-transform: uppercase; letter-spacing: .07em;
    padding: 9px; color: #fff;
}
.plan-band-popular { background: var(--brand-accent); }
.plan-band-current { background: var(--ink, #0a0a0a); }
.plan-band.is-empty { background: transparent; }   /* spacer so names align */

.plan-card-inner { padding: var(--sp-6); display: flex; flex-direction: column; flex: 1; }
.plan-card-name { font-size: 1.3rem; font-weight: 800; margin: 0 0 var(--sp-3); }
.plan-price { display: flex; align-items: baseline; gap: 6px; }
.plan-price-amount { font-size: 2.7rem; font-weight: 800; line-height: 1; letter-spacing: -.02em; }
.plan-price-period { font-size: .95rem; font-weight: 600; color: var(--text-light); }
.plan-price-note { font-size: .75rem; color: var(--text-light); margin: 6px 0 var(--sp-4); min-height: 1.1em; }
.plan-card-tagline { font-size: .85rem; line-height: 1.45; color: var(--text-light); margin: 0 0 var(--sp-5); min-height: 2.6em; }

.plan-card-cta { margin-bottom: var(--sp-5); }
.plan-cta-current {
    display: flex; align-items: center; justify-content: center; gap: 7px; width: 100%;
    padding: 11px; border: 1px solid var(--border); border-radius: var(--radius);
    font-size: .9rem; font-weight: 700; color: var(--brand-accent-strong, #c2410c); background: var(--brand-accent-tint, #fff2ec);
}

.plan-features {
    list-style: none; padding: var(--sp-5) 0 0; margin: auto 0 0;
    border-top: 1px solid var(--border-soft);
    display: flex; flex-direction: column; gap: 11px; font-size: .86rem; color: var(--text);
}
.plan-feature { display: flex; align-items: flex-start; gap: 9px; line-height: 1.35; }
.plan-feature i { color: var(--success, #15803d); margin-top: 2px; flex: 0 0 14px; text-align: center; }
.plan-feature-dash { color: var(--text-light); margin-top: 1px; flex: 0 0 14px; text-align: center; font-weight: 700; }
.plan-feature-out { color: var(--text-light); }

/* Signup — "check your inbox" success state */
.signup-sent-icon {
    width: 56px; height: 56px; margin: 0 auto var(--sp-4);
    display: flex; align-items: center; justify-content: center;
    border-radius: 50%; background: var(--brand-accent-tint, #fff2ec);
    color: var(--brand-accent); font-size: 1.4rem;
}
.signup-sent-title { font-size: 1.25rem; font-weight: 800; margin: 0 0 var(--sp-2); }
.dd-action-form { display: inline-block; margin-left: var(--sp-2); }
.form-help {
    display: block;
    font-size: .85rem;
    color: var(--text-light);
    margin-top: var(--sp-2);
}
.form-field {
    margin-bottom: var(--sp-5);
}
.form-control-upper { text-transform: uppercase; }

/* Card-style form container */
.form-card {
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: var(--sp-6) var(--sp-6);
}

/* Sectioned form — premium create/edit layout. One bordered card split into
 * labelled sections (label + description on the left, fields on the right at
 * wide widths), with a footer action bar. Constrained width so fields aren't
 * stretched edge-to-edge. */
.form-stack {
    /* Span the full content width so the card's right edge lines up with the
       page-header actions (Cancel / Save) instead of stopping short. */
    max-width: none;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    overflow: hidden;
}
.settings-subnav ~ .form-stack { max-width: none; }
.settings-subnav ~ .usage-card { max-width: none; }
.form-section {
    padding: var(--sp-6);
    border-bottom: 1px solid var(--border-soft);
}
.form-section-head { margin-bottom: var(--sp-5); }
.form-section-title { font-size: 1rem; font-weight: 700; margin: 0 0 4px; color: var(--text); }
.form-section-sub { font-size: .85rem; line-height: 1.45; color: var(--text-light); margin: 0; }
/* Trim trailing field margin so the section's own padding sets the rhythm. */
.form-section-fields > .form-field:last-child { margin-bottom: 0; }
.form-section-fields > .form-row-2:last-child .form-field { margin-bottom: 0; }
.form-section-fields > .wm-section:last-child { margin-bottom: 0; }
.form-actions {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-5) var(--sp-6);
    background: var(--bg-soft);
}
@media (min-width: 860px) {
    .form-section {
        display: grid;
        grid-template-columns: 248px 1fr;
        gap: var(--sp-7);
        align-items: start;
    }
    .form-section-head { margin-bottom: 0; padding-top: 2px; }
    /* Full-width variant — header stacked on top, content spans the row.
       For wide content like tables that shouldn't be squeezed into a column. */
    .form-section.is-full { display: block; }
    .form-section.is-full .form-section-head { margin-bottom: var(--sp-5); padding-top: 0; }
}

/* Two-column form row that collapses on mobile */
.form-row-2 {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--sp-3) var(--sp-5);
}
@media (min-width: 600px) {
    .form-row-2 { grid-template-columns: 1fr 1fr; }
}

/* Fieldset block (used for grouped address fields) */
.form-fieldset {
    border: 0;
    padding: 0;
    margin: 0 0 var(--sp-5);
}
.form-legend {
    font-size: .92rem;
    font-weight: 500;
    color: var(--text);
    margin: 0 0 var(--sp-3);
    padding: 0;
}

/* Definition-list pattern for read-only detail panels */
.detail-list {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--sp-3);
    margin: 0;
}
.detail-list dt {
    font-size: .8rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--text-light);
    margin: 0;
}
.detail-list dd {
    margin: 2px 0 0;
    color: var(--text);
    font-size: .95rem;
}
@media (min-width: 700px) {
    .detail-list { grid-template-columns: 200px 1fr; align-items: baseline; }
    .detail-list dt { margin-top: 4px; }
    .detail-list dd { margin: 0; }
}
/* Description folded into the Project Details card — a labelled block below the
   detail list, separated by a hairline (only shown when there's text). */
.detail-description {
    margin-top: var(--sp-5);
    padding-top: var(--sp-5);
    border-top: 1px solid var(--border-soft);
}
.detail-description-label {
    display: block;
    font-size: .8rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--text-light);
    margin-bottom: var(--sp-2);
}

/* Detail grid: cards auto-fit. With one card it takes full width; with two
   or more they split into equal columns down to a 20rem minimum. */
.portal-detail-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--sp-5);
}
@media (min-width: 800px) {
    .portal-detail-grid {
        grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
    }
}

/* Inline search field in toolbars */
.portal-search {
    flex: 1;
    min-width: 12rem;
    margin-left: auto;
}
.portal-search .form-control {
    max-width: 24rem;
    margin-left: auto;
}

/* Whole-row click affordance on data tables */
.row-link { cursor: pointer; }
/* Plain links inside clickable rows pick up body text color (no neon
   blue), but exclude .btn-styled anchors so action buttons keep their
   own color rules (e.g. .btn-primary white-on-brand). Same pattern as
   .data-table tbody a:not(.btn) below. */
.row-link a:not(.btn) { color: var(--text); text-decoration: none; font-weight: 500; }
.row-link a:not(.btn):hover { color: var(--brand); }

/* ---------- STATUS BANNERS ---------- */
.status-banner {
    padding: var(--sp-4) var(--sp-5);
    border-left: 4px solid;
    border-radius: var(--radius);
    font-size: .95rem;
}
.status-banner-success { background: var(--success-bg); border-color: var(--success); color: var(--success); }
.status-banner-warn    { background: var(--warn-bg);    border-color: var(--warn);    color: var(--warn);    }
.status-banner-danger  { background: var(--danger-bg);  border-color: var(--danger);  color: var(--danger);  }
.status-banner-info    { background: var(--info-bg);    border-color: var(--info);    color: var(--info);    }

/* ---------- MARKETING LANDING ---------- */
.marketing {
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    background: var(--bg);
}
.marketing-header {
    padding: var(--sp-5) var(--sp-5);
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.marketing-hero {
    flex: 1;
    padding: var(--sp-7) var(--sp-5) var(--sp-9);
    max-width: 40rem;
    width: 100%;
    margin: 0 auto;
    text-align: center;
}
.marketing-hero-headline {
    font-size: 2.25rem;
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -.02em;
    margin: 0 0 var(--sp-5);
    color: var(--text);
}
.marketing-hero-sub {
    font-size: 1.1rem;
    color: var(--text-med);
    margin: 0 0 var(--sp-8);
    line-height: 1.5;
}
.marketing-footer {
    padding: var(--sp-7) var(--sp-5) var(--sp-6);
    text-align: center;
    font-size: .85rem;
    color: var(--text-light);
    font-family: var(--font-mono);
}

/* Waitlist form */
.waitlist-form {
    text-align: left;
}

/* ---------- DESKTOP REFINEMENTS ---------- */
@media (min-width: 600px) {
    .marketing-hero-headline { font-size: 3rem; }
    .marketing-hero-sub      { font-size: 1.2rem; }
    .brand-wordmark-xl       { font-size: 3.75rem; }
    .brand-mark-xl           { width: 5rem; height: 5rem; font-size: 3.5rem; }
}
@media (min-width: 900px) {
    .marketing-hero {
        padding-top: var(--sp-12);
        padding-bottom: var(--sp-12);
        max-width: 44rem;
    }
    .marketing-hero-headline { font-size: 3.5rem; }
    .brand-wordmark-xl       { font-size: 4.5rem; }
    .brand-mark-xl           { width: 6rem; height: 6rem; font-size: 4.25rem; }
}

/* ---------- TENANT PORTAL ----------
   Sidebar nav on desktop (>=900px). On mobile, the sidebar collapses;
   a top bar with hamburger toggles a slide-in drawer. */

.portal-body {
    background: var(--bg-soft);
    min-height: 100vh;
    min-height: 100dvh;
}
.portal-shell {
    min-height: 100vh;
    min-height: 100dvh;
}

/* Approximate topbar heights — used to position sticky panels below
 * them. Don't hand-tune without also adjusting the topbar padding /
 * border. */
:root {
    --portal-topbar-h: 3.75rem;     /* mobile portal-topbar (brand + menu) */
    --admin-topbar-h:  3.5rem;      /* page-title topbar (search + bell + profile) */
}

/* Mobile-first defaults: top bar visible, sidebar acts as a full-width drawer
   that slides in BELOW the topbar (the topbar stays visible above it so the
   close button is always reachable). */
.portal-topbar {
    background: var(--bg);
    border-bottom: 1px solid var(--border);
    padding: var(--sp-3) var(--sp-5);
    height: var(--portal-topbar-h);
    display: flex;
    align-items: center;
    justify-content: space-between;
    position: sticky;
    top: 0;
    z-index: 50;
}
.portal-nav-toggle {
    background: transparent;
    border: 0;
    width: 44px;
    height: 44px;
    padding: 0;
    cursor: pointer;
    position: relative;
    display: inline-block;
}
/* CSS-drawn hamburger that animates into an X.
   Three absolute-positioned bars; outer two rotate ±45° and slide to center,
   middle one fades out. Driven by body.portal-nav-open from the toggle JS. */
.portal-nav-toggle-bar {
    display: block;
    position: absolute;
    left: 11px;
    width: 22px;
    height: 2px;
    background: var(--text);
    border-radius: 1px;
    transition: top .25s ease, transform .25s ease, opacity .15s ease;
}
.portal-nav-toggle-bar:nth-child(1) { top: 14px; }
.portal-nav-toggle-bar:nth-child(2) { top: 21px; }
.portal-nav-toggle-bar:nth-child(3) { top: 28px; }

body.portal-nav-open .portal-nav-toggle-bar:nth-child(1) {
    top: 21px;
    transform: rotate(45deg);
}
body.portal-nav-open .portal-nav-toggle-bar:nth-child(2) {
    opacity: 0;
}
body.portal-nav-open .portal-nav-toggle-bar:nth-child(3) {
    top: 21px;
    transform: rotate(-45deg);
}

@media (prefers-reduced-motion: reduce) {
    .portal-nav-toggle-bar { transition: none; }
}

/* Topbar brand: wordmark | sublabel inline, vertically centered around the
   visual midline of the 'E' in 'Edit'. The sublabel needs line-height: 1
   so its line box doesn't add extra whitespace that throws off alignment. */
.brand-topbar {
    align-items: center;
    gap: var(--sp-3);
    min-width: 0;
    flex: 1;
    overflow: hidden;
}
.brand-sublabel-inline {
    line-height: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}
/* Thin separator between wordmark and sublabel. Drawn as a 1px line,
   slightly shorter than the wordmark's cap height for visual harmony. */
.brand-separator {
    display: inline-block;
    width: 1px;
    height: 1.1rem;
    background: var(--border-strong);
    flex-shrink: 0;
}

.portal-sidebar {
    background: var(--bg-soft);   /* match the page (.portal-body) so chrome is one uniform gray */
    display: flex;       /* always rendered; visibility + transform handle hiding */
    flex-direction: column;
    position: fixed;
    /* Start below BOTH sticky bars — the brand bar (portal-topbar) AND the
     * page-title bar (admin-topbar: search + bell + account), which stays
     * pinned at z:49. Without the admin-topbar offset, that bar overlaps the
     * drawer's first nav item. */
    top: calc(var(--portal-topbar-h) + var(--admin-topbar-h));
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    max-width: none;
    z-index: 40;
    padding: var(--sp-5) var(--sp-4) var(--sp-5);
    overflow-y: auto;

    /* Closed state: slid up out of view behind the topbar, transparent,
       and non-interactive. Visibility hides interaction; transform handles
       the slide; opacity handles the fade. */
    transform: translateY(-8%);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition:
        transform 280ms cubic-bezier(.4, 0, .2, 1),
        opacity 220ms ease,
        visibility 0s linear 280ms;
}
.portal-sidebar.is-open {
    transform: translateY(0);
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition:
        transform 280ms cubic-bezier(.4, 0, .2, 1),
        opacity 220ms ease,
        visibility 0s linear 0s;
}

/* Hide the drawer's own header on mobile — the topbar already shows the brand. */
@media (max-width: 899px) {
    .portal-sidebar-header { display: none; }
    /* More horizontal breathing room in the mobile drawer so nav items + the
       active pill aren't flush to the screen edge (was --sp-4 / 1rem). */
    .portal-sidebar { padding-left: var(--sp-6); padding-right: var(--sp-6); }
}

@media (prefers-reduced-motion: reduce) {
    .portal-sidebar { transition: none; }
}
.portal-sidebar-header { padding: var(--sp-2) var(--sp-3) var(--sp-5); }
.portal-sidebar-header .brand-sublabel { margin: var(--sp-2) 0 0; display: block; }

.portal-sidenav {
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
    flex: 1;
}
/* Grouped nav: labeled sections (Overview / Clients / Work / …). */
.portal-nav-group { display: flex; flex-direction: column; gap: var(--sp-1); }
.portal-nav-grouplabel {
    font-size: .66rem; font-weight: 700; text-transform: uppercase; letter-spacing: .07em;
    color: var(--text-light); padding: 0 var(--sp-3); margin: var(--sp-4) 0 4px;
}
.portal-sidenav > .portal-nav-group:first-child .portal-nav-grouplabel { margin-top: 0; }
/* Collapsed rail: drop the labels, separate groups with a hairline instead. */
body.portal-nav-collapsed .portal-nav-grouplabel { display: none; }
body.portal-nav-collapsed .portal-nav-group + .portal-nav-group {
    border-top: 1px solid var(--border); margin-top: var(--sp-2); padding-top: var(--sp-2);
}
.portal-sidenav-link {
    display: flex;
    align-items: center;
    color: var(--text-med);
    text-decoration: none;
    font-size: .95rem;
    border-radius: var(--radius);
    overflow: hidden;
    transition: background .15s, color .15s;
}
.portal-sidenav-link:hover {
    background: var(--bg-muted);
    color: var(--text);
}
.portal-sidenav-locked { cursor: pointer; opacity: .6; }
.portal-sidenav-locked .portal-sidenav-locktag { margin-left: auto; font-size: .68rem; color: var(--text-light); }
.portal-sidenav-link.is-active {
    background: var(--brand);
    color: var(--text-inverse);
}
.portal-sidenav-link.is-active i { color: var(--text-inverse); }
/* Icon container — its width determines where the icon sits.
   Mobile default; overridden in the desktop media query so it matches the
   collapsed sidebar width and keeps the icon visually centered. */
.portal-sidenav-icon {
    display: inline-block;
    width: 2.5rem;
    text-align: center;
    font-size: 1.05rem;
    line-height: 2.5rem;
    flex-shrink: 0;
}
.portal-sidenav-label {
    padding: 0 var(--sp-3);
    line-height: 2.5rem;
}

.portal-sidebar-footer {
    padding-top: var(--sp-5);
    margin-top: var(--sp-4);
    border-top: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
}
.portal-user-card {
    padding: 0 var(--sp-3);
}
.portal-user-name {
    font-size: .95rem;
    font-weight: 500;
    color: var(--text);
}
.portal-user-role {
    margin-top: 2px;
}

/* Plan + storage snapshot in the sidebar footer — ambient usage + soft upsell. */
.portal-usage {
    display: block;
    margin: 0 calc(-1 * var(--sp-2));
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius);
    text-decoration: none;
    color: inherit;
    transition: background-color .15s ease;
}
.portal-usage:hover { background: var(--bg-muted); }
.portal-usage-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-2);
    margin-bottom: 6px;
}
.portal-usage-plan {
    font-size: .72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--brand-accent-strong);
}
.portal-usage-amt {
    font-size: .72rem;
    color: var(--text-light);
    white-space: nowrap;
}
.portal-usage-bar {
    height: 5px;
    border-radius: var(--radius-pill);
    background: var(--bg-muted);
    overflow: hidden;
}
.portal-usage-bar-fill {
    display: block;
    height: 100%;
    min-width: 3px;
    border-radius: var(--radius-pill);
    background: var(--brand-accent);
    transition: width .3s ease;
}
.portal-usage-bar.is-warn .portal-usage-bar-fill { background: #d97706; }
.portal-usage-bar.is-full .portal-usage-bar-fill { background: var(--danger); }

.btn-full > i { margin-right: var(--sp-2); }

.portal-main {
    padding: var(--sp-6) var(--sp-5) var(--sp-9);
    max-width: 1100px;
    margin: 0 auto;
}

/* Lock body scroll when mobile drawer is open */
body.portal-nav-open { overflow: hidden; }

/* Desktop: static sidebar at 16rem, always expanded. Main content takes the
   remaining viewport width up to a 1280px max, centered. */
@media (min-width: 900px) {
    :root { --portal-sidebar-w: 16rem; }

    .portal-topbar { display: none; }

    .portal-shell {
        display: grid;
        grid-template-columns: var(--portal-sidebar-w) 1fr;
        min-height: 100vh;
    }

    .portal-sidebar {
        display: flex;
        position: sticky;
        top: 0;
        left: auto;
        right: auto;
        bottom: auto;
        width: var(--portal-sidebar-w);
        max-width: none;
        height: 100vh;
        border-right: 1px solid var(--border);
        /* No top padding: the "Edit" brand band (below) is sized to the topbar
           height so it and its divider align with the page-title topbar. */
        padding: 0 var(--sp-4) var(--sp-5);
        /* Reset the mobile drawer animation state. */
        transform: none;
        opacity: 1;
        visibility: visible;
        pointer-events: auto;
        transition: none;
    }

    .portal-sidebar-header {
        padding: 0 var(--sp-2) var(--sp-6);
    }
    /* "Edit" sits in a band the same height as the page-title topbar, so the
       wordmark lines up with the topbar title and the divider beneath it lands
       on the same plane as the topbar's bottom border. */
    .portal-sidebar-header .brand-wordmark {
        display: flex;
        align-items: center;
        height: var(--admin-topbar-h);
        margin: 0 calc(var(--sp-2) * -1);
        padding: 0 var(--sp-2);
        border-bottom: 1px solid var(--border);
        box-sizing: border-box;
    }
    /* Stack the brand vertically in the sidebar so long studio names fit:
       "Edit" wordmark on top, then a subtle divider, then the studio name
       in semibold caps as a sublabel. Each line gets full sidebar width. */
    .brand-sidebar {
        flex-direction: column;
        align-items: stretch;
        gap: 0;
        max-width: 100%;
        min-width: 0;
    }
    .brand-sidebar .brand-separator {
        display: none;
    }
    .brand-sidebar .brand-sublabel-inline {
        margin-top: var(--sp-3);
        padding-top: var(--sp-3);
        padding-bottom: var(--sp-3);
        border-top: 1px solid var(--border-soft);
        border-bottom: 1px solid var(--border-soft);
        font-size: .72rem;
        font-weight: 600;
        letter-spacing: .1em;
        text-transform: uppercase;
        color: var(--text);
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        line-height: 1.2;
    }
    /* White-label sidebar: drop the studio logo into a band the SAME height as
       the page-title topbar, vertically centered, with the divider on the same
       plane as the topbar's bottom border — so the logo lines up with the page
       title instead of floating above it. (Only the white-label hero logo is a
       direct child of .brand-sidebar; the Edit-branded logo nests in
       .brand-id-frame, so it's untouched.) */
    .portal-sidebar-header .brand-sidebar > .brand-logo {
        height: var(--admin-topbar-h);
        max-height: none;
        width: 100%;
        object-fit: contain;
        object-position: center;
        margin: 0 calc(var(--sp-2) * -1);
        padding: 9px var(--sp-2);
        border-bottom: 1px solid var(--border);
        box-sizing: border-box;
    }
    .portal-sidebar-header .brand-sidebar > .brand-logo + .brand-sublabel { margin-top: var(--sp-3); text-align: center; }
    /* Default (Edit-branded) sidebar: the studio logo (when uploaded) AND the
       studio name share a divider-framed slot under the "Edit" wordmark.
       Full-width borders match the original text-only treatment. */
    .portal-sidebar-header .brand-id-frame {
        display: flex;
        flex-direction: column;
        gap: var(--sp-2);
        margin-top: 0;
        padding: var(--sp-4) 0;
        border-bottom: 1px solid var(--border-soft);
        min-width: 0;
    }
    .portal-sidebar-header .brand-id-frame .brand-logo {
        align-self: center;        /* logo centered over the studio name, matching the client portal */
        margin: 0;
        max-height: 44px;
    }
    .portal-sidebar-header .brand-id-frame .brand-id-name {
        font-size: .72rem;
        font-weight: 600;
        letter-spacing: .1em;
        text-transform: uppercase;
        color: var(--text);
        line-height: 1.2;
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .portal-sidenav-link {
        padding: var(--sp-3);
        white-space: nowrap;
    }
    .portal-sidenav-icon {
        width: 1.5rem;
        font-size: 1.05rem;
        line-height: 1;
        margin: 0 var(--sp-3) 0 0;
    }
    .portal-sidenav-label {
        padding: 0;
        line-height: 1;
    }

    .portal-main {
        padding: var(--sp-8) var(--sp-8) var(--sp-9);
        width: 100%;
        max-width: 1280px;
        margin: 0 auto;
    }

    body.portal-nav-open { overflow: auto; }
}

/* Portal-section page header */
.portal-page-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--sp-3);
    margin-bottom: var(--sp-6);
}
.portal-page-title {
    font-size: 1.5rem;
    font-weight: 700;
    letter-spacing: -.02em;
    margin: 0;
    color: var(--text);
}
.portal-page-sub {
    color: var(--text-med);
    font-size: .92rem;
    margin: var(--sp-1) 0 0;
}

/* Stat card grid (dashboard) */
.portal-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--sp-4);
}
.portal-stat-card {
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: var(--sp-5) var(--sp-5);
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    text-decoration: none;
    color: var(--text);
    transition: border-color .15s, background .15s;
}
.portal-stat-card:hover {
    border-color: var(--border-strong);
    background: var(--bg-soft);
}
.portal-stat-label {
    font-size: .85rem;
    color: var(--text-light);
    text-transform: uppercase;
    letter-spacing: .04em;
}
.portal-stat-value {
    font-size: 2rem;
    font-weight: 700;
    letter-spacing: -.02em;
    color: var(--text);
    line-height: 1;
}
@media (min-width: 600px) {
    .portal-grid { grid-template-columns: repeat(3, 1fr); }
}

/* ---------- ADMIN ---------- */
.admin-page {
    min-height: 100vh;
    min-height: 100dvh; /* iOS Safari: avoid 100vh including the area behind browser chrome */
    display: flex;
    flex-direction: column;
    background: var(--bg-soft);
}
.admin-header {
    background: var(--bg);
    border-bottom: 1px solid var(--border);
    padding: var(--sp-4) var(--sp-5);
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--sp-3);
}
.admin-container {
    width: 100%;
    max-width: 1100px;
    margin: 0 auto;
    padding: var(--sp-6) var(--sp-5);
}

/* God-mode nav */
.admin-nav { display: flex; flex-wrap: wrap; gap: var(--sp-1); flex: 1 1 auto; justify-content: center; }
.admin-nav-link {
    padding: 6px 12px; border-radius: 8px; font-size: .9rem; font-weight: 600; color: var(--text-med);
    text-decoration: none; transition: background .12s, color .12s;
}
.admin-nav-link:hover { background: var(--bg-soft); color: var(--text); }
.admin-nav-link.is-active { background: var(--brand-accent-tint, #fff2ec); color: var(--brand-accent-strong, #c2410c); }
.admin-nav-badge {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 18px; height: 18px; margin-left: 6px; padding: 0 5px;
    border-radius: 999px; background: var(--danger, #991b1b); color: #fff;
    font-size: .68rem; font-weight: 700; line-height: 1; vertical-align: middle;
}
.admin-header .brand-sublabel { color: var(--brand-accent-strong, #c2410c); font-weight: 700; }

/* RAW grouped nav: a top-level group opens a dropdown on hover (desktop) or tap. */
.admin-nav-group { position: relative; display: inline-flex; }
.admin-nav-grouphead { appearance: none; border: 0; background: transparent; cursor: pointer; font: inherit; display: inline-flex; align-items: center; gap: 4px; }
.admin-nav-caret { width: 14px; height: 14px; opacity: .55; transition: transform .15s; }
.admin-nav-group.is-active > .admin-nav-grouphead { background: var(--brand-accent-tint, #fff2ec); color: var(--brand-accent-strong, #c2410c); }
.admin-nav-dropdown {
    /* Shown via opacity/visibility (not display) with a short close delay so a
       brief mouse slip doesn't snap it shut. The visible gap below the button is
       bridged by .admin-nav-group::after (an invisible strip that is part of the
       group, so hovering it keeps the group hovered). No transform offset: that
       would push the panel into an unhoverable dead zone, which is the bug. */
    position: absolute; top: 100%; left: 0; z-index: 60; margin-top: 8px;
    min-width: 12rem; padding: 6px; display: flex; flex-direction: column; gap: 2px;
    background: var(--bg, #fff); border: 1px solid var(--border); border-radius: 10px; box-shadow: var(--shadow-lg);
    opacity: 0; visibility: hidden; transform: translateY(-4px); pointer-events: none;
    transition: opacity .13s ease, transform .13s ease, visibility 0s linear .2s;
}
/* Invisible hover bridge spanning the gap between the button and the menu, so the
   mouse can cross it without the dropdown vanishing. Inert until the group is
   hovered, so it never blocks clicks on the page beneath when the menu is closed. */
.admin-nav-group::after {
    content: ''; position: absolute; left: 0; right: 0; top: 100%; height: 14px; pointer-events: none;
}
.admin-nav-group:hover::after,
.admin-nav-group:focus-within::after,
.admin-nav-group.is-expanded::after { pointer-events: auto; }
.admin-nav-group:hover > .admin-nav-dropdown,
.admin-nav-group:focus-within > .admin-nav-dropdown,
.admin-nav-group.is-expanded > .admin-nav-dropdown {
    opacity: 1; visibility: visible; transform: translateY(0); pointer-events: auto;
    transition: opacity .13s ease, transform .13s ease;
}
.admin-nav-group:hover .admin-nav-caret,
.admin-nav-group.is-expanded .admin-nav-caret { transform: rotate(180deg); }
.admin-nav-dropitem {
    display: flex; align-items: center; padding: 8px 10px; border-radius: 7px;
    font-size: .9rem; font-weight: 600; color: var(--text-med); text-decoration: none; white-space: nowrap;
    transition: background .12s, color .12s;
}
.admin-nav-dropitem:hover { background: var(--bg-soft); color: var(--text); }
.admin-nav-dropitem.is-active { background: var(--brand-accent-tint, #fff2ec); color: var(--brand-accent-strong, #c2410c); }

/* Mobile: collapse the RAW nav behind a hamburger (desktop shows it inline). */
.admin-nav-toggle { display: none; }
@media (max-width: 760px) {
    .admin-nav-toggle {
        display: inline-flex; align-items: center; justify-content: center;
        width: 44px; height: 44px; padding: 0; order: 3;
        background: var(--bg); border: 1px solid var(--border); border-radius: 8px; cursor: pointer;
    }
    .admin-nav-toggle span,
    .admin-nav-toggle span::before,
    .admin-nav-toggle span::after {
        content: ''; display: block; position: relative;
        width: 18px; height: 2px; background: var(--text); border-radius: 2px;
    }
    .admin-nav-toggle span::before { position: absolute; top: -6px; }
    .admin-nav-toggle span::after  { position: absolute; top: 6px; }
    .admin-header-signout { order: 2; }
    .admin-nav {
        display: none; order: 4; flex-basis: 100%;
        flex-direction: column; align-items: stretch; justify-content: flex-start; gap: 2px;
    }
    .admin-nav.is-open { display: flex; }
    .admin-nav-link { padding: 11px 12px; }   /* >=44px tap targets */
    /* Stacked menu: groups + their items render as expanded indented sections. */
    .admin-nav-group { display: block; }
    .admin-nav-grouphead { width: 100%; justify-content: flex-start; padding: 11px 12px; }
    .admin-nav-caret { display: none; }
    .admin-nav-dropdown { display: flex; position: static; min-width: 0; margin-top: 0; padding: 0 0 6px 12px; border: 0; box-shadow: none; background: transparent;
        opacity: 1; visibility: visible; transform: none; pointer-events: auto; transition: none; }
    .admin-nav-group::after { display: none; }   /* no hover bridge in the stacked menu */
    .admin-nav-dropitem { padding: 10px 12px; }
}

/* God-mode metric cards */
.gm-metrics { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-4); margin-bottom: var(--sp-6); }
@media (min-width: 760px) { .gm-metrics { grid-template-columns: repeat(4, 1fr); } }
.gm-metric { background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--sp-5); }
.gm-metric-link { display: block; text-decoration: none; color: inherit; transition: border-color .12s, box-shadow .12s, transform .12s; }
.gm-metric-link:hover { border-color: var(--brand-accent); box-shadow: 0 2px 12px rgba(0,0,0,.06); transform: translateY(-1px); }
.gm-metric-link:hover .gm-metric-sub { color: var(--brand-accent-strong); }
.gm-metric-label { font-size: .72rem; text-transform: uppercase; letter-spacing: .05em; color: var(--text-light); }
.gm-metric-label-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2, 8px); }
.gm-metric-select { font-size: .7rem; color: var(--text-med); background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-sm, 6px); padding: 2px 6px; cursor: pointer; text-transform: none; letter-spacing: 0; }
.gm-metric-value { font-size: 1.6rem; font-weight: 800; margin-top: 4px; }
.gm-metric-sub { font-size: .8rem; color: var(--text-med); margin-top: 2px; }

/* God-mode distribution breakdown (e.g. studios by photography type): a labeled
   horizontal-bar list. Rows are links that filter the table below. */
.gm-dist { background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--sp-5); margin-bottom: var(--sp-6); }
.gm-dist-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-4); }
.gm-dist-row { display: flex; align-items: center; gap: var(--sp-3); padding: 5px 6px; border-radius: var(--radius); text-decoration: none; color: inherit; }
a.gm-dist-row:hover { background: var(--bg-soft); }
.gm-dist-row.is-active { background: var(--brand-accent-tint); }
.gm-dist-label { flex: 0 0 12rem; font-size: .85rem; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gm-dist-track { flex: 1 1 auto; min-width: 40px; height: 8px; background: var(--bg-muted); border-radius: var(--radius-pill); overflow: hidden; }
.gm-dist-bar { display: block; height: 100%; min-width: 3px; background: var(--brand-accent); border-radius: var(--radius-pill); }
.gm-dist-bar-muted { background: var(--border-strong); }
.gm-dist-val { flex: 0 0 auto; width: 86px; text-align: right; font-family: var(--font-mono); font-size: .72rem; color: var(--text-light); }
.gm-dist-row-muted .gm-dist-label { color: var(--text-light); }
@media (max-width: 600px) { .gm-dist-label { flex-basis: 7.5rem; } .gm-dist-val { width: 72px; } }

/* Legal docs (Terms / Privacy) */
.legal-page { min-height: 100vh; background: var(--bg); }
.legal-header {
    display: flex; align-items: center; justify-content: space-between;
    max-width: 760px; margin: 0 auto; padding: var(--sp-5) var(--sp-5) 0;
}
.legal-nav { display: flex; gap: var(--sp-4); font-size: .9rem; }
.legal-nav a { color: var(--text-med); text-decoration: none; }
.legal-nav a:hover { color: var(--brand-accent); }
.legal-doc { max-width: 760px; margin: 0 auto; padding: var(--sp-5) var(--sp-5) var(--sp-8); line-height: 1.7; color: var(--text); }
.legal-title { font-size: 2rem; font-weight: 800; margin: var(--sp-5) 0 4px; }
.legal-updated { color: var(--text-light); font-size: .85rem; margin: 0 0 var(--sp-6); }
.legal-doc h2 { font-size: 1.2rem; font-weight: 800; margin: var(--sp-6) 0 var(--sp-2); }
.legal-doc p, .legal-doc li { color: var(--text-med); }
.legal-doc ul { padding-left: 1.2em; margin: var(--sp-2) 0; }
.legal-doc li { margin-bottom: 6px; }
.legal-doc a { color: var(--brand-accent-strong, #c2410c); }
.legal-rule { border: 0; border-top: 1px solid var(--border); margin: var(--sp-6) 0 var(--sp-4); }
.legal-foot { font-size: .85rem; color: var(--text-light); }
.lp-footer-legal { font-size: .85rem; }
.lp-footer-legal a { color: inherit; text-decoration: underline; opacity: .85; }
.lp-footer-legal a:hover { opacity: 1; }

/* Settings card (e.g. custom domain) */
.settings-card {
    background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-lg);
    padding: var(--sp-5) var(--sp-6);
}
.settings-card-title { font-size: 1.05rem; font-weight: 700; margin: 0 0 var(--sp-2); }
.cd-state { display: inline-flex; align-items: center; gap: 8px; font-weight: 600; margin: 0 0 var(--sp-3); }
.cd-state-ok { color: #1c7a3f; }
.cd-state-pending { color: #9a5b00; }
.cd-add { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: center; }
.cd-add .form-control { flex: 1 1 280px; max-width: 360px; }
.cd-dns {
    display: flex; flex-direction: column; gap: 6px;
    background: var(--bg-soft); border: 1px solid var(--border); border-radius: 10px;
    padding: var(--sp-3) var(--sp-4); font-family: var(--font-mono, monospace); font-size: .85rem;
    word-break: break-all;
}
.cd-dns-k { display: inline-block; min-width: 64px; color: var(--text-light); font-family: var(--font-sans, inherit); }

/* God-mode storage-by-type rows */
.gm-storagerow { margin-bottom: var(--sp-3); }
.gm-storagerow:last-child { margin-bottom: 0; }
.gm-storagerow-head { display: flex; justify-content: space-between; font-size: .85rem; margin-bottom: 5px; }

/* God-mode profile action panels */
.gm-panel { background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--sp-5) var(--sp-6); margin-bottom: var(--sp-5); }
.gm-panel-title { font-size: .95rem; font-weight: 700; margin: 0 0 var(--sp-4); }
/* Studio clients list (RAW): a client's team members nest under it, expandable. */
.gm-client-toggle { appearance: none; background: none; border: 0; cursor: pointer; padding: 2px; margin-right: 4px; color: var(--text-light); vertical-align: middle; line-height: 0; }
.gm-client-toggle svg, .gm-client-toggle i { width: 15px; height: 15px; transition: transform .15s; }
.gm-client-toggle[aria-expanded="true"] svg, .gm-client-toggle[aria-expanded="true"] i { transform: rotate(90deg); }
.gm-client-sub > td { background: var(--bg-soft); }
.gm-client-sub-name { padding-left: 20px; }
.gm-client-sub-arrow { color: var(--text-light); }
.gm-client-role { font-size: .66rem; font-weight: 600; text-transform: uppercase; letter-spacing: .03em; color: var(--text-med); background: var(--bg-muted); border-radius: 100px; padding: 1px 7px; margin-left: 6px; }
.gm-flag { margin: 0 0 var(--sp-4); padding: var(--sp-3) var(--sp-4); border: 1px solid var(--border); border-left-width: 3px; border-radius: var(--radius); background: var(--bg-soft); }
.gm-flag-ok { border-left-color: #16a34a; }
.gm-help { max-width: 460px; margin: var(--sp-3) 0 0; line-height: 1.5; }
.gm-help-top { margin: 0 0 var(--sp-4); }
.gm-kv { display: grid; grid-template-columns: minmax(140px, max-content) 1fr; gap: 6px var(--sp-5); font-size: .9rem; }
.gm-kv dt { color: var(--text-med); }
.gm-kv dd { margin: 0; font-weight: 600; word-break: break-word; }
.d-inline { display: inline-block; }

/* Canonical checkout (app.tryedit.co) plan summary. */
.co-summary {
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4); margin-bottom: var(--sp-3);
    background: var(--brand-accent-tint); border-radius: var(--radius);
}
.co-summary-plan { font-weight: 700; }
.co-summary-price { font-weight: 700; color: var(--brand-accent-strong); }

/* Express wallets (Apple Pay / Google Pay / Link) sit above the card form.
   Hidden until checkout.js confirms at least one is usable on this device. */
.co-express { margin-bottom: var(--sp-4); }
.co-express[hidden] { display: none; }
.co-express #express-checkout { min-height: 48px; }
.co-or {
    display: flex; align-items: center; gap: var(--sp-3);
    margin-top: var(--sp-4);
    color: var(--text-light); font-size: .8rem; font-weight: 600;
    text-transform: uppercase; letter-spacing: .04em;
}
.co-or::before, .co-or::after {
    content: ""; flex: 1; height: 1px; background: var(--border);
}

/* Compact card field (Stripe Card Element): one clean row, no Payment-Element
   baggage. Stripe adds .StripeElement--focus / --invalid to the mount node. */
.co-card-label {
    display: block; margin: 0 0 6px;
    font-size: .8rem; font-weight: 600; color: var(--text-med);
}
.co-card-field {
    padding: 13px 14px;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 10px;
    transition: border-color .15s ease, box-shadow .15s ease;
}
.co-card-field.StripeElement--focus {
    border-color: var(--brand);
    box-shadow: 0 0 0 3px rgba(255, 107, 53, .12);
}
.co-card-field.StripeElement--invalid { border-color: #b42318; }

/* First-run welcome guide (dashboard) — a warm note, intentionally distinct
   from the core dashboard cards. Orientation, not a checklist. */
.welcome-card {
    position: relative;
    background: linear-gradient(135deg, var(--brand-accent-tint) 0%, #fff 72%);
    border: 1px solid #f6d6c5;   /* warm accent-tint border */
    border-radius: var(--radius-lg); padding: var(--sp-6); margin-bottom: var(--sp-6);
    box-shadow: 0 8px 28px rgba(255, 107, 53, .10);
    transition: opacity .2s ease, transform .2s ease;
}
.welcome-card.is-hiding { opacity: 0; transform: translateY(-6px); }
.welcome-dismiss {
    position: absolute; top: var(--sp-3); right: var(--sp-3);
    background: none; border: 0; font-size: 1.4rem; line-height: 1; color: var(--brand-accent-strong);
    cursor: pointer; padding: 6px 10px; border-radius: var(--radius-sm); opacity: .6;
}
.welcome-dismiss:hover { opacity: 1; background: rgba(255, 107, 53, .12); }
.welcome-title {
    font-family: 'Caveat', var(--font-display), cursive;
    font-size: 2.6rem; font-weight: 700; line-height: 1; color: var(--brand-accent-strong);
    margin: 0 0 2px; padding-right: var(--sp-7);
}
/* "Edit" rendered as the crisp brand wordmark (all black) inside the script heading. */
.welcome-wordmark {
    font-family: var(--font-display), 'Inter', system-ui, sans-serif;
    font-weight: 800; letter-spacing: -.045em; font-size: .74em; color: var(--ink);
}
/* Lead reads as a prominent subtitle (h3-ish) under the heading. */
.welcome-lead { color: var(--text-med); font-size: 1.15rem; font-weight: 500; line-height: 1.45; margin: 0 0 var(--sp-5); max-width: 60ch; }
.welcome-grid { display: grid; grid-template-columns: 1fr; gap: var(--sp-3); }
@media (min-width: 600px)  { .welcome-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .welcome-grid { grid-template-columns: repeat(3, 1fr); } }
.welcome-tile {
    display: block; padding: var(--sp-3) var(--sp-4);
    background: #fff; border: 1px solid #f1ddd3; border-radius: var(--radius);
    text-decoration: none; color: inherit;
    transition: border-color .12s, background-color .12s, box-shadow .12s, transform .12s;
}
.welcome-tile:hover { border-color: var(--brand-accent); box-shadow: 0 4px 14px rgba(255,107,53,.12); transform: translateY(-1px); }
/* Quiet "you've been here" confirmation once a tile's destination is visited —
   success green (matches the "all caught up" banner), not muddy orange. */
.welcome-tile:visited { background: var(--success-bg); border-color: var(--success-border); }
.welcome-tile:visited .welcome-tile-title { color: var(--success); }
.welcome-tile:visited .welcome-tile-icon  { color: var(--success); }
.welcome-tile-icon  { color: var(--brand-accent); font-size: 1.05rem; }
.welcome-tile-title { display: block; font-weight: 700; margin-top: 6px; }
.welcome-tile-desc  { display: block; font-size: .82rem; color: var(--text-med); margin-top: 2px; }
.welcome-foot { margin: var(--sp-4) 0 0; font-size: .85rem; color: var(--text-med); }
/* Minimal post-walkthrough greeting (just the heading). */
.welcome-card-mini { padding: var(--sp-4) var(--sp-5); }
.welcome-card-mini .welcome-title { margin: 0; }

/* ── First-run walkthrough carousel ─────────────────────────────────────── */
body.tour-open { overflow: hidden; }
.tour-overlay {
    position: fixed; inset: 0; z-index: 2000;
    display: flex; align-items: center; justify-content: center; padding: var(--sp-4);
    background: rgba(10, 10, 10, .75); -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
    animation: tourFadeIn .25s ease;
}
.tour-overlay.is-closing { opacity: 0; transition: opacity .25s ease; }
@keyframes tourFadeIn { from { opacity: 0; } to { opacity: 1; } }
.tour-modal {
    position: relative; width: 100%; max-width: 520px; background: #fff;
    border-radius: var(--radius-lg); box-shadow: 0 24px 60px rgba(0, 0, 0, .3);
    overflow: hidden; display: flex; flex-direction: column;
    animation: tourPop .3s cubic-bezier(.2, .8, .2, 1);
}
@keyframes tourPop { from { opacity: 0; transform: translateY(12px) scale(.98); } to { opacity: 1; transform: none; } }
.tour-skip {
    position: absolute; top: 12px; right: 12px; z-index: 2;
    background: none; border: 0; color: var(--text-light); font-size: .85rem; font-weight: 600;
    cursor: pointer; padding: 8px 12px; border-radius: var(--radius-sm);
}
.tour-skip:hover { color: var(--text); background: var(--bg-muted); }
.tour-viewport { overflow: hidden; }
.tour-track { display: flex; transition: transform .35s cubic-bezier(.2, .7, .2, 1); }
.tour-slide { flex: 0 0 100%; min-width: 100%; padding: var(--sp-7) var(--sp-6) var(--sp-5); text-align: center; }
.tour-art {
    width: 100%; max-width: 280px; margin: 0 auto var(--sp-4);
    background: linear-gradient(135deg, var(--brand-accent-tint), #fff);
    border: 1px solid #f6e2d8; border-radius: var(--radius-lg); padding: var(--sp-4);
}
.tour-art svg { width: 100%; height: auto; display: block; }
.tour-slide.is-active .tour-art { animation: tourArtIn .45s ease both; }
@keyframes tourArtIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.tour-eyebrow { font-size: .72rem; text-transform: uppercase; letter-spacing: .08em; color: var(--brand-accent-strong); font-weight: 700; margin: 0 0 6px; }
.tour-title { font-size: 1.4rem; font-weight: 800; letter-spacing: -.02em; margin: 0 0 8px; color: var(--ink); }
.tour-body { color: var(--text-med); line-height: 1.55; margin: 0 auto; max-width: 40ch; font-size: .95rem; }
.tour-foot {
    display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
    padding: var(--sp-4) var(--sp-5); border-top: 1px solid var(--border-soft);
}
.tour-dots { display: flex; gap: 7px; }
.tour-dot { width: 7px; height: 7px; border-radius: 999px; background: var(--border-strong); cursor: pointer; transition: background .15s, width .15s; }
.tour-dot.is-active { background: var(--brand-accent); width: 20px; }
.tour-actions { display: flex; gap: var(--sp-2); align-items: center; }
@media (max-width: 600px) {
    .tour-overlay { padding: 0; }
    .tour-modal { max-width: none; height: 100%; border-radius: 0; }
    /* Viewport grows to fill (pins the footer to the bottom) and scrolls
       vertically if a slide is taller than the screen, e.g. landscape phones,
       instead of clipping the content. overflow-x stays hidden for the carousel. */
    .tour-viewport { flex: 1 1 auto; min-height: 0; overflow-x: hidden; overflow-y: auto; }
    .tour-slide { padding-top: var(--sp-9); }
    .tour-title { font-size: 1.3rem; }
}

/* Feedback widget — floating button + type picker. */
.feedback-fab {
    position: fixed; right: var(--sp-4); bottom: var(--sp-4); z-index: 60;
    display: inline-flex; align-items: center; gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-4); border: none; border-radius: var(--radius-pill);
    background: var(--ink); color: var(--text-inverse); font-size: .85rem; font-weight: 600;
    cursor: pointer; box-shadow: 0 4px 16px rgba(0,0,0,.18); transition: transform .12s ease, background-color .12s ease;
}
.feedback-fab:hover { background: var(--brand-accent-strong); transform: translateY(-1px); }
.feedback-fab i { font-size: .95rem; }
@media (max-width: 600px) {
    .feedback-fab { padding: var(--sp-3); }
    .feedback-fab span { display: none; }   /* icon-only on phones to stay out of the way */
}
.feedback-kinds { display: flex; gap: var(--sp-2); }
.feedback-kind { flex: 1; }
.feedback-kind input { position: absolute; opacity: 0; pointer-events: none; }
.feedback-kind span {
    display: block; text-align: center; padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--border); border-radius: var(--radius); font-size: .85rem; cursor: pointer;
    transition: border-color .12s, background-color .12s;
}
.feedback-kind input:checked + span { border-color: var(--brand-accent); background: var(--brand-accent-tint); font-weight: 600; }
.feedback-kind input:focus-visible + span { outline: 2px solid var(--brand-accent); outline-offset: 2px; }

/* Per-studio error log (RAW detail page). */
.gm-errlog { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.gm-err { padding: var(--sp-3); border: 1px solid var(--border); border-left-width: 3px; border-radius: var(--radius); background: var(--bg-soft); }
.gm-err-fatal { border-left-color: var(--danger); }
.gm-err-error { border-left-color: #d97706; }
.gm-err-warning { border-left-color: var(--text-light); }
.gm-err-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); margin-bottom: 4px; font-size: .72rem; }
.gm-err-level { font-weight: 700; letter-spacing: .04em; }
.gm-err-fatal .gm-err-level, .gm-err-error .gm-err-level { color: var(--danger); }
.gm-err-time { color: var(--text-med); }
.gm-err-src { color: var(--text-light); font-family: var(--font-mono, ui-monospace, monospace); }
.gm-err-msg { font-size: .88rem; color: var(--text); word-break: break-word; white-space: pre-wrap; }
.gm-err-url { font-size: .72rem; color: var(--text-light); word-break: break-all; margin-top: 2px; }
.gm-err-detail { margin-top: var(--sp-2); font-size: .76rem; }
.gm-err-detail summary { cursor: pointer; color: var(--brand-accent-strong); }
.gm-err-detail pre { margin: var(--sp-2) 0 0; padding: var(--sp-2); background: var(--bg-muted); border-radius: var(--radius-sm); overflow-x: auto; white-space: pre-wrap; word-break: break-word; font-size: .72rem; color: var(--text-med); }

.gm-actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: flex-end; }
.gm-actions form { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: flex-end; }
.gm-actions .form-field { margin: 0; }
.gm-input-sm { max-width: 120px; }
.gm-upper { text-transform: uppercase; }

/* God-mode studios filter bar */
.gm-filter { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: flex-end; margin-bottom: var(--sp-5); }
.gm-filter input[type="search"] { max-width: 360px; }
.gm-filter select { max-width: 200px; }

/* God-mode impersonation banner (on the studio's own portal) */
.god-banner {
    position: sticky; top: 0; z-index: 1000;
    display: flex; align-items: center; justify-content: center; gap: var(--sp-3);
    background: #1f2937; color: #fff; padding: 8px var(--sp-4); font-size: .85rem; font-weight: 600;
}
.god-banner a { color: #fff; text-decoration: underline; }

/* Trial → paid nudge bar (top of the portal during/after trial). */
.trial-banner {
    display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4);
    background: var(--brand-accent-tint); color: var(--ink);
    padding: 9px var(--sp-4); font-size: .85rem; text-align: center;
    border-bottom: 1px solid var(--warn-border);
}
.trial-banner-ended { background: var(--danger-bg); border-bottom-color: var(--danger-border); }
/* Escalation: warm at ≤3 days, stronger at ≤1 day / expired. */
.trial-banner-warn   { background: #fff4e5; border-bottom-color: #f5c98a; }
.trial-banner-urgent { background: #ffe9d6; border-bottom-color: #f0a35a; font-weight: 500; }
.trial-banner-dismiss {
    background: none; border: none; cursor: pointer; color: inherit; opacity: .55;
    font-size: 1.2rem; line-height: 1; padding: 0 4px; margin-left: 2px;
}
.trial-banner-dismiss:hover { opacity: 1; }
/* In-content placement (flows with the page, doesn't push the app chrome). */
.portal-content > .trial-banner {
    margin-bottom: var(--sp-5);
    border-radius: 12px;
    border-bottom: 0;
}
/* Read-only lockdown — write controls disabled while billing has lapsed. */
.is-readonly-locked { cursor: not-allowed !important; opacity: .55; }
/* Pause-duration chooser in the billing pause modal. */
.pause-duration { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.pause-duration-opt {
    flex: 1 1 0; min-width: 92px; display: flex; align-items: center; justify-content: center; gap: 6px;
    padding: 10px; border: 1px solid var(--border); border-radius: 10px; cursor: pointer; font-size: .9rem;
}
.pause-duration-opt:has(input:checked) { border-color: var(--brand-accent); background: var(--brand-accent-tint); font-weight: 600; }
.trial-banner-cta {
    flex-shrink: 0; display: inline-block; padding: 5px 14px; border-radius: var(--radius-pill);
    background: var(--ink); color: var(--text-inverse); font-weight: 600; font-size: .8rem; text-decoration: none;
}
.trial-banner-cta:hover { background: var(--brand-accent-strong); }
@media (max-width: 600px) { .trial-banner { font-size: .8rem; padding: 8px var(--sp-3); } }
.admin-login-shell {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sp-7) var(--sp-5);
}
.admin-login-card {
    width: 100%;
    max-width: 22rem;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: var(--sp-7);
    box-shadow: var(--shadow);
}

/* Signup is a longer form than login — give it a wider card and a two-column
   grid on desktop so it isn't a tall, narrow strip with empty space beside it. */
.admin-signup-card { max-width: 40rem; }
.signup-grid { display: grid; grid-template-columns: 1fr; gap: 0 var(--sp-5); }
.signup-grid > .form-field { margin-bottom: var(--sp-5); }
@media (min-width: 600px) {
    .signup-grid { grid-template-columns: 1fr 1fr; }
    /* Items that should always run the full width of the form. */
    .signup-grid > .signup-span { grid-column: 1 / -1; }
}
/* Long slugs must wrap inside the card, never overflow past its edge. */
.slug-status, .form-help { overflow-wrap: anywhere; }
.form-help code, [data-slug-preview] { overflow-wrap: anywhere; word-break: break-word; }

/* Data table (port from TP CLAUDE pattern) */
.data-table-wrap {
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    overflow: hidden;
}
.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: .92rem;
}
.data-table th, .data-table td {
    text-align: left;
    padding: var(--sp-3) var(--sp-4);
    border-bottom: 1px solid var(--border-soft);
    vertical-align: top;
}
.data-table th {
    background: var(--bg-soft);
    font-weight: 600;
    color: var(--text-med);
    font-size: .82rem;
    text-transform: uppercase;
    letter-spacing: .02em;
}
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table tbody tr:hover { background: var(--bg-soft); }
.data-table form { display: inline; margin: 0; }

/* Anchors inside table rows get an explicit hover underline so users can
   tell they're a separate clickable target from the whole-row .row-link.
   Without this, hovering a row that ALSO contains an anchor child gives
   no visual cue that the anchor will go somewhere different. */
/* Plain links inside table rows pick up the body text color (no neon-blue
   default), but exclude .btn-styled anchors so an inline action button
   keeps its own color (e.g. .btn-primary white-on-brand). Without the
   :not(.btn), the Pay button on the client invoices list renders as
   black text on black bg = invisible. */
.data-table tbody a:not(.btn) {
    color: var(--text);
    text-decoration: none;
    transition: color .12s ease;
}
.data-table tbody a:not(.btn):hover {
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-thickness: 1px;
}

/* Mobile-friendly card layout for narrow screens */
@media (max-width: 599px) {
    .data-table thead { display: none; }
    .data-table, .data-table tbody, .data-table tr, .data-table td {
        display: block;
        width: 100%;
    }
    .data-table tr {
        border-bottom: 1px solid var(--border);
        padding: var(--sp-3) 0;
    }
    .data-table td {
        border-bottom: none;
        padding: var(--sp-1) var(--sp-4);
    }
    .data-table td::before {
        content: attr(data-label);
        display: block;
        font-size: .72rem;
        text-transform: uppercase;
        color: var(--text-light);
        letter-spacing: .05em;
        margin-bottom: 2px;
    }
}

.empty-state {
    padding: var(--sp-10) var(--sp-5);
    text-align: center;
    color: var(--text-light);
}

/* ---------- STATUS CHIPS ----------
   Reusable pills for status badges in tables AND status filter chips.
   The same class hierarchy works for both — link variants add hover/active
   states on top of the colored backgrounds. */
.status-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    padding: .3rem .7rem;
    font-size: .78rem;
    font-weight: 500;
    border-radius: var(--radius-pill);
    text-decoration: none;
    white-space: nowrap;
    line-height: 1;
    background: var(--bg-muted);
    color: var(--text-med);
    border: 1px solid transparent;
}
a.status-chip { transition: opacity .15s, box-shadow .15s; }
a.status-chip:hover { opacity: .85; }
a.status-chip.is-active {
    box-shadow: 0 0 0 2px var(--text);
}

/* Per-status colors (used both as table badges and as colored filter chips) */
.status-chip-tentative       { background: #fef3c7;           color: #92400e; } /* amber-100 / amber-800 — pre-booked / scheduling */
.status-chip-booked          { background: var(--info-bg);    color: var(--info); }
.status-chip-proofing        { background: var(--bg-muted);   color: var(--text-light); } /* gray — proofing is a passive "waiting on client" state */
.status-chip-post_processing { background: #f3e8ff;           color: #6b21a8; }
.status-chip-delivered       { background: var(--success-bg); color: var(--success); }
.status-chip-cancelled,
.status-chip-archived        { background: var(--bg-muted);   color: var(--text-light); }
.status-chip-suspended       { background: #fef3c7;           color: #92400e; } /* amber — access paused */

.status-chips {
    flex-wrap: wrap;
    gap: var(--sp-2);
}
.t-warn { color: var(--warn); }

/* Client-facing "portal suspended" locked screen (client/suspended.php). */
.client-locked-page { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: var(--sp-6); background: var(--bg-muted); }
.client-locked-card { width: 100%; max-width: 30rem; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--sp-7); box-shadow: var(--shadow); text-align: center; }
.client-locked-icon { width: 52px; height: 52px; margin: 0 auto var(--sp-4); display: flex; align-items: center; justify-content: center; border-radius: 999px; background: #fef3c7; color: #92400e; }
.client-locked-icon i { width: 26px; height: 26px; }
.client-locked-title { margin: 0 0 var(--sp-3); font-size: 1.4rem; }
.client-locked-msg { margin: 0 0 var(--sp-2); color: var(--text-med); line-height: 1.5; }
.client-locked-sub { margin: 0 0 var(--sp-2); font-size: .85rem; color: var(--text-light); }
.client-locked-actions { margin-top: var(--sp-5); display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); }

/* ---------- ACTIVITY TIMELINE ----------
   Dot + content layout used on project view pages. */
.activity-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
}
.activity-list > li {
    display: flex;
    gap: var(--sp-3);
    align-items: flex-start;
}
.activity-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--brand);
    margin-top: 7px;
    flex-shrink: 0;
}

/* ---------- FLATPICKR OVERRIDES ----------
   Brand-matched theme on top of the vendor base. Selectors use the
   .flatpickr-* class names from the library. */
.flatpickr-calendar {
    font-family: var(--font-sans);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    border: 1px solid var(--border);
    background: var(--bg);
}
/* Date fields inside a <dialog> render flatpickr with static:true (portal.js) so
   the calendar shows IN the modal's top layer instead of behind its backdrop.
   Keep the wrapped field full-width and lift the static calendar above siblings. */
dialog .flatpickr-wrapper { display: block; width: 100%; }
dialog .flatpickr-calendar.static { z-index: 10; }
.flatpickr-calendar.arrowTop:before,
.flatpickr-calendar.arrowBottom:before {
    border-bottom-color: var(--border);
    border-top-color: var(--border);
}
.flatpickr-calendar.arrowTop:after,
.flatpickr-calendar.arrowBottom:after {
    border-bottom-color: var(--bg);
    border-top-color: var(--bg);
}
.flatpickr-months,
.flatpickr-current-month,
.flatpickr-monthDropdown-months,
.flatpickr-current-month input.cur-year {
    font-family: var(--font-sans);
    color: var(--text);
}
.flatpickr-current-month {
    font-weight: 600;
    font-size: .95rem;
}
.flatpickr-weekday {
    font-family: var(--font-sans);
    font-weight: 500;
    color: var(--text-light);
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .03em;
}
.flatpickr-day {
    border-radius: var(--radius);
    color: var(--text);
    font-weight: 400;
}
.flatpickr-day:hover,
.flatpickr-day:focus {
    background: var(--bg-muted);
    border-color: transparent;
    color: var(--text);
}
.flatpickr-day.today {
    border-color: var(--border-strong);
}
.flatpickr-day.today:hover {
    background: var(--bg-muted);
    color: var(--text);
}
.flatpickr-day.selected,
.flatpickr-day.selected:hover,
.flatpickr-day.selected:focus,
.flatpickr-day.startRange,
.flatpickr-day.endRange {
    background: var(--brand);
    border-color: var(--brand);
    color: var(--text-inverse);
}
.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay {
    color: var(--text-light);
}
.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover {
    color: var(--border-strong);
    background: transparent;
}
.flatpickr-prev-month,
.flatpickr-next-month {
    color: var(--text-med);
    fill: var(--text-med);
}
.flatpickr-prev-month:hover,
.flatpickr-next-month:hover {
    color: var(--text);
    fill: var(--text);
}
.flatpickr-prev-month:hover svg,
.flatpickr-next-month:hover svg {
    fill: var(--text);
}
.empty-state-title {
    font-size: 1.1rem;
    font-weight: 500;
    color: var(--text);
    margin: 0 0 var(--sp-2);
}
.empty-state-desc {
    font-size: .95rem;
    margin: 0;
}

/* ── Invoice status chips ─────────────────────────────────────────────────── */
.status-chip-draft       { background: var(--bg-muted);    color: var(--text-light); }
.status-chip-sent        { background: var(--info-bg);     color: var(--info);       }
.status-chip-viewed      { background: #ede9fe;            color: #5b21b6;           }
.status-chip-partially_signed { background: #fef3c7;       color: #92400e;           }
.status-chip-paid        { background: var(--success-bg);  color: var(--success);    }
.status-chip-overdue     { background: #fee2e2;            color: #991b1b;           }
.status-chip-void        { background: var(--bg-muted);    color: var(--text-light); text-decoration: line-through; }
.status-chip-refunded    { background: var(--warn-bg);     color: var(--warn);       }
/* Estimate statuses (draft/sent/viewed reuse the invoice chips above). */
.status-chip-accepted    { background: var(--success-bg);  color: var(--success);    }
.status-chip-declined    { background: #fee2e2;            color: #991b1b;           }
.status-chip-revision_requested { background: var(--warn-bg); color: var(--warn);     }
.status-chip-expired     { background: var(--bg-muted);    color: var(--text-light); }
.status-chip-converted   { background: #e0f2fe;            color: #075985;           }
.status-chip-closed      { background: var(--bg-muted);    color: var(--text-light); }
/* Closed (archived) estimates stay in the list, muted and sunk to the bottom. */
.data-table tr.is-closed td { opacity: .55; }
.data-table tr.is-closed:hover td { opacity: 1; }
/* Public estimate review page (no-login, token link). */
.est-public { max-width: 720px; margin: 0 auto; padding: clamp(24px, 5vw, 48px) 20px 80px; }
.est-public-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 24px; flex-wrap: wrap; }
.est-public-brand { font-weight: 700; font-size: 1.15rem; letter-spacing: -0.01em; }
/* Small studio logo mark at the top of the estimate card (above the number). */
.est-card-logo { display: block; height: 50px; width: auto; max-width: 150px; object-fit: contain; margin: 0 0 14px; }
/* Estimate meta dates as a horizontal row (Date issued + Valid until side by side). */
.est-dates { display: flex; flex-wrap: wrap; gap: 14px 44px; margin-top: 6px; }
.est-date { display: flex; flex-direction: column; gap: 2px; }
.est-date-label { font-size: .7rem; letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted); }
.est-date-val { font-size: .9rem; }
/* Clean two-column From / Prepared-for block (each side optional). */
.est-parties { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px 32px; margin-top: 18px; }
.est-party-label { font-size: .7rem; letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted); margin-bottom: 4px; }
.est-party-name { font-weight: 600; font-size: .95rem; }
.est-party-line { font-size: .85rem; color: var(--text-med); line-height: 1.5; }
/* Studio read-only preview bar atop the client-facing estimate. */
.est-preview-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 20px; padding: 10px 14px; background: var(--bg-soft); border: 1px solid var(--border); border-radius: 10px; }
.est-preview-tag { font-size: .85rem; color: var(--text-med); display: inline-flex; align-items: center; gap: 6px; }
.est-public-actions { display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-start; margin-top: 24px; }
.est-public-actions summary { list-style: none; cursor: pointer; }
.est-public-actions summary::-webkit-details-marker { display: none; }
/* Collapsed buttons hug together (Accept + Request); only give the details room
   once it's opened so the textarea is usable. */
.est-public-actions details[open] { min-width: 320px; }
/* Decline sits apart at the right, the deliberate "destructive" position. */
.est-public-actions > :last-child { margin-left: auto; }

/* ── Estimate conversation thread ─────────────────────────────────────────── */
/* Shared by the studio detail, the client portal, and the public token page.
   Studio and client messages align to opposite sides, like a chat. */
.est-thread { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.est-msg {
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 12px 14px;
    max-width: 88%;
}
.est-msg-studio { background: var(--surface-2, #f6f6f4); align-self: flex-start; border-bottom-left-radius: 4px; }
.est-msg-client { background: var(--card, #fff);         align-self: flex-end;   border-bottom-right-radius: 4px; }
.est-msg-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; margin-bottom: 4px; }
.est-msg-who  { font-weight: 600; font-size: 0.9rem; }
.est-msg-role {
    font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.04em;
    font-weight: 600; padding: 1px 6px; border-radius: 999px;
    background: var(--warn-bg); color: var(--warn);
}
.est-msg-client .est-msg-role { background: var(--success-bg); color: var(--success); }
.est-msg-time { margin-left: auto; font-size: 0.75rem; color: var(--text-muted, #6b6b6b); white-space: nowrap; }
.est-msg-body { font-size: 0.92rem; line-height: 1.5; }
.est-reply { border-top: 1px solid var(--border); padding-top: 16px; }
.est-reply-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
#conv-cancel { color: var(--text-muted, #6b6b6b); }

/* "Sent {date}" resting state on the studio detail: the estimate is out and
   there's nothing new to send, so the prominent Send button steps back to a
   status line plus a quiet Resend. */
.est-sent-state { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.est-sent-label {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: 0.85rem; color: var(--success, #15803d); font-weight: 600; white-space: nowrap;
}
.est-sent-label svg { width: 15px; height: 15px; }
/* Bucket-only (filter chips on the list page; not a real invoice status). */
.status-chip-outstanding { background: #fef3c7;            color: #92400e;           }

/* ── Agreement status chips ───────────────────────────────────────────────── */
/* draft, sent, viewed, void reuse the invoice palette above — same intent. */
.status-chip-signed        { background: var(--success-bg); color: var(--success); }
.status-chip-countersigned { background: #d1fae5;           color: #065f46;        }

/* ── Invoice line items (form) ────────────────────────────────────────────── */
.invoice-line-items {
    border: 1px solid var(--border);
    border-radius: 10px;
    overflow: hidden;
    background: var(--bg-soft);
}
.invoice-line-head,
.invoice-line-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 90px 130px 110px 38px;
    gap: var(--sp-3);
    align-items: center;
    padding: var(--sp-3) var(--sp-4);
    border-bottom: 1px solid var(--border-soft);
}
.invoice-line-head {
    background: #fff;
    padding-top: var(--sp-2);
    padding-bottom: var(--sp-2);
    font-weight: 500;
}
.invoice-line-row:last-of-type { border-bottom: 1px solid var(--border-soft); }
.invoice-line-row .form-control { margin: 0; }
.invoice-line-amount {
    text-align: right;
    font-size: .95rem;
    color: var(--text);
}
.invoice-line-foot {
    padding: var(--sp-3) var(--sp-4);
    background: #fff;
}

/* Saved-item typeahead: floating match list under a line's Description field.
   Appended to <body> and positioned in JS, so it escapes the line-row grid. */
.invoice-typeahead {
    position: absolute;
    z-index: 1200;
    max-height: 280px;
    overflow-y: auto;
    background: #fff;
    border: 1px solid var(--border-soft, #e5e5e5);
    border-radius: 8px;
    box-shadow: 0 10px 28px rgba(0, 0, 0, .14);
    padding: 4px;
}
.invoice-typeahead-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--sp-3, 12px);
    padding: 8px 10px;
    border-radius: 6px;
    cursor: pointer;
    font-size: .9rem;
}
.invoice-typeahead-item:hover,
.invoice-typeahead-item.is-active { background: var(--surface-2, #f4f4f5); }
.invoice-typeahead-label { color: var(--text, #0a0a0a); }
.invoice-typeahead-price {
    color: var(--text-muted, #777);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Read-only marker on invoice views/PDF: this line was excluded from the % discount. */
.line-no-disc-tag {
    display: inline-block;
    margin-left: 8px;
    font-size: .68rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--text-light);
    background: var(--bg-soft);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 1px 6px;
    vertical-align: middle;
    white-space: nowrap;
}

/* Per-line "exclude from discount" toggle — only shown in % discount mode.
   Green = included in the discount, red = excluded (pass-through fees). */
.line-exempt-toggle, .line-exempt-head { display: none; }
.line-exempt-head { text-align: center; }
.invoice-line-items.discount-percent .line-exempt-head { display: block; }
.invoice-line-items.discount-percent .line-exempt-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    margin: 0 auto;
    border-radius: 8px;
    cursor: pointer;
    border: 1px solid var(--success);
    background: var(--success-bg);
    color: var(--success);
    font-size: .8rem;
    transition: background .15s, color .15s, border-color .15s;
}
.invoice-line-items.discount-percent .line-exempt-toggle:hover { filter: brightness(.97); }
.invoice-line-items.discount-percent .line-exempt-toggle.is-exempt {
    border-color: var(--danger);
    background: var(--danger-bg);
    color: var(--danger);
}
/* Desktop: percent mode adds a 6th column for the toggle (before remove). */
@media (min-width: 701px) {
    .invoice-line-items.discount-percent .invoice-line-head,
    .invoice-line-items.discount-percent .invoice-line-row {
        grid-template-columns: minmax(0, 1fr) 90px 130px 110px 44px 38px;
    }
}

/* Mobile: collapse the grid into a single-column stack per row */
@media (max-width: 700px) {
    .invoice-line-head { display: none; }
    .invoice-line-row {
        grid-template-columns: 1fr 38px;
        grid-template-areas:
            "desc    remove"
            "qty     remove"
            "unit    remove"
            "amount  remove";
        align-items: center;
    }
    .invoice-line-row > input:nth-child(1)         { grid-area: desc;   }
    .invoice-line-row > input[data-line-qty]       { grid-area: qty;    }
    .invoice-line-row > input[data-line-unit]      { grid-area: unit;   }
    .invoice-line-row > [data-line-amount]         { grid-area: amount; }
    .invoice-line-row > [data-line-remove]         { grid-area: remove; align-self: start; }

    /* Percent mode: give the exempt toggle its own slot top-right, × below it. */
    .invoice-line-items.discount-percent .invoice-line-row {
        grid-template-areas:
            "desc    exempt"
            "qty     remove"
            "unit    remove"
            "amount  remove";
    }
    .invoice-line-items.discount-percent .invoice-line-row > [data-line-exempt] {
        grid-area: exempt;
        justify-self: end;
    }
}

.btn-icon {
    padding: 6px 8px;
    line-height: 1;
}

/* ── Invoice summary card (totals: subtotal / tax / discount / total) ─────── */
.invoice-summary {
    margin-top: var(--sp-5);
    width: 100%;
    padding: var(--sp-5);
    border: 1px solid var(--border);
    border-radius: 12px;
    background: #fff;
}
.invoice-summary-title {
    margin: 0 0 var(--sp-3);
    font-size: 1rem;
    font-weight: 600;
    color: var(--text);
    text-transform: uppercase;
    letter-spacing: .04em;
    padding-bottom: var(--sp-3);
    border-bottom: 1px solid var(--border-soft);
}
.invoice-summary-row {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: var(--sp-4);
    padding: var(--sp-3) 0;
}
.invoice-summary-row + .invoice-summary-row {
    border-top: 1px solid var(--border-soft);
}
.invoice-summary-label {
    font-size: .95rem;
    font-weight: 500;
    color: var(--text);
    margin: 0;
}
.invoice-summary-value {
    font-size: 1rem;
    font-weight: 500;
    color: var(--text);
}
.invoice-summary-row-grand {
    border-top: 2px solid var(--text) !important;
    margin-top: var(--sp-2);
    padding-top: var(--sp-4);
    padding-bottom: var(--sp-2);
}
.invoice-summary-row-grand .invoice-summary-label {
    font-size: 1.05rem;
    font-weight: 600;
}
.invoice-summary-row-grand .invoice-summary-value {
    font-size: 1.25rem;
    font-weight: 700;
}

/* Input cluster: symbol + (optional toggle) + number input */
.invoice-summary-input-wrap {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    justify-self: end;
}
/* Computed $ value of a % discount, shown next to the rate input. */
.invoice-summary-discount-amount {
    min-width: 84px;
    text-align: right;
    color: var(--text-light);
    font-size: .95rem;
}
.invoice-summary-discount-amount:empty { min-width: 0; }
/* Optional discount-label input occupying the left cell of the discount row. */
.invoice-summary-row-discount { align-items: center; }
.invoice-discount-label { max-width: 340px; }
.invoice-summary-symbol {
    font-size: .95rem;
    color: var(--text-light);
    font-weight: 500;
    min-width: 14px;
    text-align: right;
}
.invoice-summary-input {
    margin: 0;
    width: 110px;
    text-align: right;
}

/* $/% segmented toggle */
.discount-type-toggle {
    display: inline-flex;
    border: 1px solid var(--border-strong);
    border-radius: 8px;
    overflow: hidden;
    background: var(--bg-muted);
}
.discount-type-btn {
    appearance: none;
    background: transparent;
    border: 0;
    padding: 4px 10px;
    font-size: .9rem;
    font-weight: 600;
    color: var(--text-light);
    cursor: pointer;
    line-height: 1.4;
    transition: background .15s, color .15s;
}
.discount-type-btn + .discount-type-btn {
    border-left: 1px solid var(--border-strong);
}
.discount-type-btn:hover { color: var(--text); }
.discount-type-btn.is-active {
    background: var(--text);
    color: #fff;
}

/* Read-only variant used on the view page */
.invoice-summary-readonly {
    background: var(--bg-soft);
}

/* ── Invoice number display (read-only, replaces the old Title input) ─────── */
.invoice-number-display {
    display: flex;
    align-items: center;
    min-height: 42px;
    padding: 0 var(--sp-4);
    border: 1px dashed var(--border-strong);
    border-radius: 8px;
    background: var(--bg-soft);
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: .03em;
    color: var(--text);
}

/* ─────────────────────────────────────────────────────────────────────────
   Client portal layout
   Mirrors the studio admin layout — uses the same .portal-* classes. The
   client-specific styles below are limited to dashboard cards, page
   headers, and the coming-soon interstitial.
   ───────────────────────────────────────────────────────────────────────── */
.client-page-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sp-5);
    margin-bottom: var(--sp-6);
    flex-wrap: wrap;
}
.client-page-title {
    font-size: 1.6rem;
    font-weight: 700;
    margin: 0 0 var(--sp-2);
    color: var(--text);
    letter-spacing: -0.01em;
}
.client-page-sub {
    margin: 0;
    color: var(--text-light);
    font-size: .95rem;
}

/* Outstanding-balance callout above the dashboard */
.client-callout {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    padding: var(--sp-5);
    border-radius: 12px;
    background: #fff;
    border: 1px solid var(--border);
    margin-bottom: var(--sp-6);
    flex-wrap: wrap;
}
.client-callout-warn {
    border-color: #fed7aa;
    background: #fffbeb;
}
.client-callout-figure {
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
    font-size: 1.75rem;
    font-weight: 700;
    margin: 0;
    color: var(--text);
}

/* Pay-by-check note inside a .client-callout (icon + stacked text, left-aligned) */
.invoice-check-note {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
}
.invoice-check-note > i {
    color: var(--brand-accent-strong, #c2410c);
    font-size: 1.25rem;
    line-height: 1.4;
    flex-shrink: 0;
}

/* Two-column grid (projects | invoices) on the dashboard */
.client-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--sp-5);
}
@media (min-width: 900px) {
    .client-grid { grid-template-columns: 1fr 1fr; }
}
.client-card {
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: var(--sp-5);
}
/* A rich project table inside a padded client card — bleed it to the card's
   side edges so its rows read edge-to-edge (matching the studio dashboard),
   with the table's own cell padding providing the inset. */
.client-card-table {
    margin: var(--sp-3) calc(var(--sp-5) * -1) 0;
}
.client-card-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-3);
    margin-bottom: var(--sp-4);
    padding-bottom: var(--sp-3);
    border-bottom: 1px solid var(--border-soft);
}
.client-card-title {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    color: var(--text);
}

/* Compact list inside dashboard cards */
.client-list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.client-list li + li {
    border-top: 1px solid var(--border-soft);
}
.client-list-item {
    display: block;
    padding: var(--sp-3) 0;
    color: inherit;
    text-decoration: none;
}
.client-list-item:hover { background: var(--bg-soft); }
.client-list-main {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    margin-bottom: 2px;
}

/* ─────────────────────────────────────────────────────────────────────────
   Project detail two-column layout
   Main content on the left, sticky-ish right rail with client info +
   comments + internal notes. Stacks to one column on tablet/mobile.
   ───────────────────────────────────────────────────────────────────────── */
.project-layout {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--sp-5);
}
@media (min-width: 1000px) {
    .project-layout { grid-template-columns: 1fr 360px; align-items: start; }
}
.project-main {
    display: flex;
    flex-direction: column;
    gap: var(--sp-5);
    min-width: 0;
}
.project-aside {
    display: flex;
    flex-direction: column;
    gap: var(--sp-5);
    min-width: 0;
}

/* ─────────────────────────────────────────────────────────────────────────
   Card section — native <details>/<summary> accordion. JS animates the
   body's height (inline style) to/from scrollHeight on toggle. Native
   browser handles state (the [open] attribute), JS only does the
   animation. Bulletproof: works with or without JS (snaps without).
   ───────────────────────────────────────────────────────────────────────── */
.card-section {
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 12px;
    overflow: hidden;
    /* Avoid the default details margin-bottom in some browsers */
}
.card-section-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    padding: var(--sp-4) var(--sp-5);
    color: var(--text);
    cursor: pointer;
    /* Remove the native disclosure triangle */
    list-style: none;
    user-select: none;
}
.card-section-head::-webkit-details-marker { display: none; }
.card-section-head::marker { display: none; }
.card-section[open] .card-section-head { border-bottom: 1px solid var(--border-soft); }
.card-section-head:hover { background: var(--bg-soft); }
.card-section-title {
    margin: 0;
    font-size: .98rem;
    font-weight: 600;
    color: var(--text);
    display: inline-flex;
    align-items: center;
    gap: 10px;
}
.card-section-title i { color: var(--text); font-size: .9rem; }
.card-section-chevron {
    color: var(--text-light);
    font-size: .8rem;
    transition: transform .22s ease;
}
.card-section[open] .card-section-chevron { transform: rotate(0deg); }
.card-section:not([open]) .card-section-chevron { transform: rotate(180deg); }

/* Per-card inline edit affordances (project page). */
.card-section-head-actions {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3);
    flex: 0 0 auto;
}
/* Quiet, borderless edit affordance — no more bordered "cheap button" look.
   It appears only once a card is open (collapsed cards stay clean). */
.card-edit-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: none;
    background: transparent;
    color: var(--text-light);
    font-size: .8rem;
    font-weight: 500;
    line-height: 1;
    padding: 5px 9px;
    border-radius: 7px;
    cursor: pointer;
    transition: color .15s ease, background .15s ease;
}
.card-edit-btn:hover {
    color: var(--text);
    background: var(--bg-muted);
}
.card-edit-btn i { font-size: .75rem; color: var(--text-light); transition: color .15s ease; }
.card-edit-btn:hover i { color: var(--text); }
/* Gate to open cards only — the affordance lives "inside" the card. */
.card-section:not([open]) .card-edit-btn { display: none; }
.card-edit-form { animation: cardEditFadeIn .18s ease; }
@keyframes cardEditFadeIn { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: none; } }

/* ── Settings modal (native <dialog>) ─────────────────────────────────────────
   Centered card on a dimmed backdrop, title + close, scrolling body, footer
   with the single ink primary on the right. Reusable beyond gallery settings. */
.settings-modal {
    width: min(560px, calc(100vw - 2rem));
    /* dvh so iOS excludes the browser chrome / home bar; vh first as fallback. */
    max-height: 85vh;
    max-height: 85dvh;
    padding: 0;
    border: none;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    background: #fff;
    color: var(--text);
    overflow: hidden;
}
.settings-modal[open] { animation: settingsModalIn .16s ease; }
@keyframes settingsModalIn {
    from { opacity: 0; transform: translateY(8px) scale(.99); }
    to   { opacity: 1; transform: none; }
}
.settings-modal::backdrop {
    background: rgba(10, 10, 10, .45);
    animation: settingsBackdropIn .16s ease;
}
@keyframes settingsBackdropIn { from { opacity: 0; } to { opacity: 1; } }
.settings-modal-form {
    display: flex;
    flex-direction: column;
    max-height: 85vh;
    max-height: 85dvh;
}
.settings-modal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    padding: var(--sp-5) var(--sp-6);
    border-bottom: 1px solid var(--border);
    flex: 0 0 auto;
}
.settings-modal-title { margin: 0; font-size: 1.05rem; font-weight: 700; color: var(--text); }
.settings-modal-close {
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
    border: none;
    background: transparent;
    color: var(--text-light);
    border-radius: var(--radius);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1.05rem;
    transition: background .15s, color .15s;
}
.settings-modal-close:hover { background: var(--bg-muted); color: var(--text); }
.settings-modal-body {
    padding: var(--sp-6);
    overflow-y: auto;
    flex: 1 1 auto;
    min-height: 0;            /* let the body scroll instead of overflowing the modal */
}
.settings-modal-foot {
    display: flex;
    justify-content: flex-end;
    gap: var(--sp-3);
    padding: var(--sp-4) var(--sp-6);
    /* Clear the iOS home indicator so the buttons stay tappable. */
    padding-bottom: max(var(--sp-4), env(safe-area-inset-bottom));
    border-top: 1px solid var(--border);
    background: var(--bg-soft);
    flex: 0 0 auto;
}
@media (prefers-reduced-motion: reduce) {
    .settings-modal[open], .settings-modal::backdrop { animation: none; }
}

/* Client summary card on the STUDIO project page. Namespaced pclient-* so it
   never collides with the CLIENT portal's own .client-card* header styles. */
.pclient-head {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    text-decoration: none;
    padding-bottom: var(--sp-4);
    border-bottom: 1px solid var(--border-soft);
}
.pclient-id { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pclient-name {
    font-weight: 700;
    font-size: 1.05rem;
    color: var(--brand);
    line-height: 1.2;
}
.pclient-head:hover .pclient-name { text-decoration: underline; }
.pclient-company { font-size: .9rem; color: var(--text-light); }
.pclient-contact {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    padding-top: var(--sp-4);
}
.pclient-line {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    font-size: .95rem;
    color: var(--text);
    text-decoration: none;
    line-height: 1.45;
}
a.pclient-line { color: var(--brand); }
a.pclient-line:hover span { text-decoration: underline; }
.pclient-line i {
    color: var(--text-light);
    font-size: .85rem;
    width: 16px;
    text-align: center;
    margin-top: 3px;
    flex-shrink: 0;
}
.pclient-line-static { color: var(--text); }

/* ── Client detail page (studio): stat strip + billing rollup + actions ── */
/* Client hero: four labeled groups (Client / Contact / Account / Billing), each
   a column of stacked fields, so related info reads together with clear hierarchy. */
.client-hero {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--sp-5) var(--sp-6);
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: var(--sp-5) var(--sp-6);
    margin-bottom: var(--sp-5);
}
.client-hero-group { display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0; }
.client-hero-grouptitle {
    font-size: .82rem; font-weight: 700; color: var(--text); margin: 0;
    padding-bottom: var(--sp-2); border-bottom: 1px solid var(--border);
}
.client-hero-field { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.client-hero-label { font-size: .66rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: var(--text-light); }
.client-hero-value { font-size: .92rem; line-height: 1.4; color: var(--text); overflow-wrap: anywhere; }
@media (max-width: 1000px) { .client-hero { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px)  { .client-hero { grid-template-columns: 1fr; } }

.billing-quickref {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--sp-5);
    flex-wrap: wrap;
}
.billing-quickref-value {
    font-size: 2rem;
    font-weight: 800;
    color: var(--text);
    line-height: 1.05;
}
.billing-quickref-control {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 190px;
}

/* Invoices tab quick-reference stats — Outstanding + Paid (with timeframe). */
.invoice-stats {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--sp-4);
    margin-bottom: var(--sp-5);
}
@media (min-width: 700px) { .invoice-stats { grid-template-columns: 1fr 1fr; } }
.invoice-stat {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: var(--sp-4) var(--sp-5);
}
.invoice-stat-icon {
    flex: 0 0 auto;
    width: 44px; height: 44px;
    border-radius: 12px;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 1.05rem;
}
.invoice-stat-icon-out  { background: var(--danger-bg);  color: var(--danger); }
.invoice-stat-icon-paid { background: var(--success-bg); color: var(--success); }
.invoice-stat-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.invoice-stat-value { font-size: 2.1rem; font-weight: 800; color: var(--text); line-height: 1.05; letter-spacing: -.01em; }
.invoice-stat-label { font-size: .8rem; color: var(--text-light); display: inline-flex; align-items: center; gap: 6px; }
.invoice-stat-select {
    border: 0;
    background: transparent;
    color: var(--text-light);
    font: inherit;
    font-size: .8rem;
    cursor: pointer;
    padding: 0 2px;
    -webkit-appearance: auto;
    appearance: auto;
}

.client-actions { display: flex; flex-direction: column; gap: var(--sp-3); }
.client-actions form { margin: 0; }
.btn-block { display: flex; width: 100%; justify-content: center; }

/* Danger zone card — red-tinted so a destructive delete reads as serious. */
.client-danger { border-color: var(--danger-border); background: var(--danger-bg); }
.client-danger .card-section-title i { color: var(--danger); }
.client-danger .card-section[open] .card-section-head,
.client-danger .card-section-head { border-color: var(--danger-border); }
.client-danger form { margin: 0; }

/* Body — JS sets inline height during the animation. Padding intentionally
   lives on the INNER wrapper, NOT here. If padding were on the body itself,
   it would fight for space as the body shrinks toward 0 (padding has nowhere
   to "go") which produces the jitter at the end of a close animation.
   With padding on inner, the body cleanly shrinks via overflow:hidden and
   the inner gets clipped naturally without any padding gymnastics. */
.card-section-body {
    overflow: hidden;
    transition: height .22s cubic-bezier(.4, 0, .2, 1);
}
.card-section-body-inner {
    padding: var(--sp-5);
}
.card-section-body-inner.p-0 { padding: 0; }

/* Tinted variant for "team-only" sections — distinguishes internal
   stuff from client-facing visually. */
.card-section-internal {
    background: #fefce8;
    border-color: #fde68a;
}
.card-section-internal .card-section-head:hover { background: #fef3c7; }
.card-section-internal .card-section-title i { color: var(--warn); }

/* Internal-note form field (project create) — amber to signal "team only",
   matching the yellow Internal Notes card it feeds into. */
.form-field-internal .form-label i { color: var(--warn); margin-right: 2px; }
.form-field-internal .form-control {
    background: #fefce8;
    border-color: #fde68a;
}
.form-field-internal .form-control:focus {
    border-color: var(--warn);
    outline-color: var(--warn);
}
.form-field-internal .form-control::placeholder { color: #b59a4d; }

/* Module slot grid (placeholder cards inside Modules section) */
.module-slot-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: var(--sp-3);
}
.module-slot {
    padding: var(--sp-4);
    background: var(--bg-soft);
    border: 1px dashed var(--border-strong);
    border-radius: 10px;
}
.module-slot-title {
    margin: 0 0 var(--sp-2);
    font-size: .95rem;
    font-weight: 600;
    color: var(--text);
}

/* ─────────────────────────────────────────────────────────────────────────
   Messages thread (project comments + reactions)
   Used on both /portal/projects/<id> and /client/projects/<id>.
   ───────────────────────────────────────────────────────────────────────── */
.messages-section { display: flex; flex-direction: column; gap: var(--sp-4); }
/* Used to be a section header above each thread; now the card-section
   header handles the title. Keep the rule in case it's referenced elsewhere. */
.messages-section-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-3);
    flex-wrap: wrap;
}
/* In the right-rail context, messages are tighter — even smaller avatars,
   compact compose footer. */
.project-aside .messages-list { gap: var(--sp-4); }
.project-aside .message-avatar { --avatar-size: 24px; }
.project-aside .messages-compose-foot {
    flex-direction: column;
    align-items: stretch;
    gap: var(--sp-2);
}
.project-aside .messages-compose-foot button { align-self: flex-end; }

.messages-empty {
    text-align: center;
    padding: var(--sp-6) var(--sp-4);
    color: var(--text-light);
    border: 1px dashed var(--border);
    border-radius: 10px;
}
.messages-empty i {
    display: block;
    font-size: 1.5rem;
    margin-bottom: var(--sp-2);
    opacity: .6;
}

/* Text-message style: each row aligns to the sender's side. The viewer's
   own messages push to the right edge; everyone else's stay left. Avatar
   tucks right next to the bubble on the appropriate side.

   The list itself is capped so the thread never grows the card past a
   sane height — overflow scrolls within the card. Subtle scrollbar
   styling keeps it from feeling like a textarea. */
.messages-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;   /* default; .message-own overrides via align-self */
    gap: var(--sp-5);
    max-height: 520px;
    overflow-y: auto;
    /* Small inset padding so right-aligned bubbles don't kiss the
       scrollbar / card edge. Avatars get a little air too. */
    padding-right: var(--sp-2);
    /* Firefox */
    scrollbar-width: thin;
    scrollbar-color: var(--border-strong) transparent;
}
.messages-list::-webkit-scrollbar { width: 8px; }
.messages-list::-webkit-scrollbar-track { background: transparent; }
.messages-list::-webkit-scrollbar-thumb {
    background: var(--border-strong);
    border-radius: 8px;
}
.messages-list::-webkit-scrollbar-thumb:hover { background: var(--text-light); }

.message {
    display: flex;
    align-items: flex-end;     /* avatar sits flush with bottom of bubble */
    gap: var(--sp-2);
    max-width: 88%;            /* keep bubbles from spanning full width */
    min-width: 0;
}
.message-avatar { flex-shrink: 0; --avatar-size: 28px; }

.message-body-wrap {
    min-width: 0;
    background: var(--bg-soft);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: var(--sp-3) var(--sp-4);
}

/* Viewer's own messages — flip side + subtle accent. Premium-SaaS feel:
   barely-there background tint, but a clearly-blue border carries the
   "this is you" cue. Anything louder feels like a chat toy, not a tool. */
.message-own {
    align-self: flex-end;
    flex-direction: row-reverse;
}
.message-own .message-body-wrap {
    background: #f5f9ff;       /* near-white, faintest blue wash */
    border-color: #60a5fa;     /* clearly blue but not loud */
}
.message-own .message-meta { justify-content: flex-end; }
.message-own .message-reactions { justify-content: flex-end; }

/* Internal notes always look like internal notes regardless of side. */
.message-internal .message-body-wrap {
    background: #fffbeb;       /* faint warm wash */
    border-color: #fde68a;
}
.message-own.message-internal .message-body-wrap {
    /* Internal-and-mine: keep the warm wash, bump the border so "mine"
       still reads from across the thread. */
    background: #fffbeb;
    border-color: #f59e0b;
}

.message-meta {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    flex-wrap: wrap;
    margin-bottom: var(--sp-2);
    font-size: .85rem;
}
.message-author { font-weight: 600; color: var(--text); }
.message-time { color: var(--text-light); font-family: var(--font-mono); font-size: .78rem; }
.message-internal-badge {
    font-size: .7rem;
    font-weight: 600;
    color: var(--warn);
    background: var(--warn-bg);
    padding: 2px 8px;
    border-radius: 4px;
    text-transform: uppercase;
    letter-spacing: .06em;
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

.message-text {
    color: var(--text);
    line-height: 1.55;
    white-space: pre-wrap;
    word-wrap: break-word;
}

/* Reactions */
.message-reactions {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: var(--sp-3);
    flex-wrap: wrap;
    position: relative;
}
.reaction-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    background: var(--bg-muted);
    border: 1px solid var(--border);
    border-radius: 14px;
    cursor: pointer;
    font-family: inherit;
    font-size: .8rem;
    color: var(--text);
    transition: background .12s, border-color .12s;
}
.reaction-chip:hover {
    background: #fff;
    border-color: var(--border-strong);
}
.reaction-chip.is-mine {
    background: var(--info-bg);
    border-color: var(--info);
    color: var(--info);
}
.reaction-chip.is-mine:hover { background: #dbeafe; }
.reaction-chip:disabled { opacity: .6; cursor: wait; }
.reaction-emoji { font-size: .95rem; line-height: 1; }
.reaction-count { font-weight: 600; }

.reaction-add {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: transparent;
    border: 1px dashed var(--border-strong);
    color: var(--text-light);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background .12s, color .12s;
}
.message-delete-btn { color: var(--text-light); margin-left: auto; }
.message-delete-btn:hover { color: var(--danger); }
.reaction-add:hover {
    background: var(--bg-muted);
    color: var(--text);
}
.reaction-picker {
    position: absolute;
    top: 100%;
    left: 0;
    margin-top: 4px;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, .12);
    padding: 4px;
    display: flex;
    gap: 2px;
    z-index: 10;
}
/* Position is computed by JS as position:fixed relative to the viewport
   (see messages.js positionPickerNear), so no per-side CSS override is
   needed. The CSS values above act as a fallback if JS ever fails. */
.reaction-picker[hidden] { display: none; }
.reaction-picker-emoji {
    width: 32px;
    height: 32px;
    border-radius: 6px;
    background: transparent;
    border: 0;
    cursor: pointer;
    font-size: 1.15rem;
    line-height: 1;
    transition: background .12s, transform .12s;
}
.reaction-picker-emoji:hover {
    background: var(--bg-soft);
    transform: scale(1.15);
}

/* Compose form */
.messages-compose {
    margin-top: var(--sp-4);
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
}
.messages-compose textarea {
    resize: vertical;
    min-height: 80px;
}
.messages-compose-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    flex-wrap: wrap;
}
.messages-internal-toggle {
    margin: 0;
    flex: 1;
    min-width: 240px;
}

/* "Coming soon" interstitial for unshipped client modules */
.coming-soon-splash {
    text-align: center;
    padding: var(--sp-7) var(--sp-5);
}
.coming-soon-icon {
    width: 72px;
    height: 72px;
    margin: 0 auto var(--sp-4);
    border-radius: 16px;
    background: var(--bg-soft);
    color: var(--text-light);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.8rem;
}
.coming-soon-title {
    margin: 0 0 var(--sp-3);
    font-size: 1.15rem;
    font-weight: 700;
    color: var(--text);
}
.coming-soon-desc {
    max-width: 520px;
    margin: 0 auto var(--sp-4);
    color: var(--text);
    line-height: 1.5;
}

/* Layout polish on invoice view (meta row across the top) */
.invoice-meta-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: var(--sp-4);
    padding-bottom: var(--sp-5);
    border-bottom: 1px solid var(--border-soft);
    margin-bottom: var(--sp-5);
}

/* ─────────────────────────────────────────────────────────────────────────
   Studio dashboard
   Five stat tiles → action items → recent projects + quick actions sidebar
   ───────────────────────────────────────────────────────────────────────── */
.dashboard-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: var(--sp-4);
    margin-bottom: var(--sp-6);
}
/* Cohesive band behind the at-a-glance stat cards — a soft neutral surface
   that groups the five numbers into one distinct zone, set apart from the
   action items and lists below. The white cards pop against it. */
.dashboard-stats-band {
    background: var(--bg-muted);
    border: 1px solid var(--border-soft);
    border-radius: 16px;
    padding: var(--sp-4);
    margin-bottom: var(--sp-6);
}
.dashboard-stats-band .dashboard-stats { margin-bottom: 0; }
.dashboard-stat {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    padding: var(--sp-5);
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 12px;
    text-decoration: none;
    color: inherit;
    cursor: pointer;
    transition: background .15s, border-color .15s, box-shadow .15s, transform .15s;
}
.dashboard-stat:hover {
    background: var(--bg-soft);
    border-color: var(--text-light);
    box-shadow: 0 4px 16px rgba(0, 0, 0, .06);
    transform: translateY(-1px);
}
.dashboard-stat:active {
    transform: translateY(0);
    box-shadow: 0 2px 8px rgba(0, 0, 0, .04);
}
.dashboard-stat-icon {
    flex-shrink: 0;
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-muted);
    border-radius: 10px;
    color: var(--text);
    font-size: 1.15rem;
    transition: background-color 0.18s ease, color 0.18s ease;
}
/* Color is reserved for meaning, not decoration. Operational icons stay calm
   ink; only the money line carries the brand accent — warm when a balance is
   owed, green when the studio is all collected. */
.dashboard-stat-icon-due   { background: var(--brand-accent-tint); color: var(--brand-accent-strong); }
.dashboard-stat-icon-clear { background: var(--success-bg);        color: var(--success); }
/* The "pop" lives in interaction: a stat card warms its icon on hover. */
.dashboard-stat:hover .dashboard-stat-icon {
    background: var(--brand-accent-tint);
    color: var(--brand-accent-strong);
}
.dashboard-stat-body { min-width: 0; }
.dashboard-stat-value {
    font-size: 1.6rem;
    font-weight: 700;
    line-height: 1.1;
    color: var(--text);
    letter-spacing: -0.01em;
}
/* The money tile's value is a long string ($10,000.00, $100,000.00); keep it on
   one line and smaller than the count tiles ("2", which keep 1.6rem) so it never
   crowds the card edge as the figure grows. Base size works everywhere; where
   container queries are supported it also scales to the card width. */
.dashboard-stat-money .dashboard-stat-value {
    font-size: 1.3rem;
    letter-spacing: -0.03em;
    white-space: nowrap;
}
@supports (font-size: 1cqi) {
    .dashboard-stat-money { container-type: inline-size; }
    .dashboard-stat-money .dashboard-stat-value { font-size: clamp(1.05rem, 9.5cqi, 1.5rem); }
}
.dashboard-stat-label {
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-light);
    margin-top: 4px;
    font-weight: 500;
}

/* "You're clear" green banner when there are no action items */
.action-clear {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-3);
    padding: var(--sp-4) var(--sp-5);
    background: var(--success-bg);
    border: 1px solid var(--success-border);
    border-radius: 10px;
    color: var(--success);
    font-weight: 500;
    margin-bottom: var(--sp-6);
}
.action-clear i { font-size: 1.05rem; }

/* Action-items card when there ARE items */
.action-items {
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: var(--sp-5);
    margin-bottom: var(--sp-6);
}
.action-items-list { list-style: none; margin: 0; padding: 0; }
.action-item {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    padding: var(--sp-3) 0;
    border-top: 1px solid var(--border-soft);
}
.action-item:first-child { border-top: 0; padding-top: 0; }
.action-item:last-child { padding-bottom: 0; }
.action-item-icon {
    width: 38px;
    height: 38px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-muted);
    color: var(--text-light);
    border-radius: 8px;
    flex-shrink: 0;
}
.action-item-warn .action-item-icon {
    background: #fef3c7;
    color: #92400e;
}
.action-item-body { flex: 1; min-width: 0; line-height: 1.35; }
.action-item-text { font-weight: 500; color: var(--text); }
.action-item-sub { margin-top: 2px; }
/* CTA label centered within its own box via symmetric padding (instead of a
   min-height tap target, whose leftover space some browsers anchor to the top). */
.action-item > .btn {
    min-height: 0;
    padding-top: .875rem;
    padding-bottom: .875rem;
}
/* A lone action item sits centered in the card. The section heading ("Needs
   attention") adds height at the top, which would otherwise leave the single
   row, and its CTA, low. On >=600px, pin the lone CTA to the card's vertical
   center, right-aligned, reserving room so text never runs under it. Multi-
   item cards keep one CTA per row (no centering). */
@media (min-width: 600px) {
    .action-items { position: relative; }
    .action-item:only-child { padding-right: 9rem; }
    .action-item:only-child > .btn {
        position: absolute;
        top: 50%;
        right: var(--sp-5);
        transform: translateY(-50%);
        margin: 0;
    }
}

/* Recent-projects + Quick-actions two-column layout */
.dashboard-main {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--sp-6);
    align-items: start;
}
@media (min-width: 1000px) {
    .dashboard-main { grid-template-columns: minmax(0, 1fr) 340px; }
}
/* Left/right stacks inside the dashboard grid. */
.dashboard-col,
.dashboard-rail {
    display: flex;
    flex-direction: column;
    gap: var(--sp-6);
    min-width: 0;
}
@media (min-width: 1000px) {
    .dashboard-rail { position: sticky; top: var(--sp-6); }
}
.dashboard-section-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    padding: var(--sp-4) var(--sp-5);
    border-bottom: 1px solid var(--border-soft);
    flex-wrap: wrap;
}
.dashboard-section-title {
    margin: 0 0 var(--sp-3);
    font-size: 1rem;
    font-weight: 600;
    color: var(--text);
}

/* Quick-actions sidebar */
.dashboard-quick-actions {
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: var(--sp-5);
    height: fit-content;
}
.dashboard-quick-actions .dashboard-section-title {
    padding-bottom: var(--sp-3);
    border-bottom: 1px solid var(--border-soft);
    margin-bottom: var(--sp-4);
}
.quick-action {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: 10px var(--sp-3);
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 8px;
    color: var(--text);
    text-decoration: none;
    font-weight: 500;
    font-size: .92rem;
    margin-bottom: var(--sp-2);
    transition: background .15s, border-color .15s, transform .1s;
}
.quick-action:last-child { margin-bottom: 0; }
.quick-action:hover {
    background: var(--bg-soft);
    border-color: var(--border-strong);
}
.quick-action i {
    color: var(--text-light);
    width: 16px;
    text-align: center;
}
.quick-action-primary {
    background: var(--text);
    color: #fff;
    border-color: var(--text);
}
.quick-action-primary i { color: rgba(255, 255, 255, .8); }
.quick-action-primary:hover {
    background: #1a1a1a;
    border-color: #1a1a1a;
    color: #fff;
}

/* ── Dashboard panels (This week, Recent activity) ────────────────────────────
   Shares the white-card look of .action-items / .dashboard-quick-actions.
   Seeds the future unified .card primitive (consolidation step 2). */
.dash-panel {
    background: #fff;
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: var(--sp-5);
}
.dash-panel-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-3);
    padding-bottom: var(--sp-3);
    border-bottom: 1px solid var(--border-soft);
    margin-bottom: var(--sp-4);
}
.dash-panel-title {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    color: var(--text);
}
.dash-panel-link { text-decoration: none; white-space: nowrap; }
.dash-panel-link:hover { color: var(--text); text-decoration: underline; text-underline-offset: 3px; }
.dash-panel-empty { margin: var(--sp-2) 0 0; }

/* This week — chronological shoots + deadlines */
.dash-week-list { list-style: none; margin: 0; padding: 0; }
.dash-week-item {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    border-radius: var(--radius);
    transition: background .12s ease;
}
/* Full-row hover (the tag + action live outside the link, so highlight the
   whole item, not just the link). */
.dash-week-item:hover { background: var(--bg-soft); }
.dash-week-item + .dash-week-item { border-top: 1px solid var(--border-soft); }
.dash-week-link {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    padding: var(--sp-3) 0;
    text-decoration: none;
    color: var(--text);
}
/* Contextual row action (quiet, on-brand) + the "Reminded" guardrail note. */
.dash-week-action {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    border: none;
    background: transparent;
    color: var(--brand-accent-strong);
    font-size: .74rem;
    font-weight: 600;
    padding: 4px 9px;
    border-radius: var(--radius);
    cursor: pointer;
    white-space: nowrap;
    transition: background .12s ease;
}
.dash-week-action:hover { background: var(--brand-accent-tint); }
.dash-week-action i { font-size: .7rem; }
.dash-week-reminded { flex-shrink: 0; white-space: nowrap; padding-right: var(--sp-1); }

/* ── Payment-reminder modal as a swipeable carousel ──────────────────────────
   The dialog becomes a transparent container so the counter + arrows can sit
   BELOW the white card. The card holds a horizontal track of one slide per
   past-due invoice; the track translates (and drag-swipes on touch). */
.reminder-modal {
    background: transparent;
    box-shadow: none;
    border-radius: 0;
    overflow: visible;
    max-height: 92dvh;
}
.reminder-modal[open] { display: flex; flex-direction: column; align-items: center; }
.reminder-card {
    width: 100%;
    max-height: 80dvh;
    background: #fff;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}
.reminder-viewport { padding: 0; overflow-x: hidden; overflow-y: auto; }
.reminder-track { display: flex; transition: transform .3s cubic-bezier(.4, 0, .2, 1); touch-action: pan-y; will-change: transform; }
.reminder-track.is-dragging { transition: none; }
.reminder-slide { flex: 0 0 100%; width: 100%; box-sizing: border-box; padding: var(--sp-6); }
.reminder-slide-summary { margin: 0 0 var(--sp-4); }
.reminder-slide.is-reminded .reminder-slide-form { display: none; }
.reminder-slide-done { display: none; align-items: center; gap: var(--sp-2); color: var(--success, #15803d); font-weight: 600; padding: var(--sp-5) 0; }
.reminder-slide.is-reminded .reminder-slide-done { display: flex; }
.reminder-done-check { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: 999px; background: var(--success-bg, #dcfce7); }
.reminder-err { margin: 0 var(--sp-6) var(--sp-5); }
.reminder-err[hidden] { display: none; }

/* Nav row below the card, on the dim backdrop. */
.reminder-nav { display: flex; align-items: center; justify-content: center; gap: var(--sp-4); margin-top: var(--sp-4); }
.reminder-nav[hidden] { display: none; }
.reminder-nav-count { min-width: 4.5rem; text-align: center; font-weight: 600; color: #fff; }
/* Plain text chevron glyphs (no lucide/svg), sized directly with font-size so
   they're unmistakably large on the dim backdrop. */
.reminder-nav-btn {
    appearance: none; border: 0; background: transparent; cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    padding: 4px 14px; color: #fff;
    font-size: 1.6rem; line-height: 1;
    transition: opacity .12s, transform .12s;
}
.reminder-nav-btn:hover { transform: scale(1.12); }
.reminder-nav-btn:active { transform: scale(.9); }
.reminder-nav-btn:disabled { opacity: .35; cursor: default; }
.dash-week-date {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 52px;
    text-align: center;
    line-height: 1.15;
}
.dash-week-day { font-size: .8rem; font-weight: 700; color: var(--text); }
.dash-week-dnum { font-size: .7rem; color: var(--text-light); }
/* Overdue deadline: the date column goes red so a missed commitment reads at a glance. */
.dash-week-date-overdue .dash-week-day  { color: var(--danger); }
.dash-week-date-overdue .dash-week-dnum { color: var(--danger); opacity: .8; }
.dash-week-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.dash-week-title {
    font-size: .92rem;
    font-weight: 500;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.dash-week-tag {
    flex-shrink: 0;
    font-size: .68rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    padding: .2rem .5rem;
    border-radius: var(--radius-pill);
    white-space: nowrap;
}
.dash-week-tag-shoot    { background: var(--info-bg); color: var(--info); }
.dash-week-tag-deadline { background: var(--brand-accent-tint); color: var(--brand-accent-strong); }
.dash-week-tag-invoice  { background: var(--danger-bg); color: var(--danger); }
.dash-week-tag-proofing        { background: #fef3c7; color: #92400e; }   /* amber — waiting on the client */
.dash-week-tag-post_processing { background: #ede9fe; color: #6d28d9; }   /* violet — studio retouching */

/* Activity feed: the project link under each entry */
.dash-activity-project { color: var(--text-med); text-decoration: none; font-weight: 500; }
.dash-activity-project:hover { color: var(--text); text-decoration: underline; text-underline-offset: 2px; }

/* Scroll affordance: a fading double-chevron shown only while more rows are
   below the fold (toggled via .has-more by the dashboard script). */
.dash-scroll { position: relative; }
.dash-scroll-hint {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 2.6rem;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    padding-bottom: 2px;
    color: var(--text-light);
    background: linear-gradient(to bottom, rgba(255, 255, 255, 0), #fff 78%);
    pointer-events: none;
    opacity: 0;
    transition: opacity .2s ease;
}
.dash-scroll.has-more .dash-scroll-hint { opacity: 1; }
.dash-scroll.has-more .dash-scroll-hint i { animation: dash-hint-bounce 1.5s ease-in-out infinite; }
@keyframes dash-hint-bounce {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(3px); }
}
@media (prefers-reduced-motion: reduce) {
    .dash-scroll.has-more .dash-scroll-hint i { animation: none; }
}

/* Cap the activity feed so it scrolls internally instead of growing forever. */
.dash-activity-scroll {
    max-height: 24rem;
    overflow-y: auto;
    margin-right: calc(var(--sp-3) * -1);   /* let the scrollbar sit in the padding gutter */
    padding-right: var(--sp-3);
    scrollbar-width: thin;
    scrollbar-color: var(--border-strong) transparent;
}
.dash-activity-scroll::-webkit-scrollbar { width: 8px; }
.dash-activity-scroll::-webkit-scrollbar-thumb {
    background: var(--border-strong);
    border-radius: var(--radius-pill);
    border: 2px solid #fff;
}
.dash-activity-scroll::-webkit-scrollbar-track { background: transparent; }

/* ── Password input with eye-icon reveal toggle ───────────────────────────── */
.password-input-wrap {
    position: relative;
    display: block;
}
.password-input-wrap .form-control {
    padding-right: 44px; /* leave room for the toggle button */
}
.password-toggle-btn {
    position: absolute;
    right: 6px;
    top: 50%;
    transform: translateY(-50%);
    background: transparent;
    border: 0;
    color: var(--text-light);
    cursor: pointer;
    padding: 8px 10px;
    border-radius: 6px;
    line-height: 1;
    font-size: .95rem;
    transition: background .15s, color .15s;
}
.password-toggle-btn:hover {
    color: var(--text);
    background: var(--bg-muted);
}
.password-toggle-btn:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 1px;
}

/* Password strength meter (first-login onboarding). */
.pw-strength { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.pw-strength-track { flex: 1; height: 5px; border-radius: 999px; background: var(--border, #e5e5e5); overflow: hidden; }
.pw-strength-fill { display: block; height: 100%; width: 0; border-radius: 999px; transition: width .2s ease, background-color .2s ease; background: #d4d4d4; }
.pw-strength-fill[data-level="weak"]   { background: #dc2626; }
.pw-strength-fill[data-level="fair"]   { background: #f59e0b; }
.pw-strength-fill[data-level="good"]   { background: #2563eb; }
.pw-strength-fill[data-level="strong"] { background: #16a34a; }
.pw-strength-label { white-space: nowrap; min-width: 3em; }

/* ─────────────────────────────────────────────────────────────────────────
   Admin top bar
   Sits at the top of .portal-main with global search, notification bell,
   and account dropdown. Sticky so it stays visible while scrolling.
   ───────────────────────────────────────────────────────────────────────── */
.admin-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    padding: var(--sp-3) var(--sp-6);
    background: var(--bg-soft);   /* uniform with the sidebar + page (one gray) */
    border-bottom: 1px solid var(--border);
    position: sticky;
    top: 0;
    /* Above the gallery's sticky action bar (z:40) so the account/notification
     * dropdowns — which live inside this bar's stacking context — aren't
     * clipped behind it. (Matches the mobile override below, which is z:49.) */
    z-index: 45;
    height: var(--admin-topbar-h);
    box-sizing: border-box;
}
/* On mobile, the brand portal-topbar is visible above the admin-topbar.
 * Stack them — admin-topbar sticks at top: portal-topbar-h instead of 0.
 * Without this, BOTH bars stick at viewport top:0; z-index covers most of
 * the conflict, but the admin-topbar's bottom edge can still peek through
 * because the two heights aren't identical. Stacking eliminates the
 * overlap entirely. */
@media (max-width: 900px) {
    .admin-topbar {
        top: var(--portal-topbar-h);
        z-index: 49;     /* below portal-topbar (50), above gallery sticky bar (30) */
    }
}
.topbar-page-title {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    color: var(--text);
    letter-spacing: -0.01em;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Admin pages opt into the topbar layout via portal-main-with-topbar.
   The class strips the default padding + max-width from .portal-main so
   the topbar can span edge-to-edge of the main column. Content centering
   moves to .portal-content. Client portal pages keep the original
   .portal-main behavior (no topbar, content centered inside .portal-main). */
.portal-main-with-topbar {
    padding: 0;
    max-width: none;
    display: flex;
    flex-direction: column;
    /* Contain any accidental horizontal overflow (e.g. a wide gallery row) so
       the page can't scroll sideways and drag the sticky topbar/sidebar around
       on mobile. clip (not hidden/auto) doesn't create a scroll container, so
       sticky positioning still works. */
    overflow-x: clip;
}
.portal-main-with-topbar .portal-content {
    padding: var(--sp-6) var(--sp-6) var(--sp-7);
    max-width: 1100px;
    margin: 0 auto;
    width: 100%;
}
@media (min-width: 900px) {
    .portal-main-with-topbar .portal-content {
        padding: var(--sp-7) var(--sp-7) var(--sp-9);
        max-width: 1280px;
    }
}
/* Project detail = a roomy workspace (the gallery grid + manager folds in
   here), so it gets a much wider cap than a standard reading page, but NOT
   edge-to-edge full-bleed (that left the hero + thumbnails too spread out).
   Scoped to pages that render the project tab bar (detail only, not the list). */
body:has(.project-tabs) .portal-main-with-topbar .portal-content { max-width: 1500px; }

/* Full-bleed override for the client gallery page — photos are the
 * product, give them the full viewport. Body class set by gallery-client.js
 * on init, removed on unload. */
body.is-gallery-page .portal-main-with-topbar .portal-content {
    max-width: none;
    padding-left: var(--sp-4);
    padding-right: var(--sp-4);
}
@media (min-width: 900px) {
    body.is-gallery-page .portal-main-with-topbar .portal-content {
        max-width: none;
        padding-left: var(--sp-5);
        padding-right: var(--sp-5);
    }
}

/* Right-aligned cluster: search trigger + bell + account dropdown.
   All three share the same light-gray-pill resting state for visual
   consistency, with a slightly darker hover state. */
.topbar-right {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
}
.topbar-popover { position: relative; }

/* Shared baseline for all three top-bar buttons */
.topbar-search-trigger,
.topbar-icon-btn,
.topbar-account-btn {
    display: inline-flex;
    align-items: center;
    height: 38px;
    background: var(--bg-soft);
    border: 1px solid var(--border);
    color: var(--text);
    cursor: pointer;
    font-family: inherit;
    transition: background .12s, border-color .12s, color .12s;
}
.topbar-search-trigger:hover,
.topbar-icon-btn:hover,
.topbar-account-btn:hover {
    background: var(--bg-muted);
    border-color: var(--border-strong);
}
.topbar-popover.is-open .topbar-icon-btn,
.topbar-popover.is-open .topbar-account-btn {
    background: var(--bg-muted);
    border-color: var(--border-strong);
}

/* Search trigger */
.topbar-search-trigger {
    gap: var(--sp-3);
    padding: 0 14px;
    border-radius: 10px;
    color: var(--text-light);
    font-size: .9rem;
    min-width: 220px;
}
.topbar-search-trigger:hover { color: var(--text); }
.topbar-search-trigger i { font-size: .85rem; }
.topbar-search-trigger span { flex: 1; text-align: left; }
.topbar-kbd {
    font-family: var(--font-mono);
    font-size: .72rem;
    padding: 2px 6px;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 4px;
    color: var(--text-light);
    flex-shrink: 0;
}
@media (max-width: 700px) {
    .topbar-search-trigger {
        min-width: 0;
        padding: 0 12px;
        width: 38px;
        justify-content: center;
    }
    .topbar-search-trigger span,
    .topbar-search-trigger .topbar-kbd { display: none; }
}

/* Bell button — same rounded-rect shape as the search trigger for
   visual cohesion across the topbar cluster. */
.topbar-icon-btn {
    width: 38px;
    justify-content: center;
    border-radius: 10px;
    position: relative;
    color: var(--text-light);
}
.topbar-icon-btn:hover { color: var(--text); }
.topbar-icon-btn i { font-size: .95rem; }
.topbar-badge {
    position: absolute;
    top: -3px;
    right: -3px;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: 9px;
    background: var(--danger);
    color: #fff;
    font-size: .65rem;
    font-weight: 700;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    box-shadow: 0 0 0 2px #fff;
    pointer-events: none;
}
/* Explicit hide-when-[hidden] — overrides the display:inline-flex above
   so the JS-controlled "hide when count=0" actually works. */
.topbar-badge[hidden] { display: none !important; }

/* Account button — same 10px rounded-rect as the search trigger and bell */
.topbar-account-btn {
    gap: var(--sp-2);
    padding: 4px 10px 4px 4px;
    border-radius: 10px;
}
.topbar-account-name {
    font-size: .9rem;
    font-weight: 500;
    color: var(--text);
}
.topbar-chevron {
    font-size: .7rem;
    color: var(--text-light);
}
@media (max-width: 700px) {
    .topbar-account-name { display: none; }
}

/* Dropdown panels (notification + account) */
.topbar-dropdown {
    position: absolute;
    top: calc(100% + var(--sp-2));
    right: 0;
    min-width: 280px;
    max-width: 380px;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: 0 10px 40px rgba(0, 0, 0, .12);
    z-index: 30;
    overflow: hidden;
}
.topbar-dropdown[hidden] { display: none; }
.topbar-dropdown-head-actions {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
}
.topbar-dropdown-close {
    background: transparent;
    border: 0;
    width: 28px;
    height: 28px;
    border-radius: 6px;
    color: var(--text-muted);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
    padding: 0;
    transition: background .12s ease, color .12s ease;
}
.topbar-dropdown-close:hover { background: var(--bg-soft); color: var(--text); }
.topbar-dropdown-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    padding: var(--sp-4);
    border-bottom: 1px solid var(--border-soft);
}
.topbar-dropdown-title {
    margin: 0;
    font-size: .92rem;
    font-weight: 600;
}
.topbar-link-btn {
    background: transparent;
    border: 0;
    color: var(--text-light);
    font-size: .8rem;
    cursor: pointer;
    padding: 4px 8px;
    border-radius: 6px;
    font-family: inherit;
}
.topbar-link-btn:hover { color: var(--text); background: var(--bg-soft); }

/* Account dropdown */
.topbar-account-dropdown { min-width: 260px; }
.topbar-account-head {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-4);
    border-bottom: 1px solid var(--border-soft);
}
.topbar-account-meta { min-width: 0; }
.topbar-account-fullname {
    font-weight: 600;
    color: var(--text);
    font-size: .92rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.topbar-account-email {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.topbar-menu-item {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: 10px var(--sp-4);
    color: var(--text);
    text-decoration: none;
    font-size: .9rem;
    background: transparent;
    border: 0;
    cursor: pointer;
    width: 100%;
    text-align: left;
    font-family: inherit;
}
.topbar-menu-item:hover { background: var(--bg-soft); }
.topbar-menu-item i { width: 16px; color: var(--text-light); text-align: center; }
.topbar-menu-item-danger { color: var(--danger, #b91c1c); }
.topbar-menu-item-danger i { color: var(--danger, #b91c1c); }
.topbar-menu-item-danger:hover { background: var(--danger-bg, #fef2f2); }
.topbar-menu-form { margin: 0; }

/* Notification panel */
.topbar-notif-dropdown {
    min-width: 360px;
    max-height: 70vh;
    display: flex;
    flex-direction: column;
}

/* MOBILE: anchor the notification panel to the viewport instead of the
 * bell button. The 360px min-width was clipping off the left edge on
 * 375px phones because absolute-right-from-bell pushed the panel past
 * viewport left. position:fixed + left/right insets gives us a clean
 * full-width-minus-padding panel that fits any phone.
 *
 * z-index 100 puts the dropdown above the portal-topbar (z:50), the
 * gallery sticky bar (z:30 on mobile), and any other page chrome. It
 * behaves as a modal overlay when open. */
@media (max-width: 600px) {
    .topbar-dropdown.topbar-notif-dropdown {
        position: fixed;
        top: calc(var(--portal-topbar-h) + var(--sp-2));
        left: var(--sp-3);
        right: var(--sp-3);
        min-width: 0;
        max-width: none;
        width: auto;
        max-height: calc(100vh - var(--portal-topbar-h) - var(--sp-4));
        z-index: 100;
    }
}
.topbar-notif-list {
    overflow-y: auto;
    flex: 1;
    max-height: 60vh;
}
.topbar-notif-empty {
    padding: var(--sp-6) var(--sp-4);
    text-align: center;
    color: var(--text-light);
}
.topbar-notif-empty i {
    display: block;
    font-size: 1.5rem;
    margin-bottom: var(--sp-2);
    opacity: .5;
}
/* ── Notification filter tabs (Unread / All) ──────────────────────── */
.topbar-notif-tabs {
    display: flex;
    gap: 0;
    padding: 4px var(--sp-4);
    border-bottom: 1px solid var(--border-soft);
    background: #fff;
}
.topbar-notif-tab {
    appearance: none;
    background: transparent;
    border: 0;
    padding: 8px 12px;
    font: inherit;
    font-size: .85rem;
    font-weight: 500;
    color: var(--text-muted);
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    transition: color .12s, border-color .12s;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.topbar-notif-tab:hover { color: var(--text); }
.topbar-notif-tab.is-active {
    color: var(--text);
    border-bottom-color: var(--brand);
}
.topbar-notif-tab-count {
    background: var(--danger);
    color: #fff;
    font-size: .65rem;
    font-weight: 700;
    line-height: 1;
    padding: 2px 6px;
    border-radius: 9px;
    min-width: 18px;
    text-align: center;
}

/* ── Date-bucket group headers (Today / Yesterday / Earlier) ─────── */
.topbar-notif-group-label {
    padding: var(--sp-3) var(--sp-4) 4px;
    font-size: .65rem;
    font-weight: 700;
    color: var(--text-light);
    text-transform: uppercase;
    letter-spacing: .08em;
}

/* ── Notification item — refined visual hierarchy ─────────────────── */
.topbar-notif-item {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    color: inherit;
    text-decoration: none;
    border-bottom: 1px solid var(--border-soft);
    background: #fff;
    transition: background .12s;
}
.topbar-notif-item:last-child { border-bottom: 0; }
.topbar-notif-item:hover { background: var(--bg-soft); }

/* Unread state — light background tint + colored left bar.
   Together with the bold title + the right-side dot, three visual
   cues a glance can pick up. */
.topbar-notif-item.is-unread {
    background: #fafafa;
    box-shadow: inset 3px 0 0 var(--brand-accent, #ff6b35);
}
.topbar-notif-item.is-unread:hover { background: #f4f4f4; }

/* Round colored icon badge on the left — replaces the bare dot.
   Tone classes drive the bg color so payment/agreement/etc each get
   their own visual identity. */
.topbar-notif-icon {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: .85rem;
    margin-top: 2px;
}
.topbar-notif-icon-success { background: #d1fae5; color: #065f46; }
.topbar-notif-icon-danger  { background: #fee2e2; color: #991b1b; }
.topbar-notif-icon-info    { background: #dbeafe; color: #1e40af; }
.topbar-notif-icon-neutral { background: var(--bg-soft); color: var(--text-muted); }
.topbar-notif-icon-gold    { background: #fdf0c8; color: #a9770a; }

/* Unread dot — small accent on the right side of the row */
.topbar-notif-dot {
    position: absolute;
    top: 50%;
    right: 14px;
    transform: translateY(-50%);
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--brand-accent, #ff6b35);
    flex-shrink: 0;
}

.topbar-notif-body-wrap { min-width: 0; flex: 1; padding-right: 18px; }
.topbar-notif-title {
    font-size: .9rem;
    font-weight: 500;
    color: var(--text);
    margin-bottom: 2px;
    line-height: 1.35;
}
.topbar-notif-item.is-unread .topbar-notif-title { font-weight: 600; }
.topbar-notif-body {
    font-size: .8rem;
    color: var(--text-muted);
    line-height: 1.4;
    margin-bottom: 4px;
}
.topbar-notif-time {
    font-size: .7rem;
    color: var(--text-light);
    font-weight: 500;
}

/* Search modal */
.topbar-search-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, .45);
    z-index: 1000;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: 10vh var(--sp-4) var(--sp-4);
}
.topbar-search-backdrop[hidden] { display: none; }
body.topbar-search-open { overflow: hidden; }
.topbar-search-modal {
    background: #fff;
    border-radius: 14px;
    width: 100%;
    max-width: 640px;
    box-shadow: 0 20px 60px rgba(0, 0, 0, .25);
    overflow: hidden;
    display: flex;
    flex-direction: column;
    max-height: 70vh;
}
.topbar-search-input-wrap {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-4) var(--sp-5);
    border-bottom: 1px solid var(--border-soft);
}
.topbar-search-input-wrap > i {
    color: var(--text-light);
    font-size: 1rem;
}
.topbar-search-input {
    flex: 1;
    border: 0;
    outline: none;
    background: transparent;
    font-size: 1rem;
    font-family: inherit;
    color: var(--text);
    min-width: 0;
}
.topbar-search-results {
    overflow-y: auto;
    flex: 1;
    padding: var(--sp-2) 0;
}
.topbar-search-hint {
    padding: var(--sp-5) var(--sp-5);
    margin: 0;
    color: var(--text-light);
    text-align: center;
    font-size: .9rem;
}
.topbar-search-group { padding-bottom: var(--sp-2); }
.topbar-search-group-label {
    padding: var(--sp-2) var(--sp-5);
    font-size: .72rem;
    color: var(--text-light);
    letter-spacing: .06em;
    font-weight: 600;
    text-transform: uppercase;
}
.topbar-search-result {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: 10px var(--sp-5);
    color: inherit;
    text-decoration: none;
}
.topbar-search-result:hover,
.topbar-search-result.is-active {
    background: var(--bg-soft);
}
.topbar-search-result > i {
    width: 20px;
    text-align: center;
    color: var(--text-light);
}
.topbar-search-thumb {
    width: 36px;
    height: 36px;
    flex: 0 0 auto;
    object-fit: cover;
    border-radius: 6px;
    background: var(--bg-muted);
}
.topbar-search-result-body { min-width: 0; flex: 1; }
.topbar-search-result-label {
    font-size: .92rem;
    font-weight: 500;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.topbar-search-result-sub {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Section divider inside a form-card ───────────────────────────────────── */
.form-divider {
    height: 1px;
    background: var(--border-soft);
    margin: var(--sp-6) calc(var(--sp-6) * -1);
}

/* ─────────────────────────────────────────────────────────────────────────
   Avatars — used in:
     * Sidebar user cards (admin + client)
     * Settings page upload preview
     * Future: activity log, client list, messages
   ───────────────────────────────────────────────────────────────────────── */

/* Base avatar — flexible size via the --avatar-size custom property
   (default 40px; override per usage site). */
.avatar {
    --avatar-size: 40px;
    width: var(--avatar-size);
    height: var(--avatar-size);
    border-radius: 50%;
    background: var(--bg-muted);
    color: var(--text);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    font-size: calc(var(--avatar-size) * 0.4);
    overflow: hidden;
    flex-shrink: 0;
    text-transform: uppercase;
    line-height: 1;
}
.avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.avatar-sm  { --avatar-size: 28px; }
.avatar-md  { --avatar-size: 40px; }
.avatar-lg  { --avatar-size: 56px; }
.avatar-xl  { --avatar-size: 80px; }

/* Settings page upload widget */
.avatar-upload-field { margin-bottom: var(--sp-5); }
.avatar-upload-row {
    display: flex;
    align-items: center;
    gap: var(--sp-5);
    flex-wrap: wrap;
}
.avatar-preview {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    background: var(--bg-muted);
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.avatar-preview img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.avatar-preview .avatar-initial {
    font-size: 1.8rem;
    font-weight: 600;
    color: var(--text);
}
.avatar-upload-controls {
    flex: 1;
    min-width: 240px;
}
.avatar-upload-btn { cursor: pointer; }

/* Avatar inside the sidebar user card */
.portal-user-card .avatar {
    --avatar-size: 36px;
    margin-bottom: var(--sp-2);
}

/* ── Avatar crop modal (Cropper.js wrapper) ───────────────────────────────── */
.avatar-cropper-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, .55);
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sp-4);
}
.avatar-cropper-backdrop[hidden] { display: none !important; }
body.avatar-cropper-open { overflow: hidden; }

.avatar-cropper-modal {
    background: #fff;
    border-radius: 14px;
    overflow: hidden;
    width: 100%;
    max-width: 640px;
    max-height: 90vh;
    display: flex;
    flex-direction: column;
    box-shadow: 0 20px 60px rgba(0, 0, 0, .25);
}
.avatar-cropper-head {
    padding: var(--sp-5) var(--sp-5) var(--sp-4);
    border-bottom: 1px solid var(--border-soft);
}
.avatar-cropper-body {
    padding: var(--sp-5);
    background: var(--bg-soft);
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    min-height: 0;
}
.avatar-cropper-stage {
    width: 100%;
    aspect-ratio: 1 / 1;
    max-height: 60vh;
    overflow: hidden;
    /* Checkerboard so a transparent PNG (e.g. a logo) reads as transparent,
       not solid black, behind the image. */
    background-color: #fff;
    background-image:
        linear-gradient(45deg, #e0e0e0 25%, transparent 25%),
        linear-gradient(-45deg, #e0e0e0 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, #e0e0e0 75%),
        linear-gradient(-45deg, transparent 75%, #e0e0e0 75%);
    background-size: 20px 20px;
    background-position: 0 0, 0 10px, 10px -10px, -10px 0;
    border-radius: 10px;
}
.avatar-cropper-stage img {
    display: block;
    max-width: 100%;
}
.avatar-cropper-hint {
    text-align: center;
    margin: 0;
}
.avatar-cropper-foot {
    padding: var(--sp-4) var(--sp-5);
    border-top: 1px solid var(--border-soft);
    display: flex;
    justify-content: space-between;
    gap: var(--sp-3);
}

/* Make the Cropper.js viewbox render as a circle so the user sees the
   actual avatar shape while positioning. */
.avatar-cropper-stage .cropper-view-box,
.avatar-cropper-stage .cropper-face {
    border-radius: 50%;
}
.avatar-cropper-stage .cropper-view-box {
    outline: 2px solid #fff;
    outline-offset: -1px;
}
/* Logos display in a rectangle, not a circle, so square off the guide when the
   modal is framing a logo (set by avatar-cropper.js). The export is still a
   square PNG; with transparency preserved the corners simply read as empty. */
.avatar-cropper-backdrop.cropper-square .avatar-cropper-stage .cropper-view-box,
.avatar-cropper-backdrop.cropper-square .avatar-cropper-stage .cropper-face {
    border-radius: 0;
}
/* Fixed circular crop — no resize handles or guide lines to mis-drag. */
.avatar-cropper-stage .cropper-point,
.avatar-cropper-stage .cropper-line,
.avatar-cropper-stage .cropper-dashed { display: none !important; }
.avatar-cropper-stage .cropper-face { cursor: move; }

/* ── Form checkbox row ────────────────────────────────────────────────────── */
.form-check {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    padding: var(--sp-3) 0;
    cursor: pointer;
}
.form-check input[type="checkbox"] {
    width: 18px;
    height: 18px;
    margin: 2px 0 0;
    accent-color: var(--text);
    cursor: pointer;
    flex-shrink: 0;
}
.form-check-label {
    display: block;
    font-size: .95rem;
    font-weight: 500;
    color: var(--text);
    margin-bottom: 2px;
}
.form-check .form-help {
    margin-top: 2px;
}

/* ── NET preset buttons (under Due date) ──────────────────────────────────── */
.due-date-presets {
    display: flex;
    gap: var(--sp-2);
    margin-top: var(--sp-2);
    flex-wrap: wrap;
}
.due-preset-btn {
    appearance: none;
    background: var(--bg-soft);
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    padding: 4px 12px;
    font-size: .8rem;
    font-weight: 600;
    color: var(--text-light);
    cursor: pointer;
    letter-spacing: .03em;
    transition: background .15s, color .15s, border-color .15s;
}
.due-preset-btn:hover {
    color: var(--text);
    border-color: var(--text-light);
}
.due-preset-btn.is-active {
    background: var(--text);
    color: #fff;
    border-color: var(--text);
}
/* "Custom" calendar pill — opens the date picker. */
.due-preset-cal {
    display: inline-flex;
    align-items: center;
    gap: 5px;
}
.due-preset-cal i { font-size: .78rem; }

/* ── Flush data-table variant (no outer wrap border) ──────────────────────── */
.data-table-flush { border: 0; box-shadow: none; }
.data-table-flush th,
.data-table-flush td { padding-left: 0; padding-right: 0; }

/* Invoice line items: give them more presence so the eye lands on the actual
   work billed, not just the totals below. A bolder/darker column header and a
   stronger description cell; taller rows on desktop (mobile keeps its compact
   stacked layout). */
.invoice-lines-table thead th {
    color: var(--text);
    font-size: .78rem;
}
.invoice-lines-table tbody td:first-child {
    font-size: 1rem;
    font-weight: 600;
    color: var(--text);
}
@media (min-width: 600px) {
    .invoice-lines-table tbody td {
        padding-top: var(--sp-4);
        padding-bottom: var(--sp-4);
    }
}


/* ─────────────────────────────────────────────────────────────────────────
   Agreements module
   ───────────────────────────────────────────────────────────────────────── */

/* Status badge variants used in the agreement list. Most of these reuse
   existing palette tokens. */
.badge-purple        { background: #ede9fe; color: #6d28d9; }
.badge-success-strong{ background: #d1fae5; color: #065f46; }
.badge-warn          { background: var(--warn-bg, #fef3c7); color: var(--warn, #92400e); }

/* "Strike-through everywhere" treatment for superseded rows in the list */
.data-table tr.is-superseded td {
    color: var(--text-light);
    text-decoration: line-through;
    text-decoration-color: rgba(0,0,0,.3);
}
/* …and for the rendered doc card */
.is-superseded-doc {
    opacity: .55;
    position: relative;
}
.is-superseded-doc::after {
    content: "SUPERSEDED";
    position: absolute;
    top: 20px;
    right: 20px;
    font-size: .7rem;
    letter-spacing: .15em;
    color: #b91c1c;
    border: 2px solid #b91c1c;
    padding: 4px 10px;
    border-radius: 4px;
    transform: rotate(8deg);
    pointer-events: none;
    font-weight: 700;
}
/* Strike through every line of the actual document body when superseded
   so it's unmistakable visually — opacity-dim alone left the doc still
   reading as authoritative. Apply to paragraphs / headings / list items
   / table cells inside the body so each line gets its own strike rather
   than one long line across blocks. The signer block (signatures, IP,
   timestamps) and the agreement-signers grid below it stay un-struck
   — those record what actually happened and should remain legible. */
.is-superseded-doc .agreement-body p,
.is-superseded-doc .agreement-body h1,
.is-superseded-doc .agreement-body h2,
.is-superseded-doc .agreement-body h3,
.is-superseded-doc .agreement-body h4,
.is-superseded-doc .agreement-body h5,
.is-superseded-doc .agreement-body h6,
.is-superseded-doc .agreement-body li,
.is-superseded-doc .agreement-body td,
.is-superseded-doc .agreement-body th,
.is-superseded-doc .agreement-body blockquote {
    text-decoration: line-through;
    text-decoration-color: rgba(0,0,0,.4);
}

/* Highlight rows in the client list that need action */
.data-table tr.is-needs-action td:first-child {
    box-shadow: inset 3px 0 0 var(--info, #2563eb);
}

/* Two-column signer block under the agreement body */
.agreement-signers {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: var(--sp-5);
    padding-top: var(--sp-4);
    border-top: 1px solid var(--border);
}
.agreement-signer {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.agreement-signer-label {
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--text-light);
}
.agreement-signer-name { font-weight: 600; }
.agreement-signature-img {
    display: block;
    margin-top: 6px;
    max-width: 240px;
    max-height: 90px;
    border-bottom: 1px solid var(--border);
    padding-bottom: 4px;
}

/* Signature pad */
.sig-pad {
    position: relative;
    border: 1px solid var(--border-strong);
    border-radius: 8px;
    background: #fff;
    overflow: hidden;
    height: 160px;
}
.sig-pad canvas {
    display: block;
    width: 100%;
    height: 100%;
    cursor: crosshair;
    touch-action: none;
}
.sig-pad.is-empty::before {
    content: "Draw your signature here";
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-light);
    font-size: .9rem;
    pointer-events: none;
    font-style: italic;
}
.sig-clear {
    position: absolute;
    bottom: 6px;
    right: 8px;
    font-size: .75rem;
    color: var(--text-light);
    background: rgba(255,255,255,.85);
    border: 1px solid var(--border);
    padding: 2px 8px;
    border-radius: 4px;
    cursor: pointer;
}
.sig-clear:hover { color: var(--text); }

/* Countersign modal */
.countersign-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,.45);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
    padding: var(--sp-4);
}
.countersign-modal-backdrop[hidden] { display: none; }
.countersign-modal {
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 20px 60px rgba(0,0,0,.25);
    padding: var(--sp-5);
    max-width: 520px;
    width: 100%;
    max-height: 92vh;
    overflow-y: auto;
}
.countersign-modal h2 { margin: 0 0 var(--sp-2); }

/* ── Rendered agreement body (in-app view) ──────────────────────────────────
   Used inside the admin/client agreement detail card. Mirrors the snapshot
   stylesheet at the typography level so an agreement looks identical
   in-app and when printed/downloaded. */
.agreement-body { color: var(--text); line-height: 1.6; }
.agreement-body h2 {
    margin: 0 0 var(--sp-5);
    font-size: 1.05rem;
    text-align: center;
    letter-spacing: .04em;
    font-weight: 700;
}
.agreement-body h3 {
    margin: var(--sp-6) 0 var(--sp-2);
    font-size: .8rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--text);
}
.agreement-body p  { margin: var(--sp-2) 0; }
.agreement-body ul { margin: var(--sp-2) 0 var(--sp-2) var(--sp-5); }
.agreement-body strong { font-weight: 600; }

/* Rates table — in-app variant (the snapshot file uses the same class with
   its own literal-value styles so it works standalone). */
.agreement-rates {
    width: 100%;
    border-collapse: collapse;
    margin-top: var(--sp-3);
    border: 1px solid var(--border);
}
.agreement-rates th,
.agreement-rates td {
    padding: var(--sp-3) var(--sp-4);
    border-top: 1px solid var(--border-soft);
    text-align: left;
    font-size: .9rem;
}
.agreement-rates thead th {
    background: var(--bg-soft);
    border-top: 0;
    font-size: .72rem;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--text-light);
    font-weight: 600;
}
.agreement-rates .price {
    text-align: right;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* ─────────────────────────────────────────────────────────────────────────
   Agreement WYSIWYG editor (templates + new-agreement page)
   ───────────────────────────────────────────────────────────────────────── */
.ag-editor {
    border: 1px solid var(--border-strong);
    border-radius: 10px;
    background: #fff;
    overflow: hidden;
}

.ag-toolbar {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-2);
    background: var(--bg-soft);
    border-bottom: 1px solid var(--border);
    flex-wrap: wrap;
}
.ag-tb-group {
    display: inline-flex;
    border: 1px solid var(--border);
    border-radius: 6px;
    overflow: hidden;
    background: #fff;
}
button.ag-tb {
    appearance: none;
    border: 0;
    background: #fff;
    color: var(--text);
    font-family: inherit;
    font-size: .85rem;
    padding: .35rem .55rem;
    min-width: 32px;
    cursor: pointer;
    line-height: 1.2;
    border-right: 1px solid var(--border);
    transition: background .12s;
}
.ag-tb-group button.ag-tb:last-child { border-right: 0; }
button.ag-tb:hover { background: var(--bg-soft); }
button.ag-tb.is-active {
    background: var(--text);
    color: var(--text-inverse);
}
button.ag-tb.is-active i { color: var(--text-inverse); }

/* Edit/Preview/HTML tabs — pushed to the right side of the toolbar */
.ag-toolbar-tabs {
    margin-left: auto;
    display: inline-flex;
    border: 1px solid var(--border);
    border-radius: 6px;
    overflow: hidden;
    background: #fff;
}
.ag-tab {
    appearance: none;
    border: 0;
    background: #fff;
    color: var(--text-light);
    font-family: inherit;
    font-size: .8rem;
    font-weight: 600;
    padding: .35rem .7rem;
    cursor: pointer;
    border-right: 1px solid var(--border);
    transition: background .12s, color .12s;
}
.ag-tab:last-child { border-right: 0; }
.ag-tab:hover { color: var(--text); }
.ag-tab.is-active {
    background: var(--text);
    color: var(--text-inverse);
}

/* When NOT in rich mode, dim/disable the formatting buttons. They wake up
   the editor automatically if clicked. */
.ag-editor:not(.ag-mode-rich) .ag-tb-group {
    opacity: .45;
}

/* The editing surface. The wrapper has a fixed initial height and can be
   resized vertically only — never horizontally — so the page layout stays
   intact when the user drags the resize handle. */
.ag-stage {
    min-height: 360px;
    height: 480px;
    resize: vertical;
    overflow: hidden;             /* let the child handle its own scrolling */
    position: relative;
}
.ag-rich,
.ag-raw,
.ag-preview {
    width: 100%;
    height: 100%;
    padding: var(--sp-5);
    overflow-y: auto;
    box-sizing: border-box;
    outline: none;
    background: #fff;
    color: var(--text);
}
.ag-rich {
    line-height: 1.6;
    font-size: 15px;
}
.ag-rich:focus { outline: none; }   /* card border carries the focus cue */
.ag-rich h2 {
    margin: 0 0 var(--sp-5);
    font-size: 1.05rem;
    text-align: center;
    letter-spacing: .04em;
    font-weight: 700;
}
.ag-rich h3 {
    margin: var(--sp-6) 0 var(--sp-2);
    font-size: .8rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
}
.ag-rich p  { margin: var(--sp-2) 0; }
.ag-rich ul { margin: var(--sp-2) 0 var(--sp-2) var(--sp-5); }
.ag-rich ol { margin: var(--sp-2) 0 var(--sp-2) var(--sp-5); }
.ag-rich strong { font-weight: 600; }
.ag-rich a { color: var(--info); text-decoration: underline; }

.ag-raw {
    font-family: var(--font-mono);
    font-size: .85rem;
    line-height: 1.55;
    resize: none;
    border: 0;
    background: var(--bg-soft);
}

/* Focused state for the whole editor */
.ag-editor:focus-within {
    border-color: var(--text);
    box-shadow: 0 0 0 3px rgba(0, 0, 0, .06);
}

/* Rates Schedule legend — give it real heading weight, not the default
   tiny fieldset legend look. */
.ag-rates-fieldset {
    border: 0;
    padding: 0;
    margin: var(--sp-6) 0 0;
}
.ag-rates-legend {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--text);
    padding: 0;
    margin-bottom: var(--sp-2);
}

/* ─────────────────────────────────────────────────────────────────────────
   New-agreement compose: 2-column layout
   ───────────────────────────────────────────────────────────────────────── */
.ag-compose-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: var(--sp-5);
    align-items: start;
}
@media (max-width: 1100px) {
    .ag-compose-layout {
        grid-template-columns: 1fr;
    }
}
.ag-compose-main { min-width: 0; }
.ag-compose-aside {
    display: flex;
    flex-direction: column;
    /* Each card is .card; mt-3 between them keeps consistent spacing */
}

.ag-meta-box {
    background: var(--bg-soft);
    border: 1px solid var(--border-soft);
    border-radius: 6px;
    padding: var(--sp-2) var(--sp-3);
    font-size: .82rem;
    margin-bottom: var(--sp-3);
}
.ag-meta-box div { margin: 2px 0; }

/* Settings card checkbox row */
.checkbox-row {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-2);
    font-size: .85rem;
    cursor: pointer;
    margin: var(--sp-2) 0 0;
}
.checkbox-row input { margin-top: .2rem; }

/* Supersedes card — give it a subtle left accent so it reads as a distinct
   "this is the supersession control" without being loud. */
.ag-supersedes-card {
    border-left: 3px solid var(--warn-border, #fed7aa);
}
.ag-supersedes-card .card-title i {
    color: var(--warn, #92400e);
    margin-right: .25rem;
}

/* ─────────────────────────────────────────────────────────────────────────
   Rates Schedule editor (grid layout — no table-cell padding squeezing
   the inputs). Used on both the template editor and the new-agreement
   composer. Function matches TP's compose page; styling is all Edit.
   ───────────────────────────────────────────────────────────────────────── */
.ag-rates-grid {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
}
.ag-rates-head {
    display: grid;
    grid-template-columns: 1fr 180px 40px;
    gap: var(--sp-3);
    padding: 0 var(--sp-2);
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--text-light);
    font-weight: 600;
}
.ag-rates-row {
    display: grid;
    grid-template-columns: 1fr 180px 40px;
    gap: var(--sp-3);
    align-items: center;
}
.ag-rates-row .form-control { margin: 0; }
.ag-rates-remove {
    appearance: none;
    border: 1px solid var(--border);
    background: #fff;
    color: var(--text-light);
    width: 40px;
    height: 40px;
    border-radius: 6px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background .12s, color .12s, border-color .12s;
}
.ag-rates-remove:hover {
    background: #fff5f5;
    color: #b91c1c;
    border-color: #fecaca;
}

/* Additional-signer rows (multi-signer compose). Each is a small stacked card
   so name/email/role read as one signer; reuses .ag-rates-remove for the X. */
.ag-signer-row {
    padding: var(--sp-3);
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--bg-muted, #fafafa);
    margin-bottom: var(--sp-2);
}
.ag-signer-row-foot {
    display: grid;
    grid-template-columns: 1fr 40px;
    gap: var(--sp-2);
    align-items: center;
}
.ag-signer-row-foot .form-control { margin: 0; }

/* Supersedes panel — tighten internal spacing so it doesn't feel
   unfinished. The orange left accent stays. */
.ag-supersedes-card .card-title {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin: 0 0 var(--sp-2);
}
.ag-supersedes-card p {
    line-height: 1.5;
}
.ag-supersedes-card select { margin-top: var(--sp-1); }

/* ─────────────────────────────────────────────────────────────────────────
   Generic .card / .card-padded / .card-title — used throughout the
   agreements module's right-rail panels and elsewhere. Mirrors the
   .client-card visual so admin and client portal cards feel identical.
   ───────────────────────────────────────────────────────────────────────── */
.card {
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 12px;
}
.card-padded { padding: var(--sp-5); }
.card-title {
    margin: 0 0 var(--sp-3);
    font-size: 1rem;
    font-weight: 600;
    color: var(--text);
}

/* Collapsible card: the title row becomes a <summary> with a caret; the body
   folds. Same "feel simple" pattern as .settings-disclosure, but for the
   card-stack contexts (e.g. the agreement composer's right rail). */
.card-collapsible > summary.card-collapsible-summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-2);
    cursor: pointer;
    list-style: none;
    min-height: 28px;
}
.card-collapsible > summary::-webkit-details-marker { display: none; }
.card-collapsible > summary::marker { content: ''; }
.card-collapsible > summary .card-title { margin: 0; }
.card-collapsible-caret { display: inline-flex; color: var(--text-muted); transition: transform .15s ease; flex-shrink: 0; }
.card-collapsible[open] > summary .card-collapsible-caret { transform: rotate(90deg); }
.card-collapsible-hint { margin-left: auto; font-size: var(--fs-sm); font-weight: 400; color: var(--text-muted); }
.card-collapsible-body { margin-top: var(--sp-3); }

/* Breadcrumb-style nav row above a page header: back-link on the left,
   secondary action(s) on the right. Used on the New Agreement page (and
   reusable wherever a sub-page wants a Back ← / → Action header pair).
   Edge ghost buttons have their horizontal padding stripped so their
   text aligns flush with the page-title heading below. */
.page-subnav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    margin-bottom: var(--sp-3);
}
/* Pull the edge buttons out by their own horizontal padding so their TEXT
   lines up with the content edges, while the hover background stays symmetric
   (not lopsided with the label shoved to one side). */
.page-subnav > .btn-ghost:first-child { margin-left: calc(var(--sp-5) * -1); }
.page-subnav > .btn-ghost:last-child  { margin-right: calc(var(--sp-5) * -1); }

/* ─────────────────────────────────────────────────────────────────────────
   Project view: live "module" lists (agreements + invoices linked to a
   project). Used instead of placeholder "shipping in the X sprint" cards
   once those modules ship.
   ───────────────────────────────────────────────────────────────────────── */
.project-module-list {
    list-style: none;
    margin: 0 0 var(--sp-3);
    padding: 0;
    display: flex;
    flex-direction: column;
    border-top: 1px solid var(--border-soft);
}
.project-module-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    padding: var(--sp-3) 0;
    border-bottom: 1px solid var(--border-soft);
}
.project-module-row-main {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    text-decoration: none;
    color: var(--text);
    flex: 1;
    min-width: 0;
}
.project-module-row-main:hover .project-module-row-title { text-decoration: underline; }
.project-module-row-title { font-weight: 500; }
.project-module-row-meta { flex-shrink: 0; }
.project-module-row.is-superseded .project-module-row-title {
    color: var(--text-light);
    text-decoration: line-through;
}

/* ─────────────────────────────────────────────────────────────────────────
   Public agreement sign page (/sign?t=...)
   Standalone — no admin/client chrome. Lives at the root so non-portal
   signers can use it.
   ───────────────────────────────────────────────────────────────────────── */
.public-sign-body {
    background: var(--bg-soft);
    margin: 0;
    min-height: 100vh;
}
.public-sign-bar {
    background: #fff;
    border-bottom: 1px solid var(--border);
    padding: var(--sp-4) var(--sp-5);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
}
.public-sign-bar-brand {
    font-weight: 700;
    font-size: 1.1rem;
    color: var(--text);
}
.public-sign-shell {
    max-width: 780px;
    margin: 0 auto;
    padding: var(--sp-7) var(--sp-5) var(--sp-9);
}
.public-sign-title {
    margin: 0 0 var(--sp-5);
    font-size: 1.5rem;
    font-weight: 700;
}
.public-sign-form .card-title { margin-top: 0; }
.public-sign-success {
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: var(--sp-7);
    text-align: center;
}
.public-sign-success h1 {
    color: var(--success, #15803d);
    margin: 0 0 var(--sp-3);
    font-size: 1.5rem;
}
.public-sign-success h1 i { margin-right: .5rem; }
.public-sign-success p { margin: var(--sp-2) 0; color: var(--text-med); }
.public-sign-error h1 {
    color: var(--warn, #92400e);
    margin: 0 0 var(--sp-3);
    font-size: 1.25rem;
}
.public-sign-footer {
    margin-top: var(--sp-7);
    text-align: center;
    font-size: .8rem;
    color: var(--text-light);
}
.public-sign-footer a {
    color: var(--text-light);
    text-decoration: underline;
}

/* Shared client-facing footer (client portal + public share/sign pages):
   a quiet "Powered by Edit" credit + legal links. Intentionally understated.
   Hidden entirely for white-labeled studios (see includes/client_footer.php). */
.client-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 8px;
    margin-top: var(--sp-7);
    padding: var(--sp-5) 0 var(--sp-3);
    font-size: .78rem;
    color: var(--text-light);
}
.client-footer a { color: var(--text-light); text-decoration: none; }
.client-footer a:hover { color: var(--text-med); text-decoration: underline; }
.client-footer-credit a { font-weight: 600; }
.client-footer-dot { opacity: .5; }

/* ── Public gallery share page (standalone /share/{token}) ──────────────────
   Reuses the sign-page chrome but a wide shell for the gallery grid. */
.public-gallery-body {
    background: var(--bg-soft);
    margin: 0;
    min-height: 100vh;
    overflow-x: hidden;   /* belt-and-suspenders: never let the page exceed the viewport (mobile zoom) */
}
/* The body carries the gallery THEME class, so the WHOLE page adopts the theme's
   canvas + text — Noir/Classic fill the page edge-to-edge instead of leaving a
   light frame around the gallery. Hardcoded (not var()) so it can't be undone by
   the --g-canvas the .gallery-shell resets on its own descendants. Editorial =
   default light above. */
.public-gallery-body.gallery-theme-noir    { background: #0e0e10; color: #f4f4f5; }
.public-gallery-body.gallery-theme-classic { background: #f5f3ee; color: #2a2622; }
.public-gallery-shell {
    max-width: none;          /* full-width gallery on the public share page */
    margin: 0;
    /* Equal inset on top + left + right (larger bottom for scroll room). */
    padding-top: var(--sp-4);
    padding-left: var(--sp-4);
    padding-right: var(--sp-4);
    padding-bottom: var(--sp-9);
}
/* The gallery THEME sets --g-pad-y (the gallery-shell's own vertical padding) up
   to ~34px for the framed-canvas look — which stacked ON TOP of the shell inset
   above, pushing the hero ~45px down. Zero it here so the only top inset is the
   shell's equal sp-4. */
.public-gallery-shell .gallery-shell { margin-top: 0; --g-pad-y: 0px; }
/* No admin/portal topbar here, so the sticky toolbar sticks to the actual top
   (not offset by --admin-topbar-h) and aligns with the grid instead of going
   edge-to-edge with the mobile negative margins (which threw alignment off). */
.public-gallery-body .gallery-status-bar-sticky { top: 0; }
@media (max-width: 900px) {
    /* The base mobile rule pulls the sticky bar edge-to-edge with negative
       margins (tuned for the portal layout). On the immersive page that makes
       the bar bleed past the gallery's gutter — force it back so the bar lines
       up with the cover + grid. !important defeats the base rule regardless of
       source order / cache. */
    .public-gallery-body .gallery-status-bar-sticky {
        top: 0;
        margin-left: 0 !important;
        margin-right: 0 !important;
        border-radius: 12px;
        border-left: 1px solid;
        border-right: 1px solid;
    }
}

/* Share-this-gallery box inside the project-detail Gallery card. */
.share-box {
    border-top: 1px solid var(--border);
    padding-top: var(--sp-4);
}
.share-box .field-inline input { min-width: 0; }

/* Share card: link + actions on one line; the URL flexes and the buttons
   (Copy / Open / Revoke) sit beside it. Wraps gracefully on narrow screens. */
.share-link-row {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    flex-wrap: wrap;
}
.share-link-row > .form-control { flex: 1 1 14rem; min-width: 0; }
.share-link-row > form { display: contents; }   /* let the revoke button join the flex row */

/* Expiry control: label + date input + Set, all vertically centered. */
.share-expiry-row {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    flex-wrap: wrap;
}
.share-expiry-row > .form-control { flex: 0 0 auto; width: auto; }
.share-expiry-label { white-space: nowrap; }

/* Settings: repeating "Photography type" category rows (Add category). */
.category-rows { display: flex; flex-direction: column; gap: var(--sp-2); }
.category-row { display: flex; align-items: center; gap: var(--sp-2); }
.category-row > .form-control { flex: 1 1 auto; min-width: 0; }
.category-remove { flex: 0 0 auto; padding: 8px 11px; color: var(--text-med); }
.category-remove:hover { color: #dc2626; }

/* ── Gallery card: zoned layout (filmstrip hero + share sub-panel) ──────────
   Lifts the card from a flat stack of controls into distinct, intentional
   zones: the filmstrip reads as one cohesive preview, and the Share controls
   live in their own quietly-tinted module. */
/* Single cover-image banner as the card preview (replaces the filmstrip). */
.gallery-cover-banner {
    position: relative;
    width: 100%;
    height: 250px;
    border-radius: 10px;
    overflow: hidden;
    border: 1px solid var(--border);   /* light card-like edge around the hero */
    box-shadow: 0 2px 10px -5px rgba(0,0,0,.22);
    margin-bottom: var(--sp-3);
    background: var(--bg-soft);
}
/* Clickable hero: opens the gallery, with an "Open gallery" hint on hover. */
.gallery-cover-banner-link { display: block; cursor: pointer; transition: box-shadow .15s; }
.gallery-cover-banner-link:hover { box-shadow: 0 6px 18px -7px rgba(0,0,0,.35); }
.gallery-cover-banner-link::after {
    content: "Open gallery"; position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center; gap: 8px;
    color: #fff; font-weight: 500; font-size: .95rem;
    background: rgba(0,0,0,.34); opacity: 0; transition: opacity .15s;
}
.gallery-cover-banner-link:hover::after { opacity: 1; }
/* Clickable cover banner with a dynamic hover label (e.g. "View and select" vs
   "View gallery"). Uses a real overlay element so the text can vary per gallery
   mode (the ::after variant above is fixed-text). */
.gallery-cover-banner-hoverlink { display: block; cursor: pointer; transition: box-shadow .15s; }
.gallery-cover-banner-hoverlink:hover { box-shadow: 0 6px 18px -7px rgba(0,0,0,.35); }
.gallery-cover-banner-cta {
    position: absolute; inset: 0; z-index: 2; pointer-events: none;
    display: flex; align-items: center; justify-content: center; gap: 8px;
    color: #fff; font-weight: 600; font-size: .95rem; letter-spacing: .01em;
    background: rgba(0,0,0,.34); opacity: 0; transition: opacity .15s ease;
}
.gallery-cover-banner-hoverlink:hover .gallery-cover-banner-cta,
.gallery-cover-banner-hoverlink:focus-visible .gallery-cover-banner-cta { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .gallery-cover-banner-cta { transition: none; } }

/* Gallery panel status bar (under the hero): status (left) + actions (right).
   Distinct class from the grid's .gallery-toolbar (select-all + size slider)
   so the two bars don't inherit each other's styling on the same page. */
.gallery-statusbar {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--sp-3); flex-wrap: wrap;
}
.gallery-toolbar-status {
    display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
    font-size: .9rem; color: var(--text-med); min-width: 0;
}
/* Keep "N photos" as one unit so the flex gap can't split "photo" from its
   plural "s" (it rendered as "20 photo s"). */
.gallery-toolbar-count { white-space: nowrap; }
.gallery-toolbar-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* Sticky in-page section nav on the project detail (jump + scrollspy). */
/* Project detail = a tabbed workspace. project-nav.js builds this bar from the
   [data-section] cards and shows one panel at a time. Clean underline tabs,
   sticky under the topbar so they stay reachable on a long panel. */
.project-tabs {
    position: sticky;
    top: var(--admin-topbar-h);   /* below the sticky search/bell topbar */
    z-index: 40;
    display: flex;
    gap: 2px;
    margin: 0 0 var(--sp-5);
    padding: 0;
    overflow-x: auto; scrollbar-width: none;
    -webkit-overflow-scrolling: touch;   /* smooth momentum scroll like the Settings subnav */
    overscroll-behavior-x: contain;      /* don't chain the horizontal scroll to the page (the "floating" on iOS) */
    background: var(--bg-soft);   /* match page chrome; solid backdrop when content scrolls under */
    border-bottom: 1px solid var(--border);
}
.project-tabs::-webkit-scrollbar { display: none; }
@media (max-width: 900px) {
    /* Drop sticky on mobile: a single element that is BOTH position:sticky AND
       overflow-x:auto detaches and "floats" during scroll on iOS Safari. Static
       lets the tab strip scroll as a clean linear row. */
    .project-tabs { position: static; top: auto; z-index: 30; }
}
.project-tab {
    flex: 0 0 auto;
    display: inline-flex; align-items: center; gap: 6px;   /* label + count badge / activity dot */
    appearance: none; -webkit-appearance: none;
    background: none;
    border: 0;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;            /* sit the underline on the bar's border */
    padding: 12px 14px;
    font-family: inherit;
    font-size: .9rem;
    font-weight: 500;
    color: var(--text-light);      /* clearly muted so the active tab reads at a glance */
    white-space: nowrap;
    cursor: pointer;
    transition: color .12s, border-color .12s;
}
.project-tab:hover { color: var(--text-med); }
/* Active: near-black label + a brand-accent underline (the same accent as the
   focus ring), so the current section pops without shouting. Weight stays put
   to avoid the row nudging sideways when you switch tabs. */
.project-tab.is-active { color: var(--text); border-bottom-color: var(--brand-accent); }
.project-tab:focus-visible { outline: 2px solid var(--brand-accent); outline-offset: -2px; }
/* Count badge: how much lives in this section, at a glance. */
.project-tab-count {
    font-size: .7rem; font-weight: 600; line-height: 1;
    color: var(--text-light); background: var(--bg-muted);
    padding: 3px 6px; border-radius: 999px; min-width: 18px; text-align: center;
}
.project-tab.is-active .project-tab-count { color: var(--text-med); }
/* Flat tab panels: the tab IS the section header, so strip the card chrome
   (border, background, padding, collapse) — content sits full-bleed under the
   bar. Scoped to .project-main so the sidebar cards keep their card look. */
.project-main.is-tabbed { gap: 0; }
.project-main.is-tabbed > .card-section[data-section] {
    border: 0; background: none; border-radius: 0; padding: 0; overflow: visible;
}
.project-main.is-tabbed > .card-section[data-section] > .card-section-body,
.project-main.is-tabbed > .card-section[data-section] > .card-section-body > .card-section-body-inner {
    padding: 0;
}
/* Panels stay open in tab mode (no collapse animation), so drop the collapse
   clip — otherwise the full-bleed gallery grid's edge tiles + their selection
   /pick rings get shaved off at the left/right edge. */
.project-main.is-tabbed > .card-section[data-section] > .card-section-body { overflow: visible; }
/* The section head becomes a right-aligned action toolbar; the title is
   redundant with the tab, and a head with no actions disappears entirely. */
.project-main.is-tabbed > .card-section[data-section] > .card-section-head {
    padding: 0; margin: 0 0 var(--sp-4); cursor: default; border: 0; background: none;
    display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-2);
}
.project-main.is-tabbed > .card-section[data-section] > .card-section-head .card-section-title,
.project-main.is-tabbed > .card-section[data-section] > .card-section-head .card-section-chevron { display: none; }
.project-main.is-tabbed > .card-section[data-section] > .card-section-head:not(:has(button, a)) { display: none; }

/* Project overview in the header: glanceable facts strip + description. */
.project-overview { margin: var(--sp-2) 0 var(--sp-5); }
.project-facts {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px var(--sp-5);
    font-size: .9rem; padding-bottom: var(--sp-3);
    border-bottom: 1px solid var(--border-soft);
}
.project-fact { color: var(--text); }
.project-fact-k { color: var(--text-light); margin-right: 5px; }
.project-fact a { color: var(--text); text-decoration: none; }
.project-fact a:hover { color: var(--brand-accent-strong); }
.project-overview-edit {
    margin-left: auto; background: none; border: 0; padding: 0; cursor: pointer;
    font: inherit; font-size: .85rem; color: var(--brand-accent-strong);
    display: inline-flex; align-items: center; gap: 5px;
}
.project-overview-edit:hover { text-decoration: underline; }
.project-desc { margin-top: var(--sp-3); max-width: 70ch; }
.project-desc-label { display: block; font-size: .7rem; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 3px; }
.project-desc-text { margin: 0; color: var(--text-med); line-height: 1.55; white-space: pre-line; }
.project-desc-text.is-clamped { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.project-desc-more { margin-top: 4px; font-size: .8rem; }

@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
/* "View all" footer row under a truncated list (e.g. dashboard Recent). */
.dash-card-more {
    display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
    padding: 11px; border-top: 1px solid var(--border-soft);
    font-size: .82rem; font-weight: 600; color: var(--text-med); text-decoration: none;
}
.dash-card-more:hover { color: var(--brand-accent-strong); background: var(--bg-soft); }
.dash-card-more i { font-size: .72rem; transition: transform .12s ease; }
.dash-card-more:hover i { transform: translateX(2px); }
/* "Gallery is off" → first-class inline mode picker (Proofing / Static). */
.gallery-mode-picker { border: 1px solid var(--border); border-radius: var(--radius); padding: var(--sp-4); background: var(--bg-soft); }
.gallery-mode-options { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }
@media (max-width: 600px) { .gallery-mode-options { grid-template-columns: 1fr; } }
.gallery-mode-options form { margin: 0; }
.gallery-mode-option {
    display: flex; align-items: flex-start; gap: var(--sp-3); width: 100%;
    text-align: left; padding: var(--sp-3) var(--sp-4);
    background: var(--bg); border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
    cursor: pointer; transition: border-color .12s, box-shadow .12s, transform .12s;
}
.gallery-mode-option:hover { border-color: var(--brand-accent); box-shadow: var(--shadow-sm); transform: translateY(-1px); }
.gallery-mode-option > i { color: var(--brand-accent-strong); font-size: 1.1rem; margin-top: 2px; }
.gallery-mode-option span { display: flex; flex-direction: column; gap: 2px; font-size: .82rem; color: var(--text-light); line-height: 1.35; }
.gallery-mode-option span strong { font-size: .92rem; color: var(--text); font-weight: 600; }
/* Hero banner revealed live when the first photo finishes uploading. */
.gallery-cover-banner-pop { animation: galleryBannerPop .45s ease both; }
@keyframes galleryBannerPop {
    from { opacity: 0; transform: translateY(8px) scale(.985); }
    to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
    .gallery-cover-banner-pop { animation: none; }
}
/* Cover banner used as a tappable link (recipient project page). */
.gallery-cover-banner-link { display: block; cursor: pointer; }
.gallery-cover-banner-link img { transition: transform .25s ease; }
.gallery-cover-banner-link:hover img { transform: scale(1.02); }
/* Recipient project page: keep the public shell readable, cards stacked. */
.recipient-shell { max-width: 760px; }
.gallery-cover-banner img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center 30%;   /* bias toward faces near the top (overridden by an inline focal point) */
    display: block;
}
/* Taller hero so portraits / headshots aren't reduced to a sliver. */
.gallery-cover-banner-tall { height: 420px; }
@media (max-width: 700px) { .gallery-cover-banner-tall { height: 300px; } }
/* The hero doubles as the cover focal-point editor: drag to reposition. The
   saved focal point drives both the client gallery banner and the project-list
   thumbnail (one master framing). */
.gallery-hero-editor { margin: 0; }
.gallery-cover-banner.is-repositionable { cursor: grab; touch-action: none; user-select: none; }
.gallery-cover-banner.is-repositionable.is-dragging { cursor: grabbing; }
.gallery-cover-banner.is-repositionable img { transition: none; }
.gallery-cover-banner.is-dragging .gallery-cover-drag-hint { opacity: 0; }
/* "Saved" confirmation sits ON the hero (top-right pill). Must be absolute:
   the banner is overflow:hidden around a full-height image, so a normal-flow
   span would land below the image and get clipped (i.e. never appear). */
.gallery-hero-editor .gallery-cover-editor-status {
    position: absolute; top: 12px; right: 12px; z-index: 4;
    background: rgba(0,0,0,.6); color: #fff;
    padding: 6px 12px; border-radius: 999px; backdrop-filter: blur(4px);
}
.gallery-hero-editor .gallery-cover-editor-status i { color: #4ade80; }
/* Blurred-fill hero for tall/portrait covers: the image fills a capped-width
   crop window in the center (zoomed in, cover — NOT a full-banner sliver and
   NOT the whole shrunk image), with a blurred copy of itself filling the space
   on either side. Drag to reposition the vertical crop (also frames the
   project-list thumbnail). */
.gallery-cover-banner-blur { display: flex; align-items: center; justify-content: center; background: #fafafa; isolation: isolate; }
.gallery-cover-capped {
    position: relative; z-index: 1; height: 100%;
    aspect-ratio: 5 / 4; max-width: 92%;   /* landscape crop window (a touch wider than square), repositionable */
    overflow: hidden; border-radius: 0;    /* square corners — rounded looked odd inside the banner */
    box-shadow: 0 12px 40px rgba(0,0,0,.35), 0 4px 12px rgba(0,0,0,.2);
}
/* Zoom slider under the cover crop editor. */
.gallery-cover-zoomrow {
    display: flex; align-items: center; gap: 10px;
    max-width: 360px; margin: var(--sp-3) auto 0;
    color: var(--text-light);
}
.gallery-cover-zoomrow i { font-size: .78rem; flex: 0 0 auto; }
.gallery-cover-zoom {
    flex: 1 1 auto; height: 4px; cursor: pointer;
    accent-color: var(--brand-accent);
}

/* The card's primary action — given real presence so it reads as THE thing
   to do, not just another small button. */
.gallery-browse-cta {
    padding: 12px 22px;
    font-size: .95rem;
    font-weight: 650;
    border-radius: 10px;
    box-shadow: 0 6px 16px -6px rgba(0,0,0,.32);          /* fallback */
    transition: transform .12s ease, box-shadow .12s ease;
}
@supports (background: color-mix(in srgb, red, blue)) {
    .gallery-browse-cta { box-shadow: 0 8px 20px -7px color-mix(in srgb, var(--brand-accent) 55%, transparent); }
}
.gallery-browse-cta:hover {
    transform: translateY(-1px);
    box-shadow: 0 11px 24px -8px rgba(0,0,0,.38);
}
@supports (background: color-mix(in srgb, red, blue)) {
    .gallery-browse-cta:hover { box-shadow: 0 12px 26px -8px color-mix(in srgb, var(--brand-accent) 62%, transparent); }
}
.gallery-browse-cta i { font-size: 1.05em; }

.gallery-share-panel {
    background: var(--bg-soft);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: var(--sp-4);
}
.gallery-share-panel-divider {
    border: 0;
    border-top: 1px solid var(--border);
    margin: var(--sp-3) 0;
}

/* ── Rates Schedule visual anchor (in the WYSIWYG editor) ─────────────────
   Stands in for the literal {{rates_schedule}} token so the user never
   sees implementation detail. contenteditable=false on the JS side
   prevents accidental cursor-inside-the-block edits. */
.ag-rates-anchor {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    margin: var(--sp-4) 0;
    padding: var(--sp-3) var(--sp-5) var(--sp-3) var(--sp-4);
    border: 2px dashed var(--border-strong);
    border-radius: 8px;
    background: var(--bg-soft);
    color: var(--text-light);
    font-size: .85rem;
    user-select: none;
    cursor: grab;
}
.ag-rates-anchor:active { cursor: grabbing; }
.ag-rates-anchor.is-dragging { opacity: .5; }
.ag-rates-anchor i { color: var(--text-light); font-size: 1rem; }
.ag-rates-anchor-x {
    position: absolute;
    top: 50%;
    right: 8px;
    transform: translateY(-50%);
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--text-light);
    cursor: pointer;
    font-size: 1.15rem;
    line-height: 1;
    padding: 2px 6px;
    border-radius: 4px;
}
.ag-rates-anchor-x:hover { color: #b91c1c; background: #fff5f5; }

/* Button busy / submit-in-flight state (set by wireSubmitGuards in portal.js).
   Disabled + dimmed + cursor:wait. */
button.is-busy,
input[type="submit"].is-busy {
    opacity: .7;
    cursor: wait;
}

/* Messages inbox: unread thread treatment. Bold text + a small blue dot
   before the project title. Sorts to the top of the list (server-side). */
.data-table tr.is-thread-unread td { font-weight: 600; }
.thread-unread-dot {
    display: inline-block;
    width: 8px; height: 8px;
    background: var(--info, #2563eb);
    border-radius: 50%;
    margin-right: var(--sp-2);
    vertical-align: middle;
}

/* Rectangular variant of the avatar preview (used for studio-logo upload —
   logos aren't necessarily square). */
.avatar-preview-rect {
    width: 120px;
    height: 80px;
    border-radius: 6px;
    background: var(--bg-soft);
    border: 1px solid var(--border);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}
.avatar-preview-rect img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

/* Studio logo image inside the brand block (sidebar / topbar) — falls back
   to the wordmark text when no logo_path is set on the tenant. */
.brand-logo {
    max-width: 100%;
    max-height: 48px;
    height: auto;
    width: auto;
    display: block;
    margin-bottom: var(--sp-2);
    object-fit: contain;
}

/* ─────────────────────────────────────────────────────────────────────────
   Settings → Payments → Processor cards
   ───────────────────────────────────────────────────────────────────────── */
.processor-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--sp-4);
}
@media (max-width: 700px) {
    .processor-grid { grid-template-columns: 1fr; }
}
.processor-card {
    background: var(--bg-soft);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: var(--sp-4);
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    transition: border-color .15s;
}
.processor-card.is-connected {
    background: #fff;
    border-color: var(--success, #15803d);
    box-shadow: inset 3px 0 0 var(--success, #15803d);
}
.processor-card-head {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
}
.processor-card-icon {
    font-size: 1.75rem;
    width: 40px;
    height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
    background: #fff;
    border: 1px solid var(--border);
    color: var(--text);
}
.processor-card-name {
    font-weight: 600;
    font-size: 1rem;
}
.processor-card-sub { margin-top: 1px; }
.processor-card-head .badge { margin-left: auto; }

/* ─────────────────────────────────────────────────────────────────────────
   Client notification preferences — two-column toggle grid
   ───────────────────────────────────────────────────────────────────────── */
.notif-prefs-group { margin-bottom: var(--sp-6); }
.notif-prefs-group:last-of-type { margin-bottom: 0; }
/* When a notif group is also a form-section, the section's padding + divider
   handle spacing — drop the legacy margin so they don't double up. */
.form-section.notif-prefs-group { margin-bottom: 0; }
.notif-prefs-table {
    width: 100%;
    border-collapse: collapse;
    border: 1px solid var(--border);
    border-radius: 10px;
    overflow: hidden;
    background: #fff;
}
.notif-prefs-table thead th {
    background: var(--bg-soft);
    color: var(--text-muted);
    font-weight: 600;
    font-size: .75rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    padding: var(--sp-3);
    text-align: left;
}
.notif-prefs-table thead th.notif-prefs-col-toggle { text-align: center; width: 90px; }
.notif-prefs-table tbody td {
    border-top: 1px solid var(--border);
    padding: var(--sp-3);
    vertical-align: middle;
}
.notif-prefs-table tbody td.notif-prefs-col-toggle { text-align: center; }
.notif-prefs-label { font-weight: 600; display: inline-flex; align-items: center; gap: var(--sp-2); }
.notif-prefs-desc  { margin-top: 4px; }
.notif-prefs-locked { font-size: .65rem; letter-spacing: .04em; text-transform: uppercase; }
.notif-prefs-checkbox input[type="checkbox"] {
    width: 18px; height: 18px;
    accent-color: var(--brand-accent);
    cursor: pointer;
}
.notif-prefs-checkbox input[type="checkbox"]:disabled { cursor: not-allowed; opacity: .5; }

/* Sidebar nav unread count badge + per-row "New" pill. The badge color
   matches the topbar notification bell (--danger) for visual
   consistency — same signal across the chrome. */
.nav-unread-badge {
    margin-left: auto;          /* push to the far right of the .portal-sidenav-link flex row */
    background: var(--danger);
    color: #fff;
    font-size: .7rem;
    font-weight: 700;
    line-height: 1;
    padding: 3px 7px;
    border-radius: 10px;
    min-width: 18px;
    text-align: center;
}
/* Red-dot indicator on a nav item that has unread bell notifications. */
.nav-alert-dot {
    margin-left: auto;          /* sit at the far right of the nav row */
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--danger);
}
.pill-new {
    display: inline-block;
    background: var(--brand-accent, #ff6b35);
    color: #fff;
    font-size: .6rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    padding: 2px 6px;
    border-radius: 8px;
    vertical-align: 2px;
    margin-left: 6px;
}

/* Receipt print mode — hide sidebar/topbar chrome and the .no-print
   inline header so what prints is just the white receipt card. */
@media print {
    .portal-shell { display: block; }
    .portal-sidebar, .portal-topbar, .topbar, .client-sidebar { display: none !important; }
    .no-print { display: none !important; }
    .printable-receipt { box-shadow: none !important; border: 0 !important; }
    body { background: #fff !important; }
}

/* ─────────────────────────────────────────────────────────────────────────
   Breadcrumb trail — used at the top of deep detail pages
   (agreement detail, client detail, template edit, etc.)
   ───────────────────────────────────────────────────────────────────────── */
.breadcrumbs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    font-size: .85rem;
    color: var(--text-muted);
    margin-bottom: var(--sp-2);
}
.breadcrumb-link {
    color: var(--text-muted);
    text-decoration: none;
    transition: color .15s;
}
.breadcrumb-link:hover { color: var(--text); text-decoration: underline; text-underline-offset: 2px; }
.breadcrumb-sep { color: var(--text-light); user-select: none; }
.breadcrumb-current { color: var(--text); font-weight: 500; }
.breadcrumb-back {
    display: inline-flex;
    align-items: center;
    color: var(--text-light);
    text-decoration: none;
    font-size: .8rem;
    transition: color .15s;
}
.breadcrumb-back:hover { color: var(--text); }

/* Inline ?-icon field help — emits a tooltip on hover + a native
   title attribute as fallback for keyboard-only users. */
.field-help {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    margin-left: 4px;
    padding: 0;
    background: transparent;
    border: 0;
    color: var(--text-light);
    cursor: help;
    font-size: .8rem;
    vertical-align: middle;
    border-radius: 50%;
    position: relative;
}
.field-help:hover, .field-help:focus { color: var(--brand); outline: none; }
.field-help[data-tooltip]:hover::after,
.field-help[data-tooltip]:focus::after {
    content: attr(data-tooltip);
    position: absolute;
    bottom: calc(100% + 8px);
    left: 50%;
    transform: translateX(-50%);
    background: var(--brand);
    color: #fff;
    padding: 6px 10px;
    border-radius: 6px;
    font-size: .75rem;
    font-weight: 400;
    line-height: 1.4;
    white-space: nowrap;
    max-width: 280px;
    white-space: normal;
    width: max-content;
    text-align: left;
    z-index: 100;
    pointer-events: none;
}
.field-help[data-tooltip]:hover::before,
.field-help[data-tooltip]:focus::before {
    content: '';
    position: absolute;
    bottom: calc(100% + 2px);
    left: 50%;
    transform: translateX(-50%);
    border: 4px solid transparent;
    border-top-color: var(--brand);
    z-index: 100;
    pointer-events: none;
}

/* ─────────────────────────────────────────────────────────────────────────
   Shortcuts cheatsheet modal — opened by `?`
   ───────────────────────────────────────────────────────────────────────── */
dialog.shortcuts-modal {
    border: 0;
    padding: 0;
    border-radius: 14px;
    background: #fff;
    max-width: 600px;
    width: calc(100% - 32px);
    max-height: 80vh;
    box-shadow: 0 24px 60px rgba(10, 10, 10, 0.22), 0 4px 12px rgba(10, 10, 10, 0.08);
    color: var(--text);
    font-family: inherit;
}
dialog.shortcuts-modal::backdrop {
    background: rgba(10, 10, 10, 0.45);
    backdrop-filter: blur(2px);
}
.shortcuts-modal-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: var(--sp-4) var(--sp-5);
    border-bottom: 1px solid var(--border);
}
.shortcuts-modal-head h2 {
    font-size: 1.05rem;
    font-weight: 600;
    margin: 0;
}
.shortcuts-modal-close {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    font-size: 1rem;
    cursor: pointer;
    width: 32px;
    height: 32px;
    border-radius: 8px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.shortcuts-modal-close:hover { background: var(--bg-soft); color: var(--text); }
.shortcuts-modal-body {
    padding: var(--sp-5);
    overflow-y: auto;
    max-height: calc(80vh - 64px);
}
.shortcuts-group { margin-bottom: var(--sp-5); }
.shortcuts-group:last-child { margin-bottom: 0; }
.shortcuts-group-title {
    font-size: .7rem;
    font-weight: 700;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: .08em;
    margin: 0 0 var(--sp-3);
}
.shortcuts-list { margin: 0; }
.shortcuts-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 8px 0;
    border-bottom: 1px solid var(--border);
}
.shortcuts-row:last-child { border-bottom: 0; }
.shortcuts-label {
    margin: 0;
    font-size: .9rem;
    color: var(--text);
}
.shortcuts-keys {
    margin: 0;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: .8rem;
}
.shortcuts-keys kbd {
    display: inline-block;
    background: var(--bg-soft);
    border: 1px solid var(--border);
    border-bottom-width: 2px;
    border-radius: 5px;
    padding: 2px 8px;
    font-family: 'SF Mono', Menlo, monospace;
    font-size: .75rem;
    font-weight: 500;
    color: var(--text);
    min-width: 22px;
    text-align: center;
}
.shortcuts-keys-sep { color: var(--text-light); font-size: .7rem; }

/* ─────────────────────────────────────────────────────────────────────────
   Kebab menus — secondary actions on table rows
   ───────────────────────────────────────────────────────────────────────── */
.kebab {
    position: relative;
    display: inline-block;
}
.kebab-btn {
    appearance: none;
    background: transparent;
    border: 0;
    cursor: pointer;
    width: 32px;
    height: 32px;
    border-radius: 6px;
    color: var(--text-muted);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: .95rem;
    transition: background .12s, color .12s;
}
.kebab-btn:hover { background: var(--bg-soft); color: var(--text); }
.kebab-btn[aria-expanded="true"] { background: var(--bg-soft); color: var(--text); }
.kebab-menu {
    /* Default rendering — only ever visible briefly before kebab-menu.js
       portals it to <body>. JS sets explicit position/top/left there. */
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    min-width: 180px;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 12px 28px rgba(10,10,10,.12), 0 2px 6px rgba(10,10,10,.06);
    padding: 6px;
    z-index: 1200;
    text-align: left;
}
/* When portaled to <body>, the menu loses its parent context for
   width (was naturally constrained inside the .kebab inline-block).
   width:max-content makes it shrink-wrap to the widest menu item +
   max-width caps it at a sensible 240px so a long action label
   doesn't blow out the layout. z-index needs to beat the topbar
   (1100) and any drawer backdrops (1000). */
.kebab-menu-portaled {
    width: max-content;
    max-width: 240px;
    z-index: 1200;
}
.kebab-menu a,
.kebab-menu button {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px 10px;
    border-radius: 6px;
    background: transparent;
    border: 0;
    color: var(--text);
    font-size: .88rem;
    font-weight: 400;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    font-family: inherit;
}
.kebab-menu a:hover,
.kebab-menu button:hover { background: var(--bg-soft); }
.kebab-menu a i,
.kebab-menu button i { color: var(--text-muted); font-size: .85rem; width: 14px; }
.kebab-menu .kebab-item-danger { color: var(--danger); }
.kebab-menu .kebab-item-danger i { color: var(--danger); }
.kebab-menu .kebab-item-danger:hover { background: #fef2f2; }
.kebab-divider {
    border: 0;
    border-top: 1px solid var(--border);
    margin: 4px 0;
}
.kebab-menu form { margin: 0; }

/* Tiny inline kbd chip for the account-dropdown shortcuts hint. */
.topbar-menu-kbd {
    margin-left: auto;
    background: var(--bg-soft);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 1px 6px;
    font-family: 'SF Mono', Menlo, monospace;
    font-size: .7rem;
    color: var(--text-muted);
}

/* ─────────────────────────────────────────────────────────────────────────
   Settings sub-nav — horizontal tab bar at the top of every /portal/
   settings/ page. Same pattern as Stripe / Linear settings.
   ───────────────────────────────────────────────────────────────────────── */
.settings-subnav {
    display: flex;
    gap: 4px;
    /* Spread the tabs across the full width (justified) instead of bunching
       them at the left. On narrow screens they overflow + scroll as before. */
    justify-content: space-between;
    padding-bottom: var(--sp-3);
    margin-bottom: var(--sp-5);
    border-bottom: 1px solid var(--border);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
@media (max-width: 720px) {
    /* When the tabs overflow and scroll, justified spacing fights the scroll —
       fall back to natural left-aligned tabs. */
    .settings-subnav { justify-content: flex-start; }
}
.settings-subnav-item {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    border-radius: 8px 8px 0 0;
    font-size: .9rem;
    font-weight: 500;
    color: var(--text-muted);
    text-decoration: none;
    white-space: nowrap;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;  /* sit on top of the parent border so active overlays it */
    transition: color .15s, background .15s, border-color .15s;
}
.settings-subnav-item:hover { color: var(--text); background: var(--bg-soft); border-bottom-color: var(--brand-accent); }
.settings-subnav-item.is-active {
    color: var(--brand-accent-strong);
    border-bottom-color: var(--brand-accent);
    background: transparent;
}
.settings-subnav-item.is-soon {
    color: var(--text-light);
    cursor: not-allowed;
    opacity: .65;
}
.settings-subnav-item.is-soon:hover { background: transparent; color: var(--text-light); border-bottom-color: transparent; }
.settings-subnav-soon {
    font-size: .65rem;
    font-style: normal;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--text-light);
    background: var(--bg-soft);
    padding: 2px 6px;
    border-radius: 4px;
    margin-left: 4px;
}

/* ─────────────────────────────────────────────────────────────────────────
   Custom confirm modal — replaces native window.confirm()
   ───────────────────────────────────────────────────────────────────────── */
dialog.confirm-modal {
    border: 0;
    padding: 0;
    border-radius: 12px;
    background: #fff;
    max-width: 440px;
    width: calc(100% - 32px);
    box-shadow: 0 20px 50px rgba(10,10,10,.22), 0 4px 12px rgba(10,10,10,.08);
    color: var(--text);
    font-family: inherit;
}
dialog.confirm-modal::backdrop {
    background: rgba(10, 10, 10, 0.45);
    backdrop-filter: blur(2px);
}
.confirm-modal-body { padding: var(--sp-5) var(--sp-5) var(--sp-3); }
.confirm-modal-title {
    font-size: 1.1rem;
    font-weight: 600;
    margin: 0 0 var(--sp-2);
    letter-spacing: -.01em;
}
.confirm-modal-text {
    margin: 0;
    color: var(--text-muted);
    font-size: .92rem;
    line-height: 1.5;
}
.confirm-modal-text:empty { display: none; }
.confirm-modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-5) var(--sp-5);
}

/* Danger-tier CTA button — red bg, white text, hover deepens.
   Distinct from .btn-danger (ghost variant) used elsewhere. */
.btn-danger-strong {
    background: var(--danger);
    color: #fff;
    border-color: var(--danger);
}
.btn-danger-strong:hover { background: #7f1d1d; border-color: #7f1d1d; }

/* ─────────────────────────────────────────────────────────────────────────
   Toast notifications — slide-in from bottom-right
   ───────────────────────────────────────────────────────────────────────── */
.toast-host {
    position: fixed;
    bottom: 24px;
    right: 24px;
    z-index: 1100;
    display: flex;
    flex-direction: column;
    gap: 12px;
    pointer-events: none;
    max-width: calc(100vw - 48px);
}
.toast {
    pointer-events: auto;
    display: flex;
    align-items: flex-start;
    gap: 10px;
    background: #fff;
    color: var(--text);
    border: 1px solid var(--border);
    border-left: 4px solid var(--success);
    border-radius: 10px;
    box-shadow: 0 10px 28px rgba(10, 10, 10, 0.12), 0 2px 6px rgba(10, 10, 10, 0.05);
    padding: 12px 14px;
    min-width: 280px;
    max-width: 380px;
    transform: translateX(120%);
    opacity: 0;
    transition: transform .22s cubic-bezier(.21,1.02,.73,1), opacity .22s ease;
}
.toast.is-shown { transform: translateX(0); opacity: 1; }
.toast.toast-error   { border-left-color: var(--danger); }
.toast.toast-info    { border-left-color: var(--info, #2563eb); }
.toast-icon {
    font-size: 1rem;
    color: var(--success);
    margin-top: 2px;
    flex-shrink: 0;
}
.toast-error .toast-icon { color: var(--danger); }
.toast-info  .toast-icon { color: var(--info, #2563eb); }
.toast-message { flex: 1; font-size: .9rem; line-height: 1.4; }
.toast-close {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    cursor: pointer;
    padding: 2px 4px;
    border-radius: 4px;
    font-size: .85rem;
    flex-shrink: 0;
}
.toast-close:hover { color: var(--text); background: var(--bg-soft); }
@media (max-width: 600px) {
    .toast-host { left: 16px; right: 16px; bottom: 16px; }
    .toast { max-width: none; min-width: 0; }
}

/* Auto-dismiss + manual-close on status banners (success only). */
.status-banner.is-fading {
    opacity: 0;
    transform: translateY(-4px);
    transition: opacity .25s ease, transform .25s ease;
}
.status-banner .banner-close {
    appearance: none;
    background: transparent;
    border: 0;
    color: inherit;
    opacity: .55;
    cursor: pointer;
    margin-left: auto;
    padding: 2px 6px;
    font-size: .9rem;
    border-radius: 4px;
    align-self: flex-start;
}
.status-banner .banner-close:hover { opacity: 1; }
.status-banner-success { display: flex; align-items: flex-start; gap: 12px; }
/* Dismissible admin (RAW) flash — lay out for the close button on both variants. */
[data-admin-flash] { display: flex; align-items: flex-start; gap: 12px; }

/* ─────────────────────────────────────────────────────────────────────────
   Empty states — used inside .data-table-wrap when there are no rows.
   Richer than plain text — icon + title + sub + optional CTA.
   ───────────────────────────────────────────────────────────────────────── */
.empty-state {
    padding: 56px 24px;
    text-align: center;
    color: var(--text-muted);
}
.empty-state-icon {
    width: 56px;
    height: 56px;
    border-radius: 14px;
    background: var(--bg-soft);
    color: var(--text-muted);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1.5rem;
    margin: 0 auto var(--sp-3);
}
.empty-state-title {
    font-size: 1rem;
    font-weight: 600;
    color: var(--text);
    margin: 0 0 6px;
}
.empty-state-desc {
    font-size: .9rem;
    max-width: 420px;
    margin: 0 auto var(--sp-4);
    line-height: 1.5;
}
.empty-state-cta { margin-top: 0; }

/* Centered success confirmation (e.g. after inviting a client to the portal). */
.confirmation-card {
    padding: 48px 24px;
    text-align: center;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 14px;
}
.confirmation-card-icon {
    width: 56px; height: 56px; border-radius: 50%;
    background: #e9f7ef; color: #1f9d57;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 1.6rem; margin: 0 auto 16px;
}
.confirmation-card-title { font-size: 1.15rem; font-weight: 700; color: var(--text); margin: 0 0 8px; }
.confirmation-card-body { font-size: .92rem; color: var(--text-med); max-width: 520px; margin: 0 auto 20px; line-height: 1.55; }

/* ─────────────────────────────────────────────────────────────────────────
   Invoice payment drawer — right-side slide-out
   Premium-SaaS pattern (HoneyBook, Linear billing, Stripe Dashboard).
   Slides in from the right with a dimmed backdrop over the invoice.
   ───────────────────────────────────────────────────────────────────────── */
.pay-drawer-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(10, 10, 10, 0.45);
    z-index: 1000;
    opacity: 0;
    transition: opacity .22s ease;
    pointer-events: none;
}
body.has-pay-drawer-open .pay-drawer-backdrop {
    opacity: 1;
    pointer-events: auto;
}
.pay-drawer {
    position: fixed;
    top: 0;
    right: 0;
    height: 100vh;
    width: 480px;
    max-width: 100vw;
    background: #fff;
    box-shadow: -16px 0 48px rgba(10, 10, 10, 0.16);
    z-index: 1001;
    display: flex;
    flex-direction: column;
    transform: translateX(100%);
    transition: transform .22s ease;
}
body.has-pay-drawer-open .pay-drawer {
    transform: translateX(0);
}
body.has-pay-drawer-open {
    /* Lock body scroll so only the drawer scrolls. */
    overflow: hidden;
}
.pay-drawer-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--sp-3);
    padding: var(--sp-5);
    border-bottom: 1px solid var(--border);
}
.pay-drawer-title {
    font-size: 1.15rem;
    font-weight: 600;
    margin: 0 0 4px;
    letter-spacing: -0.01em;
}
.pay-drawer-close {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    font-size: 1.25rem;
    cursor: pointer;
    width: 32px;
    height: 32px;
    border-radius: 8px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.pay-drawer-close:hover { background: var(--bg-soft); color: var(--text); }
.pay-drawer-body {
    padding: var(--sp-5);
    overflow-y: auto;
    flex: 1;
}
.pay-summary-note {
    padding: var(--sp-3);
    background: var(--bg-soft);
    border-radius: 8px;
}

@media (max-width: 600px) {
    .pay-drawer { width: 100vw; }
}

/* ─────────────────────────────────────────────────────────────────────────
   Bulk invoice pay: multi-select column + sticky "Pay selected" bar on the
   client invoices list, and the two-pane batch pay page.
   ───────────────────────────────────────────────────────────────────────── */
.bulk-pay-check-col { width: 40px; text-align: center; }
.bulk-pay-check {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    cursor: pointer;
}
.bulk-pay-check input { width: 18px; height: 18px; cursor: pointer; }

.bulk-pay-bar {
    margin-bottom: var(--sp-4);
}
.bulk-pay-bar-inner {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    align-items: stretch;
    padding: var(--sp-3) var(--sp-4);
    background: var(--surface, #fff);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.bulk-pay-bar-summary { font-size: var(--fs-sm); }
.bulk-pay-bar-actions {
    display: flex;
    gap: var(--sp-2);
    justify-content: flex-end;
}

/* Receipt itemization: expandable "Invoices paid" rows on in-app receipts
   (single + batch). Native <details>; caret rotates when open. */
.receipt-invoices { margin-bottom: var(--sp-5); }
.receipt-inv {
    border: 1px solid var(--border);
    border-radius: 10px;
    margin-bottom: var(--sp-2);
    overflow: hidden;
}
.receipt-inv-head {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    min-height: 44px;
    cursor: pointer;
    background: var(--bg-soft);
    list-style: none;
}
.receipt-inv-head::-webkit-details-marker { display: none; }
.receipt-inv-head::marker { content: ''; }
.receipt-inv-caret { display: inline-flex; color: var(--text-muted); transition: transform .15s ease; }
.receipt-inv[open] .receipt-inv-caret { transform: rotate(90deg); }
.receipt-inv-num { font-weight: 600; }
.receipt-inv-desc { flex: 1; font-size: var(--fs-sm); }
.receipt-inv-amt { margin-left: auto; font-weight: 600; }
.receipt-inv-body { padding: var(--sp-3) var(--sp-4) var(--sp-4); }
.receipt-inv-lines { margin-bottom: var(--sp-3); }
.receipt-inv-totals {
    display: grid;
    grid-template-columns: 1fr max-content;
    row-gap: var(--sp-1);
    column-gap: var(--sp-4);
    max-width: 320px;
    margin-left: auto;
    font-size: var(--fs-sm);
}
.receipt-inv-total-row { display: contents; }
.receipt-inv-total-row > span:first-child { color: var(--text-muted); }
.receipt-inv-total-row > span:last-child { text-align: right; }
.receipt-inv-total-grand > span {
    font-weight: 700;
    color: var(--text);
    padding-top: var(--sp-2);
    border-top: 1px solid var(--border);
}

/* Batch pay page: invoices list + card form side by side on wide screens. */
.pay-batch-grid { display: grid; gap: var(--sp-5); grid-template-columns: 1fr; }
.pay-batch-pane {
    padding: var(--sp-5);
    background: var(--bg-soft);
    border: 1px solid var(--border);
    border-radius: 12px;
}

@media (min-width: 600px) {
    .bulk-pay-bar-inner { flex-direction: row; align-items: center; justify-content: space-between; }
}
@media (min-width: 900px) {
    .pay-batch-grid { grid-template-columns: 1.4fr 1fr; align-items: start; }
}

/* ─────────────────────────────────────────────────────────────────────────
   Client invoice → Square embedded payment form
   The Web Payments SDK iframes the card field into #sq-card; we style
   the surrounding shell, not the iframe itself (Square owns those styles).
   ───────────────────────────────────────────────────────────────────────── */
.sq-pay-shell {
    max-width: 480px;
    margin: 0 auto;
}
.sq-tabs {
    display: flex;
    gap: var(--sp-2);
    margin-bottom: var(--sp-4);
    border-bottom: 1px solid var(--border);
}
.sq-tab {
    appearance: none;
    background: transparent;
    border: 0;
    padding: var(--sp-2) var(--sp-3);
    font: inherit;
    color: var(--text-muted);
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    transition: color .15s, border-color .15s;
}
.sq-tab:hover { color: var(--text); }
.sq-tab.is-active {
    color: var(--text);
    border-bottom-color: var(--primary, #111);
    font-weight: 600;
}
.sq-tab i { margin-right: 6px; }
.sq-mount {
    min-height: 90px;
    padding: var(--sp-2) 0;
}
.sq-breakdown {
    border-top: 1px solid var(--border);
    margin-top: var(--sp-4);
    padding-top: var(--sp-3);
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.sq-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    font-size: .9rem;
}
.sq-row-total {
    padding-top: var(--sp-2);
    border-top: 1px dashed var(--border);
    margin-top: 4px;
    font-weight: 600;
    font-size: 1.05rem;
}
.sq-status {
    margin-top: var(--sp-3);
    padding: var(--sp-3);
    border-radius: 8px;
    font-size: .9rem;
    background: var(--bg-soft);
    border: 1px solid var(--border);
}
.sq-status[data-kind="error"] {
    background: #fef2f2;
    border-color: #fecaca;
    color: #991b1b;
}
.sq-status[data-kind="success"] {
    background: #f0fdf4;
    border-color: #bbf7d0;
    color: #166534;
}
/* Stripe pay-drawer status (class-based; hidden when empty). */
.stripe-status {
    margin-top: var(--sp-3);
    padding: var(--sp-3);
    border-radius: 8px;
    font-size: .9rem;
    background: var(--bg-soft);
    border: 1px solid var(--border);
}
.stripe-status:empty { display: none; }
.stripe-status-error   { background: #fef2f2; border-color: #fecaca; color: #991b1b; }
.stripe-status-success { background: #f0fdf4; border-color: #bbf7d0; color: #166534; }

/* ── Gallery admin ──────────────────────────────────────────────────── */
.gallery-dropzone {
    border: 2px dashed var(--border);
    border-radius: 14px;
    background: var(--bg-soft);
    padding: var(--sp-7) var(--sp-5);
    text-align: center;
    cursor: pointer;
    transition: border-color .15s ease, background .15s ease, transform .15s ease;
    margin-bottom: var(--sp-5);
}
.gallery-dropzone:hover,
.gallery-dropzone:focus-visible {
    border-color: var(--ink);
    background: #fff;
    outline: none;
}
.gallery-dropzone.is-dragover {
    border-color: var(--ink);
    background: #fff;
    transform: scale(1.005);
    box-shadow: 0 8px 24px -10px rgba(0,0,0,.15);
}
.gallery-dropzone-icon {
    font-size: 2.5rem;
    color: var(--muted);
    margin-bottom: var(--sp-2);
    display: block;
}
.gallery-dropzone-title {
    font-size: 1.1rem;
    font-weight: 600;
    margin: 0 0 var(--sp-1);
}
.gallery-dropzone-sub {
    font-size: .9rem;
    color: var(--muted);
    margin: 0;
}
.link-button {
    background: none; border: 0; padding: 0;
    color: var(--ink); text-decoration: underline; cursor: pointer;
    font: inherit;
}

.gallery-queue {
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: var(--sp-4);
    margin-bottom: var(--sp-5);
}
.gallery-queue-head {
    display: flex; justify-content: space-between; align-items: baseline;
    margin-bottom: var(--sp-3);
}
.gallery-queue-title { font-weight: 600; }
.gallery-queue-progress { color: var(--muted); font-variant-numeric: tabular-nums; font-size: .9rem; }
.gallery-queue-list {
    list-style: none; padding: 0; margin: 0;
    display: flex; flex-direction: column; gap: var(--sp-2);
    max-height: 360px;
    overflow-y: auto;
}
/* Collapse completed rows during large batches so the queue stays scannable
 * (300 visible rows would push the grid offscreen). Failures stay expanded. */
.gallery-queue-item.is-collapsed {
    grid-template-columns: 1fr;
    grid-template-rows: auto;
    padding: 4px 0;
    border-top: 1px dashed var(--border);
    margin-top: -4px;
    font-size: .8rem;
    color: #16a34a;
}
.gallery-queue-item.is-collapsed .gallery-queue-bar,
.gallery-queue-item.is-collapsed .gallery-queue-status { display: none; }
.gallery-queue-item.is-collapsed .gallery-queue-name::before {
    content: '\f00c';
    font-family: 'Font Awesome 6 Free'; font-weight: 900;
    margin-right: 6px;
    color: #16a34a;
}
.gallery-queue-item {
    display: grid;
    grid-template-columns: 1fr 200px 60px;
    align-items: center;
    gap: var(--sp-3);
    font-size: .9rem;
}
.gallery-queue-name {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gallery-queue-bar {
    height: 6px; background: var(--bg-soft); border-radius: 999px; overflow: hidden;
}
.gallery-queue-fill {
    display: block; height: 100%;
    background: var(--ink);
    transition: width .15s ease;
}
.gallery-queue-status {
    text-align: right; color: var(--muted); font-variant-numeric: tabular-nums; font-size: .85rem;
}
.gallery-queue-item.is-done .gallery-queue-fill { background: #16a34a; }
.gallery-queue-item.is-done .gallery-queue-status { color: #16a34a; }
.gallery-queue-item.is-failed .gallery-queue-fill { background: #dc2626; }
.gallery-queue-item.is-failed .gallery-queue-status { color: #dc2626; }

.gallery-bulkbar {
    position: sticky;
    top: 1rem;
    z-index: 50;
    display: flex;
    justify-content: space-between;
    align-items: center;
    background: var(--ink);
    color: #fff;
    border-radius: 12px;
    padding: var(--sp-2) var(--sp-4);
    margin-bottom: var(--sp-4);
    box-shadow: 0 6px 18px -8px rgba(0,0,0,.35);
}
.gallery-bulkbar[hidden] { display: none; }   /* author display:flex would otherwise beat [hidden] */
.gallery-bulkbar-count { font-weight: 600; font-size: .9rem; }
.gallery-bulkbar-actions { display: flex; gap: var(--sp-3); align-items: center; }
.gallery-bulkbar .btn-ghost { color: #fff; }
.gallery-bulkbar .btn-ghost:hover { background: rgba(255,255,255,.1); }
.gallery-bulkbar-link {
    color: #fff;
    text-decoration: underline;
    text-underline-offset: 3px;
    font-size: .85rem;
}
.gallery-bulkbar-link:hover { color: #fff; opacity: .8; }

/* Toolbar above the grid (Select all + shortcut hint + thumb size slider) */
.gallery-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    padding: 0 2px var(--sp-3);
    border-bottom: 1px solid var(--border);
    margin-bottom: var(--sp-3);
    flex-wrap: wrap;
}
.gallery-toolbar-left {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
}
.gallery-toolbar .link-button { font-size: .9rem; font-weight: 500; }

/* Thumbnail size slider — Lightroom-style. Small image icon on the
 * left, large on the right, slider in between. Range 160-500px row
 * height. Preference persists in localStorage. */
.gallery-thumb-size {
    display: flex;
    align-items: center;
    gap: 10px;
    color: var(--muted);
}
.gallery-thumb-size-icon-sm { font-size: .65rem; }
.gallery-thumb-size-icon-lg { font-size: 1rem; }
.gallery-thumb-size-slider {
    -webkit-appearance: none;
    appearance: none;
    width: 140px;
    height: 4px;
    background: var(--border);
    border-radius: 999px;
    outline: none;
    cursor: pointer;
    margin: 0;
}
.gallery-thumb-size-slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 16px; height: 16px;
    border-radius: 50%;
    background: var(--ink);
    cursor: grab;
    border: 2px solid #fff;
    box-shadow: 0 1px 3px rgba(0,0,0,.3);
    transition: transform .1s ease;
}
.gallery-thumb-size-slider::-webkit-slider-thumb:hover { transform: scale(1.15); }
.gallery-thumb-size-slider::-webkit-slider-thumb:active { cursor: grabbing; transform: scale(1.25); }
.gallery-thumb-size-slider::-moz-range-thumb {
    width: 16px; height: 16px;
    border-radius: 50%;
    background: var(--ink);
    cursor: grab;
    border: 2px solid #fff;
    box-shadow: 0 1px 3px rgba(0,0,0,.3);
    transition: transform .1s ease;
}
.gallery-thumb-size-slider::-moz-range-thumb:hover { transform: scale(1.15); }
.gallery-thumb-size-slider::-moz-range-thumb:active { cursor: grabbing; transform: scale(1.25); }
@media (max-width: 600px) {
    .gallery-thumb-size { display: none; }
}
.kbd-inline {
    display: inline-block;
    padding: 1px 6px;
    border: 1px solid var(--border);
    border-bottom-width: 2px;
    border-radius: 4px;
    font: 600 .72rem / 1 ui-monospace, SFMono-Regular, Menlo, monospace;
    background: var(--bg-soft);
    color: var(--ink);
    vertical-align: 1px;
}
@media (max-width: 600px) {
    .gallery-toolbar [data-shortcut-hint],
    .gallery-toolbar .kbd-inline { display: none; }
}

/* Justified-rows gallery (Flickr / Google Photos / Unsplash pattern).
 *
 * Every row has a uniform height (target ~200px, allowed to scale ±20%
 * so the row exactly fills the container). Tile widths flex per image's
 * natural aspect ratio. Images are NEVER cropped — the cell IS the image's
 * aspect ratio because we set width = rowHeight × aspectRatio in JS.
 *
 * Width + height are set inline on each tile by layoutJustified() in
 * gallery-admin.js. Uses flex-wrap so the browser handles row breaks
 * naturally once widths are pre-computed. */
.gallery-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-content: flex-start;
}
.gallery-empty { margin-bottom: var(--sp-5); }
.gallery-tile {
    position: relative;
    display: flex;
    flex-direction: column;
    flex: 0 0 auto;       /* width is JS-set; do not flex */
    border-radius: 10px;
    overflow: hidden;
    background: #fff;
    border: 1px solid var(--border);
    cursor: pointer;   /* click opens the lightbox; gallery is filename-ordered (no drag-reorder) */
    transition: box-shadow .12s ease;
}
.gallery-tile:hover { box-shadow: 0 6px 16px -8px rgba(0,0,0,.18); }
.gallery-tile.is-dragging { opacity: .35; cursor: grabbing; }
.gallery-tile.is-drop-target { outline: 2px solid var(--ink); outline-offset: -2px; }
.gallery-tile.is-cover { border-color: var(--ink); box-shadow: 0 0 0 1px var(--ink) inset; }
.gallery-tile.is-selected { border-color: #2563eb; box-shadow: 0 0 0 1px #2563eb inset; }

/* The image well — fills the JS-computed width × height exactly. Image
 * inside fills the cell with object-fit:fill (safe because cell aspect
 * was computed to match image aspect — no distortion). */
.gallery-tile-imgwrap {
    position: relative;
    width: 100%;
    flex: 1 1 auto;
    background: #fafafa;
    overflow: hidden;
}
.gallery-tile-img {
    width: 100%;
    height: 100%;
    display: block;
    pointer-events: none;
    /* cover fills the cell edge-to-edge. The justified layout sizes each
     * cell to the image's aspect, so any mismatch is sub-pixel — cover
     * absorbs it as an invisible hairline crop rather than showing a
     * letterbox strip against the well (which read as "cheap"). The full,
     * uncropped image is always available in the lightbox. */
    object-fit: cover;
}

/* Always-visible filename caption strip under the image. Truncates with
 * ellipsis; full filename in the tooltip. Lightroom-style. */
.gallery-tile-caption {
    padding: 7px 10px;
    font-size: .78rem;
    line-height: 1.3;
    color: var(--ink);
    background: #fff;
    border-top: 1px solid var(--border);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    cursor: text;   /* click to rename inline */
    transition: background .12s ease;
}
.gallery-tile-caption:hover { background: var(--bg-muted); }
.gallery-tile-rename-input {
    width: 100%; box-sizing: border-box; font: inherit; font-size: .78rem;
    padding: 2px 6px; margin: 0; border: 1px solid var(--brand-accent-strong);
    border-radius: 5px; background: var(--bg); color: var(--text);
}
.gallery-tile-caption.is-untitled { color: var(--muted); font-style: italic; }
.gallery-tile-checkbox {
    position: absolute; top: 8px; left: 8px;
    width: 22px; height: 22px;
    cursor: pointer;
    opacity: 0;
    transition: opacity .12s ease;
}
.gallery-tile:hover .gallery-tile-checkbox,
.gallery-tile.is-selected .gallery-tile-checkbox,
.gallery-tile-checkbox input:focus-visible + span { opacity: 1; }
.gallery-tile-checkbox input { position: absolute; opacity: 0; pointer-events: none; }
.gallery-tile-checkbox span {
    display: block; width: 22px; height: 22px;
    border-radius: 6px;
    background: rgba(255,255,255,.95);
    border: 1.5px solid rgba(0,0,0,.25);
    box-shadow: 0 2px 4px rgba(0,0,0,.2);
}
.gallery-tile-checkbox input:checked + span {
    background: #2563eb; border-color: #2563eb;
}
.gallery-tile-checkbox input:checked + span::after {
    content: '\f00c';
    font-family: 'Font Awesome 6 Free'; font-weight: 900;
    color: #fff; font-size: 12px;
    display: flex; align-items: center; justify-content: center;
    height: 100%;
}
.gallery-tile-cover-badge {
    position: absolute; top: 8px; right: 8px;
    background: var(--ink); color: #fff;
    font-size: .7rem; font-weight: 600;
    padding: 3px 8px; border-radius: 999px;
    display: inline-flex; align-items: center; gap: 4px;
}
.gallery-tile-actions {
    position: absolute; bottom: 8px; right: 8px;
    display: flex; gap: 6px;
    opacity: 0;
    transition: opacity .12s ease;
}
.gallery-tile:hover .gallery-tile-actions { opacity: 1; }
.gallery-tile-action {
    width: 30px; height: 30px;
    display: inline-flex; align-items: center; justify-content: center;
    background: rgba(255,255,255,.95);
    border: 1px solid rgba(0,0,0,.1);
    border-radius: 8px;
    cursor: pointer;
    color: var(--ink);
    font-size: .85rem;
    box-shadow: 0 2px 4px rgba(0,0,0,.15);
    transition: background .12s ease, color .12s ease;
}
.gallery-tile-action:hover { background: #fff; }
.gallery-tile-action:disabled { opacity: .5; cursor: not-allowed; background: rgba(255,255,255,.7); }
.gallery-tile-action-danger:hover { background: #fee2e2; color: #991b1b; border-color: #fecaca; }

/* Project list table — cover thumb in the title cell. Fixed-size landscape
 * box (60×40) with the image contained inside — natural aspect, no crop.
 * Falls back to a neutral icon when the project has no photos yet. */
.project-title-cell { padding-top: 8px !important; padding-bottom: 8px !important; }
.project-title-row {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
}
/* Title + any domain-state pills share a column that wraps within itself,
   so a long title never falls to a new flex line underneath the thumbnail. */
.project-title-main {
    min-width: 0;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-2);
}
/* "New activity" red dot on a project row — the trail past the nav red dot.
   Cleared when the client opens that project. Flex child of .project-title-main,
   so the column gap spaces it from the title automatically. */
.project-alert-dot {
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--danger);
}
/* Inline "new activity" red dot for list rows (invoices, agreements, …) — the
   trail past the nav red dot. Cleared when the item is opened. Used in table
   cells, so it sits inline next to the item's number/title link. */
.notif-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--danger);
    vertical-align: middle;
    margin-left: 6px;
}
/* Domain-state pills on a project row (Downloads ready, Invoice due, …). */
.project-pill {
    display: inline-flex; align-items: center;
    padding: 2px 9px; border-radius: 999px;
    font-size: .7rem; font-weight: 700; letter-spacing: .02em; white-space: nowrap;
}
.project-pill-good { background: var(--success-bg);        color: var(--success); }
.project-pill-warn { background: var(--brand-accent-tint);  color: var(--brand-accent-strong); }
.project-pill-info { background: #eff6ff;                   color: #2563eb; }
.project-cover-thumb {
    flex: 0 0 60px;
    width: 60px;
    height: 40px;
    border-radius: 6px;
    background: #fafafa;
    border: 1px solid var(--border);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    color: #cbd5e1;        /* fallback icon color */
}
.project-cover-thumb img {
    width: 100%;
    height: 100%;
    display: block;
    object-fit: cover;            /* fill the frame; object-position sets the crop */
    object-position: 50% 50%;     /* default; per-project value applied inline */
}
.project-cover-thumb .fa-image { font-size: 1rem; }
/* Job folder row in the unified Shoots list: a folder tile instead of a photo. */
.project-cover-thumb-job { background: var(--brand-accent-tint); border-color: transparent; color: var(--brand-accent-strong); }
.project-cover-thumb-job .fa-folder { font-size: 1.05rem; }
.project-row-job .project-title-link { font-weight: 600; }
.project-title-link { font-weight: 500; }

/* ── Client gallery viewer ──────────────────────────────────────────── */
:root {
    --gallery-heart: #e11d48;     /* rose-600 — favorites (the client's preference) */
    --gallery-check: #16a34a;     /* green-600 — selections (final cut) */
    --gallery-star:  #f59e0b;     /* amber-500 — guest (share-link) favorites */
    --gallery-match: #7c3aed;     /* violet-600 — "matched" (2+ likes agree) */
    --gallery-bg:    #f4f4f5;     /* subtle off-white background */
}

/* Subtle bg on the gallery page only — makes white photos pop without
 * the harsh white-on-white look. Scoped via body.is-gallery-page so
 * other pages stay on the original surface. */
body.is-gallery-page,
body.is-gallery-page .portal-main-with-topbar { background: var(--gallery-bg); }

/* Hero with reflective-blur background — Apple Music / Spotify pattern.
 * Blurred + dimmed version of the cover fills the full width; the actual
 * cover renders centered at its natural aspect on top. Premium feel
 * without being cheesy. */
.gallery-hero {
    position: relative;
    width: 100%;
    height: 440px;
    overflow: hidden;
    border-radius: 16px;
    margin-bottom: var(--sp-5);
    background: #fafafa;
    isolation: isolate;     /* contain z-index stacking */
    display: flex;
    align-items: center;
    justify-content: center;
}
/* Shrink-wraps the centered cover so the watermark overlay covers ONLY the
   photo, not the blurred letterbox bars on either side. */
.gallery-hero-imgwrap {
    position: relative;
    z-index: 1;
    height: 100%;
    max-width: 100%;
    display: flex;
}
.gallery-hero-blur {
    position: absolute;
    inset: -40px;           /* extend beyond edges so blur doesn't show seams */
    background-size: cover;
    background-position: center;
    filter: blur(40px) brightness(.78) saturate(1.2);
    transform: scale(1.1);
    z-index: 0;
}
.gallery-hero-blur::after {
    /* subtle gradient overlay so the centered image has contrast */
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(to bottom, rgba(0,0,0,.08), rgba(0,0,0,.18));
}
.gallery-hero-img {
    position: relative;
    z-index: 1;
    display: block;
    margin: 0 auto;
    height: 100%;
    width: auto;
    max-width: 100%;
    object-fit: contain;
    box-shadow: 0 12px 40px rgba(0,0,0,.35), 0 4px 12px rgba(0,0,0,.2);
    border-radius: 6px;
}
@media (max-width: 700px) {
    .gallery-hero { height: 280px; border-radius: 12px; }
}

/* Brand color picker (Settings → Gallery defaults) */
.color-field { display: flex; align-items: center; gap: 10px; }
input[type="color"].color-swatch-input { width: 48px; height: 36px; padding: 2px; border: 1px solid var(--border); border-radius: 8px; background: #fff; cursor: pointer; flex: 0 0 auto; }

/* ── Gallery templates (Phase 1: Editorial + Classic) ───────────────────────
   A template is presentation only: cover style + accent. Proofing/static
   functions are unchanged. Accent comes from --gallery-accent (studio brand
   colour), set on .gallery-shell. */
/* Theme tokens. Each template is just a set of these custom properties on the
   shell — so a template changes the WHOLE gallery (canvas, thumbnail corners,
   gutter, framing, cover), not only the cover. Editorial = the defaults below
   (unchanged from before); Classic and Noir override the tokens. */
.gallery-shell {
    --gallery-accent: #ff6b35;
    --g-canvas: transparent;       /* surface behind the grid */
    --g-pad-y: 0px;                /* vertical breathing room inside the surface
                                      (no horizontal inset — the banner + grid
                                      stay flush with the page header width) */
    --g-surface-radius: 0px;
    --g-tile-radius: 10px;         /* thumbnail corners */
    --g-gap: 8px;                  /* grid gutter */
    --g-tile-bg: #e5e7eb;          /* lazy-load placeholder behind a thumb */
    --g-tile-border: transparent;
    --g-tile-hover-border: rgba(0,0,0,.18);
    --g-text: var(--text);
    --g-text-muted: var(--text-light);
    --g-cover-h: 100vh;   /* Editorial (default): full-screen immersive cover */
    --g-cover-min: 480px;
    --g-cover-max: none;

    box-sizing: border-box;
    background: var(--g-canvas);
    padding: var(--g-pad-y) 0;
    border-radius: var(--g-surface-radius);
    color: var(--g-text);
}
/* Apply the tokens to the grid + thumbnails. */
.gallery-shell .gallery-grid-client { gap: var(--g-gap); }
.gallery-shell .gallery-grid-client .gallery-tile {
    border-radius: var(--g-tile-radius);
    background: var(--g-tile-bg);
    border-color: var(--g-tile-border);
}
.gallery-shell .gallery-grid-client .gallery-tile:hover { border-color: var(--g-tile-hover-border); }

/* Branded full-bleed cover (base = Editorial). The cover image fills the hero;
   logo/title/date/welcome sit on a bottom scrim for legibility. */
.gallery-cover { height: var(--g-cover-h); min-height: var(--g-cover-min); max-height: var(--g-cover-max); }
.gallery-cover .gallery-hero-imgwrap { position: absolute; inset: 0; width: 100%; height: 100%; }
.gallery-cover .gallery-hero-img { width: 100%; height: 100%; object-fit: cover; }
/* Editorial cover = flush full-bleed: square corners + break out of the page's
   left/right inset (negative margins cancel the .public-gallery-shell sp-4
   padding). Classic/Noir keep their framed, inset, rounded covers. */
.gallery-theme-editorial .gallery-cover {
    width: auto;
    margin-left: calc(-1 * var(--sp-4));
    margin-right: calc(-1 * var(--sp-4));
    border-radius: 0;
}
/* Drop the top inset on the Editorial gallery so the full-screen cover sits
   flush to the top — which also stops the 100vh cover being pushed ~16px below
   the fold by that padding. */
.public-gallery-body.gallery-theme-editorial .public-gallery-shell { padding-top: 0; }
/* Capped variant (portrait cover) — matches the studio's project-page hero: the
   image fills a centered square crop window (zoomed, same focal point) and the
   blurred backdrop fills the sides. The .gallery-hero flex parent centers it. */
.gallery-cover--capped .gallery-hero-imgwrap {
    position: relative; inset: auto;
    height: 100%; width: auto; aspect-ratio: 5 / 4; max-width: 92%;   /* match the studio hero's landscape crop window */
    margin: 0 auto; overflow: hidden; border-radius: 0;   /* square corners to match the studio hero */
    box-shadow: 0 12px 40px rgba(0,0,0,.35), 0 4px 12px rgba(0,0,0,.2);
}
/* The image itself carries .gallery-hero-img (radius + shadow); square it and
   drop its shadow inside the capped window so only the wrapper frames it. */
.gallery-cover--capped .gallery-hero-img { border-radius: 0; box-shadow: none; }
/* Title drops to the bottom over a bottom-up scrim so it doesn't sit on the
   face in the centered crop window. */
.gallery-cover--capped .gallery-cover-overlay {
    align-items: flex-end;
    background: linear-gradient(to top, rgba(0,0,0,.6) 0%, rgba(0,0,0,.18) 32%, transparent 58%);
    padding-bottom: var(--sp-7, 56px);
}
.gallery-cover-overlay {
    position: absolute; inset: 0; z-index: 2; display: flex; justify-content: center;
    align-items: center; text-align: center; padding: var(--sp-7, 56px) var(--sp-5);
    /* Centered title/logo: a balanced vignette (a touch darker at center) keeps
       the type legible over any image without crushing the photo at the edges. */
    background: radial-gradient(ellipse 78% 78% at 50% 50%, rgba(0,0,0,.46), rgba(0,0,0,.26) 70%, rgba(0,0,0,.34));
}
.gallery-cover-inner { color: #fff; max-width: 720px; }
.gallery-cover-logo { display: block; max-height: 56px; max-width: 220px; margin: 0 auto var(--sp-3); object-fit: contain; filter: drop-shadow(0 1px 4px rgba(0,0,0,.5)); }
/* Studio name above the gallery title — deliberately subtle (small, spaced,
   uppercase) so the project title stays the focal point. */
.gallery-cover-studio { color: #fff; opacity: .9; margin: 0 0 var(--sp-3); font-size: .8rem; font-weight: 600; text-transform: uppercase; letter-spacing: .16em; text-shadow: 0 1px 10px rgba(0,0,0,.5); }
.gallery-cover-title { color: #fff; margin: 0; font-size: clamp(1.9rem, 4.5vw, 3.1rem); font-weight: 800; line-height: 1.08; text-shadow: 0 2px 16px rgba(0,0,0,.4); }
.gallery-cover-meta { color: #fff; opacity: .9; margin: 10px 0 0; font-size: .8rem; text-transform: uppercase; letter-spacing: .08em; }
.gallery-cover-welcome { color: #fff; opacity: .95; margin: var(--sp-3) auto 0; max-width: 560px; font-size: 1.02rem; line-height: 1.55; text-shadow: 0 1px 10px rgba(0,0,0,.4); }
/* "View gallery" CTA on the cover — a quiet glass pill, smooth-scrolls to the grid. */
.gallery-cover-cta {
    margin-top: var(--sp-5); display: inline-flex; align-items: center; gap: 9px;
    padding: 11px 22px; border-radius: 999px;
    background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.55); color: #fff;
    font: inherit; font-size: .9rem; font-weight: 600; letter-spacing: .02em;
    cursor: pointer; backdrop-filter: blur(4px);
    transition: background .15s ease, transform .15s ease;
}
.gallery-cover-cta:hover { background: rgba(255,255,255,.22); transform: translateY(-1px); }
.gallery-cover-cta i { font-size: .8rem; }
/* Studio project-page cover EDITOR mirrors the selected (or studio-default)
   theme's PROPORTIONS, so the crop the studio frames matches what the client
   sees: Editorial ~full-screen (tall), Classic/Noir wider/shorter banners.
   aspect-ratio (not a fixed height) keeps the framing honest; max-height caps
   it on wide screens so the management page stays usable. */
.gallery-hero-editor[data-cover-theme] .gallery-cover-banner-tall { height: auto; }
.gallery-hero-editor[data-cover-theme="editorial"] .gallery-cover-banner-tall { aspect-ratio: 16 / 9;  max-height: 760px; }
.gallery-hero-editor[data-cover-theme="classic"]   .gallery-cover-banner-tall { aspect-ratio: 12 / 5;  max-height: 560px; }
.gallery-hero-editor[data-cover-theme="noir"]      .gallery-cover-banner-tall { aspect-ratio: 5 / 2;   max-height: 540px; }

/* ── Classic — warm, framed, restrained ─────────────────────────────────────
   Soft paper canvas with padding (the grid sits in a framed surface), lightly-
   squared thumbnails with a thin warm frame, wider gutters, a shorter cover. */
.gallery-theme-classic {
    --g-canvas: #f5f3ee;
    --g-pad-y: clamp(16px, 2.5vw, 30px);
    --g-surface-radius: 14px;
    --g-tile-radius: 3px;
    --g-gap: 16px;
    --g-tile-bg: #ffffff;
    --g-tile-border: #e6e0d4;
    --g-tile-hover-border: #c9bfa8;
    --g-cover-h: 70vh;   /* Classic: tall, framed cover (not full-screen) */
    --g-cover-min: 420px;
    --g-cover-max: none;
}
.gallery-theme-classic .gallery-cover-title { font-weight: 700; letter-spacing: .01em; }

/* ── Noir — dark, dramatic, edge-to-edge ────────────────────────────────────
   Near-black canvas, SQUARE thumbnails with tight gutters (a photo-wall
   mosaic), light type. The control surfaces go dark to match. */
.gallery-theme-noir {
    --g-canvas: #0e0e10;
    --g-pad-y: clamp(16px, 2.5vw, 34px);
    --g-surface-radius: 16px;
    --g-tile-radius: 0px;
    --g-gap: 5px;
    --g-tile-bg: #1b1b1f;
    --g-tile-border: transparent;
    --g-tile-hover-border: rgba(255,255,255,.28);
    --g-text: #f4f4f5;
    --g-text-muted: #a1a1aa;
    --g-cover-h: 65vh;   /* Noir: tall, dramatic cover */
    --g-cover-min: 420px;
    --g-cover-max: none;
}
/* Dark control surfaces so the whole gallery reads as one dark canvas. */
.gallery-theme-noir .gallery-status-bar {
    background: rgba(20,20,23,.82);
    border-color: rgba(255,255,255,.10);
    color: var(--g-text);
    backdrop-filter: blur(8px);
}
.gallery-theme-noir .gallery-filter-tabs { background: rgba(255,255,255,.06); }
.gallery-theme-noir .gallery-filter-tab { color: var(--g-text-muted); }
.gallery-theme-noir .gallery-filter-tab:hover { color: var(--g-text); }
.gallery-theme-noir .gallery-filter-tab.is-active { background: rgba(255,255,255,.12); box-shadow: none; }
.gallery-theme-noir .gallery-filter-count { background: rgba(255,255,255,.12); color: var(--g-text); }
.gallery-theme-noir .gallery-filter-empty-text,
.gallery-theme-noir .gallery-selcount { color: var(--g-text); }
/* Back-link + "Clear all" go light so they're legible on the dark bar. */
.gallery-theme-noir .gallery-back-link { color: var(--g-text-muted); }
.gallery-theme-noir .gallery-back-link:hover { color: var(--g-text); }
.gallery-theme-noir [data-clear-all] { color: var(--g-text-muted); }
.gallery-theme-noir [data-clear-all]:hover {
    color: var(--g-text);
    background: rgba(255,255,255,.12);   /* not the default white — keeps light text legible */
}
/* Slider track reads on the dark bar. */
.gallery-theme-noir .gallery-thumb-size-slider { background: rgba(255,255,255,.18); }
.gallery-theme-noir .gallery-thumb-size-icon-sm,
.gallery-theme-noir .gallery-thumb-size-icon-lg { color: var(--g-text-muted); }

/* Studio accent wiring (kept conservative): active filter tab + submit button. */
.gallery-shell .gallery-filter-tab.is-active { color: var(--gallery-accent); }
.gallery-shell [data-submit-selections] { background: var(--gallery-accent); border-color: var(--gallery-accent); }
/* Make the studio's brand accent visible on EVERY gallery — not just the
   proofing controls above (which don't exist on a static gallery). The
   thumb-size slider handle + a short underline beneath the cover title carry
   the colour on every gallery, static or proofing. */
.gallery-shell .gallery-thumb-size-slider::-webkit-slider-thumb { background: var(--gallery-accent); }
.gallery-shell .gallery-thumb-size-slider::-moz-range-thumb { background: var(--gallery-accent); }
.gallery-cover-title::after {
    content: ''; display: block; width: 46px; height: 3px; margin: 14px auto 0;
    background: var(--gallery-accent); border-radius: 2px; box-shadow: 0 1px 6px rgba(0,0,0,.35);
}

@media (max-width: 700px) {
    .gallery-cover { height: 50vh; min-height: 260px; }
    .gallery-cover-overlay { padding: var(--sp-5) var(--sp-4); }
}

/* ── Cover reposition editor (studio-side gallery management) ────────────────
   Renders the cover exactly as the client sees it, but at a contained height,
   and lets the studio drag the image to set its focal point. */
.gallery-cover-editor-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: var(--sp-4); margin-bottom: var(--sp-3);
}
.gallery-cover-editor-status {
    flex: 0 0 auto; color: var(--success); font-size: .8rem; font-weight: 600;
    display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
}
/* display:inline-flex above outranks the UA [hidden]{display:none}, so the
   badge ignored its `hidden` attribute and stayed on permanently. Respect it. */
.gallery-cover-editor-status[hidden] { display: none; }
/* Contained preview height so the editor doesn't dominate the page. */
.gallery-cover-editor .gallery-cover,
.gallery-cover-editor .gallery-theme-classic .gallery-cover {
    height: 320px; min-height: 0; max-height: none;
}
@media (max-width: 700px) {
    .gallery-cover-editor .gallery-cover { height: 240px; }
}
/* Overlay text is decorative here — let drags pass through to the image. */
.gallery-cover-editor .gallery-cover-overlay { pointer-events: none; }
.gallery-hero.is-repositionable {
    cursor: grab; touch-action: none; user-select: none;
}
.gallery-hero.is-repositionable.is-dragging { cursor: grabbing; }
.gallery-hero.is-repositionable .gallery-hero-img { transition: none; }
/* List-thumbnail crop editor: a 3:2 frame matching the project-list thumbnail. */
.gallery-thumb-frame {
    position: relative;
    width: 260px; max-width: 100%;
    aspect-ratio: 3 / 2;
    border-radius: 10px;
    overflow: hidden;
    border: 1px solid var(--border);
    background: #fafafa;
}
.gallery-thumb-frame.is-repositionable { cursor: grab; touch-action: none; user-select: none; }
.gallery-thumb-frame.is-repositionable.is-dragging { cursor: grabbing; }
.gallery-thumb-img {
    width: 100%; height: 100%; display: block;
    object-fit: cover; object-position: 50% 50%;
}
.gallery-cover-drag-hint {
    position: absolute; z-index: 3; left: 50%; bottom: 14px; transform: translateX(-50%);
    display: inline-flex; align-items: center; gap: 6px; pointer-events: none;
    background: rgba(0,0,0,.6); color: #fff; font-size: .75rem; font-weight: 600;
    padding: 6px 12px; border-radius: 999px; backdrop-filter: blur(4px);
    transition: opacity .15s ease;
}
.gallery-hero.is-dragging .gallery-cover-drag-hint { opacity: 0; }

/* Status bar above grid — instructions + counter + clear + submit.
 * Sticky variant follows the scroll, with subtle blur backdrop so the
 * gallery photos behind it stay visible-but-soft. */
.gallery-status-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: var(--sp-3) var(--sp-4);
    margin-bottom: var(--sp-3);
    flex-wrap: wrap;
    transition: padding .25s ease, box-shadow .25s ease;
}
.gallery-status-bar-sticky {
    position: sticky;
    /* Stick flush below the admin-topbar (which is itself sticky at top:0
     * with height var(--admin-topbar-h)). Without this offset the filter
     * bar slides up ON TOP of the admin-topbar during scroll, which
     * looks sloppy because the admin-topbar (search + bell + profile)
     * is still meant to be the dominant header. */
    top: var(--admin-topbar-h);
    z-index: 40;     /* below the topbars (admin z:45, portal z:50) — sticks just beneath the header */
    background: rgba(255,255,255,.92);
    -webkit-backdrop-filter: saturate(180%) blur(10px);
    backdrop-filter: saturate(180%) blur(10px);
    box-shadow: 0 2px 12px -4px rgba(0,0,0,.08);
}
/* On mobile both the portal-topbar AND the admin-topbar are visible
 * stacked at the top. The gallery sticky bar sticks BELOW both. Solid
 * opaque background + edge-to-edge to feel like a third deck of the
 * topbar stack (Pixieset / ShootProof pattern). */
@media (max-width: 900px) {
    .gallery-status-bar-sticky {
        top: calc(var(--portal-topbar-h) + var(--admin-topbar-h));
        z-index: 30;
        background: #fff;
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
        border-radius: 0;
        border-left: 0;
        border-right: 0;
        margin-left: calc(var(--sp-4) * -1);
        margin-right: calc(var(--sp-4) * -1);
    }
}
@media (min-width: 900.1px) and (max-width: 900px) {}    /* no-op spacer */
@media (max-width: 900px) {
    /* Counter the negative margin on the gallery-fullbleed pages so the
     * sticky bar still has internal padding aligned with the photo grid. */
    body.is-gallery-page .gallery-status-bar-sticky {
        margin-left: calc(var(--sp-5) * -1);
        margin-right: calc(var(--sp-5) * -1);
    }
}
/* Compact variant — 3-column grid sticky toolbar:
 *   LEFT   = selections/favorites counter
 *   CENTER = filter tabs (truly centered via grid-template-columns 1fr auto 1fr)
 *   RIGHT  = slider + Clear all + Submit
 * Grid is better than flex space-between here because it keeps the
 * center column visually centered relative to the bar, regardless of
 * left/right column width. */
.gallery-status-bar-compact {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-2) var(--sp-3);
    flex-wrap: nowrap;
}
.gallery-status-bar-left  { justify-self: start; min-width: 0; }
.gallery-status-bar-compact .gallery-filter-tabs {
    justify-self: center;
    margin: 0;
}
.gallery-status-bar-compact .gallery-status-bar-right {
    justify-self: end;
    gap: var(--sp-3);
    flex-wrap: nowrap;
}
/* Tighten the slider when it sits inside the bar so it doesn't dominate */
.gallery-status-bar-compact .gallery-thumb-size-slider {
    width: 110px;
}
/* Center group: the selection counter sits right next to the filter tabs. */
.gallery-status-bar-center {
    justify-self: center;
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    min-width: 0;
}
/* "Back to project" link in the sticky-bar left slot. */
.gallery-back-link {
    display: inline-flex;
    align-items: center;
    vertical-align: middle;   /* center in the line box (was baseline → sat low) */
    line-height: 1;
    gap: 6px;
    font-size: .85rem;
    font-weight: 600;
    color: var(--text-med);
    text-decoration: none;
    white-space: nowrap;
}
.gallery-back-link:hover { color: var(--text); }
/* Clickable hero title (returns to the project). */
.gallery-cover-title-link { color: inherit; text-decoration: none; }
.gallery-cover-title-link:hover { text-decoration: underline; }
@media (max-width: 900px) {
    .gallery-status-bar-compact .gallery-thumb-size { display: none; }
    .gallery-status-bar-compact { padding: var(--sp-2); }
}

/* ── Stacked sticky bar layout (≤1280px: phones, tablets, narrow windows) ──
 * The inline bar (back + counter + 6 filter tabs + slider + actions) only fits
 * comfortably on a wide desktop. Below that — iPad portrait/landscape,
 * split-screen, smaller laptops, resized windows — collapse to a stack
 * (counter row / filter tabs / actions) so nothing overlaps or runs off. */
@media (max-width: 1280px) {
    .gallery-status-bar-compact {
        grid-template-columns: 1fr;
        grid-template-rows: auto auto auto;
        gap: var(--sp-2);
        padding: var(--sp-2);
    }
    .gallery-status-bar-left,
    .gallery-status-bar-center,
    .gallery-status-bar-compact .gallery-filter-tabs,
    .gallery-status-bar-compact .gallery-status-bar-right {
        justify-self: center;
        width: 100%;
    }
    /* Center group stacks (counter over tabs) on narrow screens. */
    .gallery-status-bar-center {
        flex-direction: column;
        gap: var(--sp-2);
    }
    .gallery-status-bar-left { text-align: center; }
    .gallery-status-bar-compact .gallery-filter-tabs {
        justify-content: center;
    }
    .gallery-status-bar-compact .gallery-filter-tab {
        padding: 5px 8px;
        font-size: .8rem;
        gap: 5px;
    }
    .gallery-status-bar-compact .gallery-filter-count {
        min-width: 18px;
        padding: 1px 5px;
        font-size: .68rem;
    }
    /* Stop the tabs container from forcing its own background card width —
     * let it shrink with the viewport so it doesn't push past the bar edge. */
    .gallery-status-bar-compact .gallery-filter-tabs {
        max-width: 100%;
        padding: 3px;
    }
    .gallery-status-bar-compact .gallery-status-bar-right {
        justify-content: space-between;
        gap: var(--sp-2);
    }
    .gallery-status-bar-compact .gallery-selcount {
        font-size: .9rem;
    }
    .gallery-status-bar-compact .gallery-selcount strong { font-size: 1.05rem; }
}

/* ── Active proofing bar: back-link + counter + tabs + actions ──────────────
 * Desktop: 4 columns — back (left) | counter tabs (centered) | actions (right). */
.gallery-status-bar-proofing {
    /* minmax(max-content, 1fr): the back link + actions columns can grow to
       balance the bar but never shrink below their own content, so the nowrap
       "Back to project" can't spill over the counter when space gets tight. */
    grid-template-columns: minmax(max-content, 1fr) auto auto minmax(max-content, 1fr);
}
.gallery-status-bar-proofing .gallery-selcount   { justify-self: end; }
.gallery-status-bar-proofing .gallery-filter-tabs { justify-self: start; }
/* Stacked: "< Back to project" and the counter share the top row (back left,
 * counter right); filter tabs and the actions each get their own row below. */
@media (max-width: 1280px) {
    .gallery-status-bar-proofing {
        grid-template-columns: 1fr 1fr;
        grid-template-areas:
            "back    counter"
            "tabs    tabs"
            "actions actions";
        gap: var(--sp-2);
        align-items: center;
    }
    .gallery-status-bar-proofing .gallery-status-bar-left {
        grid-area: back; justify-self: start; width: auto; text-align: left;
    }
    .gallery-status-bar-proofing .gallery-selcount {
        grid-area: counter; justify-self: end;
    }
    .gallery-status-bar-proofing .gallery-filter-tabs {
        grid-area: tabs; justify-self: center; width: 100%;
    }
    .gallery-status-bar-proofing .gallery-status-bar-right {
        grid-area: actions; width: 100%;
    }
}

/* Touch devices — bigger hit areas for the heart + check icons (iOS
 * recommends 44×44pt minimum). Doubles the safe-tap radius without
 * affecting visual size, since the larger area is invisible/transparent. */
@media (hover: none) {
    .gallery-icon {
        width: 44px;
        height: 44px;
    }
    .gallery-icon-heart { top: 4px; left: 4px; }
    .gallery-icon-check { top: 4px; right: 4px; }
    .gallery-icon-download { top: 4px; right: 4px; }
    .gallery-icon-star  { top: 4px; left: 4px; bottom: auto; }
    /* Hover-only filename can't fire on touch — hide it entirely. */
    .gallery-tile-filename { display: none; }
}

/* Topbar logo — shows the studio's uploaded logo in the mobile topbar
 * instead of the truncatable studio name. Capped at the topbar height
 * minus padding so it doesn't blow up the bar. */
.brand-logo-topbar {
    height: calc(var(--portal-topbar-h) - var(--sp-3) * 2);
    width: auto;
    max-width: 160px;
    object-fit: contain;
    display: block;
}

.gallery-instructions {
    display: flex; flex-direction: column; gap: 4px;
    font-size: .9rem;
    color: var(--ink);
}
.gallery-instructions i { width: 16px; margin-right: 4px; }
.gallery-status-bar-right {
    display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
}
.gallery-selcount {
    font-size: 1rem;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    transition: color .25s ease;
}
.gallery-selcount strong { font-size: 1.2rem; transition: color .25s ease; }
.gallery-selcount [data-sel-count-num].is-bump {
    display: inline-block;
    animation: gallery-count-bump .35s ease;
}
@keyframes gallery-count-bump {
    0%   { transform: scale(1); }
    40%  { transform: scale(1.25); color: var(--gallery-check); }
    100% { transform: scale(1); }
}

/* Near-max emphasis: when client is 1 away from max selections,
 * counter goes slightly amber. At-max: counter goes solid green with
 * a subtle ring. Gentle, not screamy. */
.gallery-selcount[data-near-max="1"] strong { color: #d97706; }
.gallery-selcount[data-at-max="1"]   strong { color: var(--gallery-check); }
.gallery-selcount[data-at-max="1"]::before {
    content: '';
    display: inline-block;
    width: 8px; height: 8px;
    border-radius: 50%;
    background: var(--gallery-check);
    box-shadow: 0 0 0 2px rgba(22,163,74,.2);
    margin-right: 4px;
    animation: gallery-at-max-pulse 2s ease-in-out infinite;
}
@keyframes gallery-at-max-pulse {
    0%, 100% { box-shadow: 0 0 0 2px rgba(22,163,74,.2); }
    50%      { box-shadow: 0 0 0 6px rgba(22,163,74,.05); }
}

/* Save cue — subtle ✓ checkmark next to the counter that flashes in
 * briefly when a server save lands. Single attention cue, NOT a
 * per-click toast (which Tyler said would get annoying with rapid
 * picks). Animation is automatic via .is-saved class toggled by JS. */
.gallery-save-cue {
    display: inline-flex;
    align-items: center;
    color: var(--gallery-check);
    font-size: .9rem;
    opacity: 0;
    transform: translateX(-4px);
    transition: opacity .25s ease, transform .25s ease;
}
.gallery-save-cue.is-saved {
    opacity: 1;
    transform: translateX(0);
}
.gallery-status-bar-locked {
    background: #f0fdf4;
    border-color: #bbf7d0;
    color: #166534;
}
.gallery-status-bar-locked .fa-lock { color: #16a34a; margin-right: 6px; }

/* Per-filter empty state — shown when current filter has 0 matching tiles.
 * Friendlier than an empty grid; explains what the filter does + offers
 * a one-click escape back to All. */
.gallery-filter-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: var(--sp-7) var(--sp-4);
    background: rgba(255,255,255,.6);
    border: 1px dashed var(--border);
    border-radius: 12px;
    margin: var(--sp-3) auto var(--sp-5);
    max-width: 520px;
    gap: var(--sp-3);
}
.gallery-filter-empty[hidden] { display: none; }
.gallery-filter-empty-icon {
    font-size: 2.5rem;
    color: var(--muted);
    opacity: .5;
}
.gallery-filter-empty-text {
    font-size: .95rem;
    color: var(--ink);
    margin: 0;
    line-height: 1.5;
}
.gallery-filter-empty-text i {
    color: var(--gallery-heart);
    margin: 0 2px;
}
.gallery-filter-empty[data-filter-mode="selections"] .gallery-filter-empty-text i {
    color: var(--gallery-check);
}

/* Toolbar row holding filter tabs (centered) + thumb-size slider (right).
 * 3-column grid: spacer | tabs | slider — keeps tabs visually centered
 * while letting the slider live on the right edge. */
.gallery-toolbar-row {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: var(--sp-3);
    margin-bottom: var(--sp-4);
}
.gallery-toolbar-row .gallery-thumb-size { justify-self: end; }
.gallery-toolbar-row-end { grid-template-columns: 1fr; }
.gallery-toolbar-row-end .gallery-thumb-size { justify-self: end; }
.gallery-toolbar-spacer {}    /* invisible left column to balance grid */
@media (max-width: 700px) {
    .gallery-toolbar-row {
        grid-template-columns: 1fr;
        gap: var(--sp-2);
    }
    .gallery-toolbar-row .gallery-thumb-size { justify-self: center; }
    .gallery-toolbar-spacer { display: none; }
}

/* Filter tabs (ALL / FAVES / SELECTS) */
.gallery-filter-tabs {
    display: flex;
    gap: 4px;
    background: var(--bg-soft);
    padding: 4px;
    border-radius: 10px;
    width: fit-content;
    margin: 0 auto;
}
.gallery-filter-tab {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    border: 0;
    background: transparent;
    color: var(--muted);
    font-size: .9rem;
    font-weight: 500;
    border-radius: 6px;
    cursor: pointer;
    transition: background .12s ease, color .12s ease;
}
.gallery-filter-tab:hover { color: var(--ink); }
.gallery-filter-tab.is-active {
    background: #fff;
    color: var(--ink);
    box-shadow: 0 1px 2px rgba(0,0,0,.08);
}
.gallery-filter-count {
    display: inline-block;
    min-width: 22px;
    padding: 1px 6px;
    border-radius: 999px;
    background: rgba(0,0,0,.06);
    font-size: .72rem;
    font-weight: 600;
    text-align: center;
    color: var(--ink);
}
.gallery-filter-tab.is-active .gallery-filter-count {
    background: var(--ink);
    color: #fff;
}

/* Lock-icon button in the filter bar (locked state, per TP spec). Sits
 * next to the filter tabs; click → opens the lockout info modal. */
.gallery-filter-tab-lock {
    color: #16a34a;
    margin-left: 4px;
    padding: 8px 12px;
}
.gallery-filter-tab-lock:hover { background: rgba(22,163,74,.08); color: #166534; }

/* Heart + check icons — TP spec exactly: icon-only with text-STROKE
 * (not shadow) so they're visible on white AND dark backgrounds without
 * the dark-shadow-on-white smudge effect Tyler reported during fast
 * scroll. Active state switches to colored fill. */
.gallery-icon {
    position: absolute;
    width: 38px;
    height: 38px;
    border: 0;
    background: transparent;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    transition: transform .15s ease;
    z-index: 2;
}
/* The icon character itself — color + stroke live on the <i> so we can
 * style independently of the button hit-area. Outline kept light so it
 * doesn't compete visually with the photos behind it. */
.gallery-icon i {
    color: rgba(255,255,255,.6);
    font-size: 22px;
    line-height: 1;
    -webkit-text-stroke: 1px rgba(0,0,0,.22);
    text-shadow: 0 1px 1px rgba(0,0,0,.15);  /* Firefox fallback (lighter than stroke) */
    transition: color .15s ease, -webkit-text-stroke-color .15s ease;
}
.gallery-icon-heart { top: 8px; left: 8px; }
.gallery-icon-check { top: 8px; right: 8px; }
/* Static-gallery download (finals) — top-right, slightly smaller glyph. */
.gallery-icon-download { top: 8px; right: 8px; text-decoration: none; }
.gallery-icon-download i { font-size: 18px; }
/* On the grid, reveal on hover (pointer) so the tile stays clean; keyboard focus
 * reveals it too, and touch devices (no hover) always show it. The lightbox
 * download button is unaffected (it lives outside .gallery-tile). */
.gallery-tile .gallery-icon-download { opacity: 0; transition: opacity .15s ease; }
.gallery-tile:hover .gallery-icon-download,
.gallery-tile:focus-within .gallery-icon-download { opacity: 1; }
@media (hover: none) { .gallery-tile .gallery-icon-download { opacity: 1; } }
.gallery-icon:hover i { color: rgba(255,255,255,.95); }
.gallery-icon:active { transform: scale(.9); }

/* ── Image sales: per-tile Buy pill + sticky cart bar ────────────────────── */
.gallery-buy-btn {
    position: absolute;
    left: 8px;
    bottom: 8px;
    z-index: 3;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 5px 11px;
    border: 0;
    border-radius: 999px;
    background: rgba(10, 10, 10, .72);
    color: #fff;
    font-size: .82rem;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
    transition: background .15s ease, transform .12s ease;
}
.gallery-buy-btn i { width: 15px; height: 15px; }
.gallery-buy-btn:hover { background: rgba(10, 10, 10, .9); }
.gallery-buy-btn:active { transform: scale(.96); }
/* The [hidden] attribute must beat .gallery-buy-btn's display:inline-flex so the
   JS-toggled lightbox Buy button actually hides. Without this the lightbox cart
   shows even when the tile pill is gated (e.g. proofing selections not yet all
   used, or unlimited-selection proofing where buying never unlocks). */
.gallery-buy-btn[hidden] { display: none !important; }
.gallery-buy-btn .gallery-buy-btn-in { display: none; align-items: center; gap: 5px; }
/* Already-bought photo: a static badge in place of the Buy pill. */
.gallery-purchased-badge {
    position: absolute;
    left: 8px; bottom: 8px;
    z-index: 3;
    display: inline-flex; align-items: center; gap: 5px;
    padding: 5px 11px;
    border-radius: 999px;
    background: rgba(10, 10, 10, .55);
    color: #fff;
    font-size: .8rem; font-weight: 600;
    -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
}
.gallery-purchased-badge i { width: 14px; height: 14px; }
/* In-cart state fires two ways: on the tile (.is-in-cart on the parent tile) and
   on the lightbox pill (.is-in-cart on the button itself). */
.gallery-tile.is-in-cart .gallery-buy-btn,
.gallery-buy-btn.is-in-cart { background: var(--brand-accent); color: #0a0a0a; }
.gallery-tile.is-in-cart .gallery-buy-btn .gallery-buy-btn-add,
.gallery-buy-btn.is-in-cart .gallery-buy-btn-add { display: none; }
.gallery-tile.is-in-cart .gallery-buy-btn .gallery-buy-btn-in,
.gallery-buy-btn.is-in-cart .gallery-buy-btn-in { display: inline-flex; }
/* Selling galleries hide the filename caption so the price pill owns the corner. */
.gallery-grid-selling .gallery-tile-filename { display: none; }

.gallery-cart-bar {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 60;
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
    background: rgba(20, 20, 20, .94);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    border-top: 1px solid rgba(255, 255, 255, .12);
}
.gallery-cart-bar[hidden] { display: none; }
.gallery-cart-bar-inner {
    max-width: 900px;
    margin: 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
}
.gallery-cart-summary { color: #fff; font-size: .95rem; }
.gallery-cart-actions { display: flex; align-items: center; gap: 8px; }
.gallery-cart-bar .btn-ghost { color: rgba(255, 255, 255, .82); }
.gallery-cart-bar .btn-ghost:hover { color: #fff; }

/* Money input: a "$" prefix fused to a text field (studio price-per-image). */
.price-input-wrap { display: flex; align-items: stretch; max-width: 220px; }
.price-input-wrap-full { max-width: none; }   /* fill the field (e.g. deposit) */

/* No stepper arrows on number inputs (amounts, quantities, rates) — the up/down
   spinner is noise; nobody nudges a price by the penny. The numeric keypad on
   mobile is preserved by inputmode="decimal" on the inputs themselves. */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
.price-input-prefix {
    display: inline-flex;
    align-items: center;
    padding: 0 12px;
    min-height: 44px;
    border: 1px solid var(--border-strong);
    border-right: 0;
    border-radius: var(--radius) 0 0 var(--radius);
    background: var(--bg-soft);
    color: var(--text-med);
    font-weight: 600;
}
.price-input-wrap .form-control {
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
}

/* Purchases tab: thumbnail grid of bought photos with a download affordance. */
.purchase-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 8px; }
.purchase-thumb {
    position: relative;
    display: block;
    aspect-ratio: 1 / 1;
    border-radius: 8px;
    overflow: hidden;
    border: 1px solid var(--border);
    background: var(--bg-muted);
}
.purchase-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.purchase-thumb-dl {
    position: absolute;
    right: 6px; bottom: 6px;
    width: 26px; height: 26px;
    display: flex; align-items: center; justify-content: center;
    border-radius: 50%;
    background: rgba(10, 10, 10, .68);
    color: #fff;
    opacity: 0;
    transition: opacity .15s ease;
}
.purchase-thumb-dl i { width: 15px; height: 15px; }
.purchase-thumb:hover .purchase-thumb-dl,
.purchase-thumb:focus-visible .purchase-thumb-dl { opacity: 1; }
@media (hover: none) { .purchase-thumb-dl { opacity: 1; } }
/* Retouch-order thumbnail: no download (studio delivers the edited file). */
.purchase-thumb-static { cursor: default; }

/* Proofing "buy extras": buy buttons stay hidden until the included selections
   are all used, then the whole grid unlocks them. */
.gallery-buy-btn-gated { display: none; }
.gallery-grid-buys-unlocked .gallery-buy-btn-gated { display: inline-flex; }
/* A selected (included) photo can't also be bought — it's already part of the
   client's selections, so hide its Buy pill even when buying is unlocked. */
.gallery-grid-buys-unlocked .gallery-tile.is-selected-pick .gallery-buy-btn-gated { display: none; }

/* Submit & Buy modal: purchased list + embedded card element. */
.gallery-submit-modal-content { max-height: 92vh; overflow-y: auto; }
.gallery-buy-summary { margin-top: var(--sp-4); padding-top: var(--sp-4); border-top: 1px solid var(--border); }
.gallery-buy-list { list-style: none; margin: 0 0 var(--sp-3); padding: 0; max-height: 150px; overflow-y: auto; }
.gallery-buy-list li { padding: 4px 0; font-size: .86rem; color: var(--text-med); border-bottom: 1px solid var(--border-soft); }

/* Buy pill inside the lightbox: bottom-center, a touch larger than the tile. */
.gallery-buy-btn-lb {
    left: 50%; right: auto; bottom: 16px;
    transform: translateX(-50%);
    z-index: 6;
    padding: 8px 18px;
    font-size: .95rem;
}
.gallery-buy-btn-lb:active { transform: translateX(-50%) scale(.96); }
.gallery-icon-heart.is-active i {
    color: var(--gallery-heart);
    -webkit-text-stroke: 1px rgba(0,0,0,.18);
}
.gallery-icon-check.is-active i {
    color: var(--gallery-check);
    -webkit-text-stroke: 1px rgba(0,0,0,.18);
}
.gallery-icon.is-locked { cursor: default; pointer-events: none; }

/* Pop animation when a heart/check is toggled — Instagram-style bounce */
@keyframes gallery-icon-pop {
    0%   { transform: scale(1); }
    40%  { transform: scale(1.45); }
    100% { transform: scale(1); }
}
.gallery-icon.just-popped { animation: gallery-icon-pop 350ms cubic-bezier(.34, 1.56, .64, 1); }

/* Tile state outline — kept subtle; selection wins (it's the final cut) */
.gallery-tile.is-favorited       { border-color: var(--gallery-heart); }
.gallery-tile.is-selected-pick   { border-color: var(--gallery-check); box-shadow: 0 0 0 1px var(--gallery-check) inset; }

/* Refined tile hover (TP spec): image scales 1.02× + border darkens.
 * Subtle, tactile, doesn't shift the layout. */
.gallery-grid-client .gallery-tile {
    background: #e5e7eb;        /* gray so icons stay visible during lazy-load
                                  * instead of showing as smudges on white */
    border-color: transparent;
    overflow: hidden;
    transition: transform .2s ease, border-color .2s ease, box-shadow .2s ease;
}
.gallery-grid-client .gallery-tile-img {
    cursor: -webkit-zoom-in;    /* explicit cursor on the img too — with
                                  * pointer-events:none the parent's cursor
                                  * doesn't always propagate */
    cursor: zoom-in;
}
/* The hover zoom scales the IMAGE + its WATERMARK together as one unit.
 * Scaling only the image (with a static watermark on top) forced the browser
 * to repaint the watermark every frame — the overlap de-opt that caused the
 * hover lag. Scaling the shared wrapper lets it composite on the GPU as a
 * single layer. .gallery-tile-media is a client-grid-only wrapper around the
 * img + overlay; icons/filename are siblings and stay put. */
.gallery-tile-media { position: absolute; inset: 0; }
.gallery-grid-client .gallery-tile-media {
    height: 100%;   /* fill the JS-sized imgwrap so the img's height:100% resolves
                       exactly — otherwise it lands ~1px short and the tile bg
                       shows as a hairline at the bottom (visible on Noir). */
    transition: transform .25s ease;
}
.gallery-grid-client .gallery-tile:hover .gallery-tile-media {
    transform: scale(1.02) translateZ(0);
}
.gallery-grid-client .gallery-tile:hover {
    border-color: rgba(0,0,0,.18);
}
/* Photo load-in: a subtle shimmer over each tile's placeholder until its image
   loads, then the photo fades in. Scoped to .grid-fade-ready (JS adds it) so a
   no-JS gallery still shows every photo immediately. */
.gallery-grid-client.grid-fade-ready .gallery-tile-imgwrap { position: relative; }
.gallery-grid-client.grid-fade-ready .gallery-tile-img { opacity: 0; transition: opacity .45s ease; }
.gallery-grid-client.grid-fade-ready .gallery-tile.is-loaded .gallery-tile-img { opacity: 1; }
.gallery-grid-client.grid-fade-ready .gallery-tile-imgwrap::before {
    content: ''; position: absolute; inset: 0; pointer-events: none;
    background: linear-gradient(100deg, transparent 28%, rgba(255,255,255,.5) 50%, transparent 72%);
    background-size: 220% 100%;
    animation: galleryTileShimmer 1.4s ease-in-out infinite;
}
.gallery-grid-client.grid-fade-ready .gallery-tile.is-loaded .gallery-tile-imgwrap::before { display: none; }
@keyframes galleryTileShimmer { from { background-position: 220% 0; } to { background-position: -220% 0; } }
@media (prefers-reduced-motion: reduce) {
    .gallery-grid-client.grid-fade-ready .gallery-tile-imgwrap::before { animation: none; }
    .gallery-grid-client.grid-fade-ready .gallery-tile-img { transition: none; }
}

/* Filename — hover-only overlay at bottom of image (TP pattern). No
 * always-visible caption strip; cleaner client view. Tooltip carries
 * the full filename for accessibility. */
.gallery-tile-filename {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    padding: 8px 10px;
    background: linear-gradient(to top, rgba(0,0,0,.7), rgba(0,0,0,0));
    color: #fff;
    font-size: .72rem;
    line-height: 1.2;
    text-align: center;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    opacity: 0;
    transition: opacity .2s ease;
    pointer-events: none;
    z-index: 1;
}
.gallery-grid-client .gallery-tile:hover .gallery-tile-filename { opacity: 1; }

/* Client gallery cursor — custom SVG zoom-in cursor on image area,
 * pointer on interactive controls. SVG data URL guarantees consistent
 * rendering across browsers (Safari's native cursor:zoom-in is
 * unreliable). Hotspot at (11, 11) = center of the magnifying-glass
 * circle. Fallback: pointer.
 *
 * Each cursor is a 28×28 SVG: white circle with black stroke + handle.
 * Zoom-in has a "+" inside; zoom-out has a "−". */
/* Apply the zoom-in cursor to the .gallery-tile element TOO — not just
 * the imgwrap — so the thin border area (~1-3px between the tile edge
 * and the imgwrap content) doesn't show the underlying admin "grab"
 * cursor. Covers the whole tile surface uniformly. */
.gallery-grid-client .gallery-tile,
.gallery-grid-client .gallery-tile-imgwrap,
.gallery-grid-client .gallery-tile-img,
.gallery-grid-client .gallery-tile-filename {
    cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='28' height='28'><circle cx='11' cy='11' r='7.5' fill='white' stroke='black' stroke-width='1.5'/><line x1='11' y1='7' x2='11' y2='15' stroke='black' stroke-width='1.5' stroke-linecap='round'/><line x1='7' y1='11' x2='15' y2='11' stroke='black' stroke-width='1.5' stroke-linecap='round'/><line x1='16.5' y1='16.5' x2='25' y2='25' stroke='black' stroke-width='2.5' stroke-linecap='round'/></svg>") 11 11, pointer;
}
.gallery-grid-client .gallery-icon,
.gallery-grid-client .gallery-icon i {
    cursor: pointer;
}
/* imgwrap structure — relative + overflow:hidden so the hover filename
 * overlay positions correctly and the 1.02× hover scale doesn't spill */
.gallery-grid-client .gallery-tile { position: relative; }
.gallery-grid-client .gallery-tile-imgwrap {
    position: relative;
    overflow: hidden;
}
.gallery-grid-client .gallery-tile-img {
    pointer-events: auto;       /* restore — admin's drag pattern doesn't apply here */
}

/* ── Masonry layout (static delivery galleries only; set by gallery_layout) ──
 * Macy.js (self-hosted, init in gallery-client.js) distributes the tiles into the
 * shortest column and sets each tile's width + position. The container is plain
 * block; tiles keep their natural image aspect so Macy measures real heights.
 * 2/3/4/5 columns by width (Macy breakAt), 5px gutters. Proofing stays justified. */
.gallery-grid-masonry { display: block; }
.gallery-grid-masonry .gallery-tile { width: 100%; margin: 0; }
/* Image defines the tile height (natural aspect). max-height + inherited
 * overflow:hidden bound any runaway/corrupt image so it crops instead of growing
 * the column unboundedly. Higher specificity beats the .gallery-grid-client
 * overrides regardless of source order. */
.gallery-grid-masonry .gallery-tile-imgwrap { height: auto; max-height: 90vh; }
.gallery-grid-masonry.gallery-grid-client .gallery-tile-media { position: static; height: auto; }
.gallery-grid-masonry.gallery-grid-client .gallery-tile-img {
    position: static;
    width: 100%;
    height: auto;
    object-fit: contain;        /* whole frame, no crop */
    display: block;
}

/* Image-harvest friction — CSS-level. Combined with the JS-level
 * contextmenu + dragstart preventers in gallery-client.js, this stops
 * the casual right-click-save / drag-to-desktop on the client gallery.
 * Determined attackers can still grab via devtools — the goal is to
 * raise the bar, not seal it. */
.gallery-grid-client .gallery-tile-img,
.gallery-lightbox-stage img {
    -webkit-user-drag: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

/* Lightbox cursor:
 *   - Image area → custom zoom-OUT SVG cursor (signals click closes)
 *   - Nav arrows + close X + heart/check → pointer (hand)
 *   - Dark backdrop + empty stage area + caption → default arrow
 * SVG data URL approach matches the gallery zoom-in cursor; identical
 * design but with "−" instead of "+". */
.gallery-lightbox,
.gallery-lightbox-stage,
.gallery-lightbox-caption {
    cursor: default;
}
.gallery-lightbox-imgwrap,
.gallery-lightbox-stage img {
    cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='28' height='28'><circle cx='11' cy='11' r='7.5' fill='white' stroke='black' stroke-width='1.5'/><line x1='7' y1='11' x2='15' y2='11' stroke='black' stroke-width='1.5' stroke-linecap='round'/><line x1='16.5' y1='16.5' x2='25' y2='25' stroke='black' stroke-width='2.5' stroke-linecap='round'/></svg>") 11 11, pointer;
}
.gallery-lightbox-btn,
.gallery-lightbox-btn i,
.gallery-lightbox .gallery-icon,
.gallery-lightbox .gallery-icon i {
    cursor: pointer;
}
.gallery-lightbox-imgwrap {
    position: relative;
    display: inline-block;
    line-height: 0;            /* kill descender gap under img */
    max-width: 90vw;
    max-height: 82vh;
}

/* Lightbox heart + check — minimalist (matches grid icons), scaled up,
 * sit on the image's corners (NOT viewport corners, so they never collide
 * with the close X). */
.gallery-icon-lb {
    width: 44px; height: 44px;
    font-size: 26px;
    z-index: 1001;
}
.gallery-icon-lb-heart { top: 10px; left: 10px; }
.gallery-icon-lb-check { top: 10px; right: 10px; }
.gallery-icon-lb-star  { top: 10px; left: 10px; bottom: auto; }

/* ── Submit modal ──────────────────────────────────────────────────── */
.gallery-submit-modal {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,.55);
    z-index: 1100;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sp-4);
}
.gallery-submit-modal[hidden] { display: none; }
.gallery-success-icon {
    font-size: 2.5rem;
    color: var(--gallery-check);
    margin-bottom: var(--sp-3);
}
.gallery-submit-modal-content {
    background: #fff;
    border-radius: 14px;
    padding: var(--sp-5) var(--sp-5) var(--sp-4);
    width: 100%;
    max-width: 520px;
    position: relative;
    box-shadow: 0 12px 32px -8px rgba(0,0,0,.3);
    max-height: calc(100vh - 2 * var(--sp-4));
    overflow-y: auto;       /* scroll if content overflows on tiny phones */
}
@media (max-width: 480px) {
    .gallery-submit-modal-content {
        padding: var(--sp-4);
        max-height: calc(100vh - 2 * var(--sp-3));
    }
    .gallery-submit-modal-content h2 { font-size: 1.1rem; }
    /* Keep "Keep browsing" + "Submit selections" SIDE BY SIDE (centered)
     * instead of stacking. Tyler audit: stacked buttons feel unfinished. */
    .gallery-submit-modal-content .action-row,
    .gallery-submit-modal-content .action-row-end {
        flex-direction: row;
        justify-content: center;
        flex-wrap: nowrap;
        gap: var(--sp-2);
    }
    .gallery-submit-modal-content .action-row .btn {
        flex: 0 1 auto;
        padding-left: var(--sp-3);
        padding-right: var(--sp-3);
        white-space: nowrap;
    }
}
.gallery-submit-modal-content h2 {
    font-size: 1.25rem;
    margin: 0 0 var(--sp-3);
}
.gallery-submit-modal-close {
    position: absolute;
    top: 12px; right: 12px;
    background: transparent; border: 0; padding: 6px 10px;
    color: var(--muted); font-size: 1.1rem; cursor: pointer;
    border-radius: 6px;
}
.gallery-submit-modal-close:hover { background: var(--bg-soft); color: var(--ink); }

.form-check-row {
    display: flex; align-items: center; gap: 10px;
    font-size: .9rem; color: var(--ink);
}
.form-check-row input[type="checkbox"] {
    width: 18px; height: 18px; cursor: pointer;
}

/* Lightbox */
.gallery-lightbox {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,.92);
    z-index: 200;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}
.gallery-lightbox[hidden] { display: none; }
/* Classic theme: fade the lightbox in/out (the JS reads this duration to time
   the close, so other themes stay instant). */
.gallery-theme-classic .gallery-lightbox {
    opacity: 0;
    transition: opacity 250ms ease;
}
.gallery-theme-classic .gallery-lightbox.is-open { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
    .gallery-theme-classic .gallery-lightbox { transition: none; }
}
.gallery-lightbox-stage {
    flex: 1 1 auto;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sp-5);
    overflow: hidden;
    min-height: 0;          /* allow flex child to shrink */
}
/* Viewport-relative constraints — the inline-block imgwrap doesn't
 * propagate the parent's percentage max-* to the img, so we constrain
 * the img directly in viewport units. Matches TP's 80vw/80vh. */
.gallery-lightbox-stage img {
    max-width: 90vw;
    max-height: 82vh;
    width: auto;
    height: auto;
    display: block;
    transition: opacity .25s ease;
}
/* Lightbox caption — counter ABOVE filename (TP layout). Centered stack. */
.gallery-lightbox-caption {
    color: #fff;
    width: 100%;
    padding: var(--sp-3) var(--sp-5);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    font-size: .9rem;
    text-align: center;
}
.gallery-lightbox-counter {
    color: rgba(255,255,255,.55);
    font-size: .78rem;
    font-variant-numeric: tabular-nums;
    letter-spacing: .04em;
}

/* Lightbox nav arrows + close — TP style: icon-only, smaller + brighter
 * than the previous pass. Just a clean white chevron with subtle drop
 * shadow for contrast on any image. */
.gallery-lightbox-btn {
    position: absolute;
    background: transparent;
    border: 0;
    color: rgba(255,255,255,.95);
    width: 44px; height: 44px;
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    text-shadow: 0 1px 3px rgba(0,0,0,.5);
    transition: opacity .15s ease, transform .15s ease;
    z-index: 1;
    padding: 0;
    opacity: .85;
}
.gallery-lightbox-btn:hover { opacity: 1; transform: scale(1.18); color: #fff; }
.gallery-lightbox-close { top: 20px; right: 24px; }
/* Download sits just left of the close X (off the image), same bare top-bar
   style as the other lightbox buttons. Only rendered for static galleries
   with client downloads enabled. */
.gallery-lightbox-download { top: 20px; right: 76px; }
.gallery-lightbox-prev  { left: 18px; top: 50%; transform: translateY(-50%); }
.gallery-lightbox-prev:hover  { left: 18px; top: 50%; transform: translateY(-50%) scale(1.18); }
.gallery-lightbox-next  { right: 18px; top: 50%; transform: translateY(-50%); }
.gallery-lightbox-next:hover  { right: 18px; top: 50%; transform: translateY(-50%) scale(1.18); }

/* Loader spinner shown while a medium variant is fetching. Sits in the
 * middle of the imgwrap. NEVER show a pixelated thumb in place of the
 * medium (Tyler audit) — a brief loader is way cleaner. */
.gallery-lightbox-loader {
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    color: rgba(255,255,255,.45);
    font-size: 2rem;
    z-index: 2;
    pointer-events: none;
}

/* Lightbox mobile — tighter nav button positioning + bigger viewport
 * usage. The 18px edge offset feels far on narrow screens. */
@media (max-width: 700px) {
    .gallery-lightbox-prev { left: 8px; }
    .gallery-lightbox-prev:hover { left: 8px; }
    .gallery-lightbox-next { right: 8px; }
    .gallery-lightbox-next:hover { right: 8px; }
    .gallery-lightbox-close { top: 14px; right: 14px; }
    .gallery-lightbox-download { top: 14px; right: 60px; }
    .gallery-lightbox-stage { padding: var(--sp-3); }
    /* Heart/check inside lightbox imgwrap — slightly smaller on mobile
     * so they don't crowd the photo */
    .gallery-icon-lb { width: 40px; height: 40px; font-size: 22px; }
}
.gallery-lightbox-stage img {
    transition: opacity .25s ease;
}

/* Client gallery uses a slightly looser default — tiles are showcased,
 * not managed. Bigger gap, more "exhibition" feel. */
.gallery-grid-client { gap: 10px; }

/* Project view module — small "filmstrip" preview. Uniform height row so
 * the visual baseline aligns, widths flex with each image's native aspect.
 * Never crops; portrait + landscape mix gracefully. */
.project-module-gallery-preview {
    display: flex;
    flex-wrap: nowrap;
    align-items: stretch;
    gap: 6px;
    margin-bottom: var(--sp-3);
    overflow: hidden;        /* hide any overflow if the row would be too wide */
}
.project-module-gallery-preview-tile {
    position: relative;
    flex: 0 0 auto;
    height: 80px;
    border-radius: 6px;
    overflow: hidden;
    background: var(--bg-soft);
}
.project-module-gallery-preview-tile img {
    width: 100%;
    height: 100%;
    object-fit: cover;       /* fill the JS-sized tile; tile matches the image aspect so there's no real crop */
    display: block;
}

/* Favorites / selections counters on the project-page gallery card. Heart =
   favorites (rose), check = selections (green) — matches the gallery icons. */
.gallery-pick-count {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-variant-numeric: tabular-nums;
}
.gallery-pick-count .fa-heart        { color: var(--gallery-heart); }
.gallery-pick-count .fa-square-check { color: var(--gallery-check); }

/* ── Client Submission card (studio project page) ─────────────────────── */
.client-sub-status {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    flex-wrap: wrap;
    margin-bottom: var(--sp-4);
}
.client-sub-unlock { margin-left: auto; }
.client-sub-group { margin-bottom: var(--sp-4); }
.client-sub-label {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: .72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-light);
    margin-bottom: 6px;
}
.client-sub-label .fa-square-check { color: var(--gallery-check); }
.client-sub-label .fa-heart        { color: var(--gallery-heart); }
.client-sub-list {
    list-style: none;
    margin: 0;
    padding: var(--sp-3) var(--sp-4);
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--bg-soft);
    max-height: 240px;
    overflow-y: auto;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: .85rem;
    line-height: 1.75;
    color: var(--text);
}
/* Favorites is a collapsible <details>; the summary IS the label row. */
.client-sub-fav > summary {
    cursor: pointer;
    list-style: none;
    user-select: none;
}
.client-sub-fav > summary::-webkit-details-marker { display: none; }
.client-sub-fav-chevron { font-size: .65rem; transition: transform .2s ease; color: var(--text-light); }
.client-sub-fav:not([open]) .client-sub-fav-chevron { transform: rotate(-90deg); }
.client-sub-email { font-size: .9rem; margin: var(--sp-4) 0 0; }
.client-sub-notes {
    padding: var(--sp-3) var(--sp-4);
    border-radius: 10px;
    background: #fefce8;
    border: 1px solid #fde68a;
    font-size: .9rem;
    line-height: 1.5;
}
.client-sub-notes.is-empty { color: var(--text-light); }

/* ──────────────────────────────────────────────────────────────
   Image protection — casual right-click / drag / long-press save
   deterrence across every portal surface. Photographer-SaaS table
   stakes; competitors (Pixieset, ShootProof, etc.) all do this.
   Not real DRM — anyone with dev tools or a screenshot can still
   extract the pixels. Goal is just to stop the 90% who
   right-click → Save Image As.

   Right-click is blocked via a JS contextmenu handler in
   portal.js (wireImageProtection). Drag + iOS long-press save
   are blocked declaratively here.
   ────────────────────────────────────────────────────────────── */
img {
    -webkit-user-drag: none;
    user-drag: none;
    -webkit-touch-callout: none;   /* iOS Safari long-press "Save Image" */
    -webkit-user-select: none;
    user-select: none;
}

/* ──────────────────────────────────────────────────────────────
   Deliverables — admin management list + publish bar.
   (Upload dropzone + queue reuse the .gallery-dropzone / .gallery-queue
   styles; only the file list + publish bar are deliverables-specific.)
   Mobile-first; enhanced ≥600px.
   ────────────────────────────────────────────────────────────── */
/* "Download everything" hero + collapsed per-file list */
.deliverables-download-all {
    display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--sp-4);
    padding: var(--sp-5) var(--sp-6); margin-bottom: var(--sp-4);
    background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-lg);
}
.deliverables-download-all-info { display: flex; align-items: center; gap: var(--sp-4); }
.deliverables-download-all-info > i { font-size: 1.5rem; color: var(--brand-accent); flex: 0 0 auto; }
.deliverables-download-all-title { font-size: 1.05rem; font-weight: 700; }
.deliverables-individual > summary {
    cursor: pointer; font-size: .85rem; font-weight: 600; color: var(--text-med);
    padding: var(--sp-2) 0; list-style: revert;
}
.deliverables-individual > summary:hover { color: var(--text); }

.deliverables-list {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    margin-top: var(--sp-4);
}
.deliverable-row {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3);
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 10px;
    text-decoration: none;   /* row is sometimes an <a> (cross-project list) */
    color: inherit;
}

/* Unpublished (not yet sent to the client): amber tint + a "Not sent" chip so it
   stands out against published deliverables in the same project. */
.deliverable-row-draft { background: var(--warn-bg); border-color: var(--warn-border); }
.deliverable-chip-draft { display: none; }
.deliverable-row-draft .deliverable-chip-draft {
    display: inline-flex; align-items: center; margin-left: var(--sp-2);
    padding: 1px 8px; border-radius: var(--radius-pill); vertical-align: 1px;
    background: var(--warn); color: #fff; font-size: .66rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: .04em;
}
a.deliverable-row:hover { border-color: var(--border-strong); }
/* Drill-down row (Files level 1: one shoot per row, links into its files). */
.deliverable-row-chevron { flex: 0 0 auto; color: var(--text-light); font-size: .85rem; }
.deliverable-row-drill:hover .deliverable-row-chevron { color: var(--text-med); }

/* Studio Files tab: a delivery group (one shoot, or the General bucket). */
.files-group { margin-bottom: var(--sp-6); }
.files-group-head { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-3); color: var(--text-med); }
.files-group-title { font-size: 1rem; font-weight: 700; margin: 0; color: var(--text); }
.files-group-manage { margin-left: auto; color: var(--brand-accent-strong); text-decoration: none; }
.files-group-manage:hover { text-decoration: underline; }
.deliverable-row-actions { display: flex; align-items: center; gap: var(--sp-2); flex: 0 0 auto; }
/* Upload modal: the dropzone is inert until a client is picked. */
.files-dropzone.is-disabled { opacity: .5; pointer-events: none; }

/* Expand all / Collapse all toolbar above a stack of collapsible cards. */
.cards-toolbar {
    display: flex;
    justify-content: flex-start;
    margin-bottom: var(--sp-3);
}
.cards-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: none;
    border: none;
    cursor: pointer;
    color: var(--text-light);
    font: inherit;
    font-size: .8rem;
    font-weight: 600;
    padding: 6px 4px;
    border-radius: 6px;
}
.cards-toggle:hover { color: var(--text); }

/* Banner with a text block + an action button (e.g. gallery unlock, "Ready to
   publish"). Becomes a flex row on any color variant; wraps on mobile. */
.status-banner:has(.status-banner-action) { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.status-banner-main   { flex: 1 1 auto; min-width: 0; }
.status-banner-action { flex: 0 0 auto; align-self: center; margin: 0; }
@media (max-width: 599px) {
    .status-banner-action { align-self: stretch; width: 100%; }
    .status-banner-action .btn { width: 100%; justify-content: center; }
}
.deliverable-row-icon {
    color: var(--text-light);
    font-size: 1.1rem;
    flex: 0 0 auto;
}
.deliverable-row-body {
    flex: 1 1 auto;
    min-width: 0;   /* enable truncation */
}
.deliverable-row-name {
    font-weight: 600;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.deliverable-row-meta { margin-top: 2px; }
/* Inline expiry controls on a delivery row (extend / set / remove). */
.deliverable-expiry-act {
    background: none; border: none; padding: 0 2px; margin-left: 8px;
    color: var(--brand-accent-strong); font: inherit; cursor: pointer;
    text-decoration: underline; text-underline-offset: 2px;
}
.deliverable-expiry-act:hover { color: var(--text); }
.deliverable-expiry-act[disabled] { opacity: .5; cursor: default; }
/* Expiry popover: "Set expiry" opens a small panel of preset chips + a custom
   date, anchored to the row. The box is the positioning context. */
.deliverable-expiry { position: relative; }
.expiry-popover {
    position: absolute; top: calc(100% + 6px); left: 0; z-index: 40;
    min-width: 220px; padding: 12px;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 12px; box-shadow: 0 10px 30px rgba(0, 0, 0, .16);
}
.expiry-popover-presets { display: flex; flex-wrap: wrap; gap: 6px; }
.expiry-chip {
    padding: 5px 11px; font: inherit; font-size: .85em; line-height: 1.2;
    color: var(--text-med); background: var(--bg-soft);
    border: 1px solid var(--border); border-radius: 999px; cursor: pointer;
}
.expiry-chip:hover { border-color: var(--brand-accent-strong); color: var(--text); }
.expiry-chip.is-selected {
    color: var(--text-inverse); background: var(--brand-accent-strong);
    border-color: var(--brand-accent-strong);
}
/* The date control is a peer "chip" with a calendar icon + native date field.
   Lights up in the accent color once a date is chosen (chips go unselected). */
.expiry-date-chip {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 5px 11px; font-size: .85em; line-height: 1.2;
    color: var(--text-med); background: var(--bg-soft);
    border: 1px solid var(--border); border-radius: 999px; cursor: pointer;
}
.expiry-date-chip:hover { border-color: var(--brand-accent-strong); color: var(--text); }
.expiry-date-chip i { font-size: .95em; }
.expiry-date-chip.is-selected {
    color: var(--brand-accent-strong); font-weight: 600;
    background: var(--brand-accent-tint, #fff2ec); border-color: var(--brand-accent-strong);
}
.expiry-date-chip input[type="date"] {
    width: 7.4em; padding: 0; margin: 0; border: none; background: none;
    font: inherit; color: inherit; cursor: pointer;
}
.expiry-date-chip input[type="date"]:focus { outline: none; }
.expiry-date-chip input[type="date"]::-webkit-calendar-picker-indicator { display: none; -webkit-appearance: none; }
.expiry-popover-actions {
    display: flex; align-items: center; justify-content: flex-end;
    gap: 14px; margin-top: 12px;
}
.expiry-popover-cancel {
    background: none; border: none; padding: 0; font: inherit; font-size: .85em;
    color: var(--text-light); cursor: pointer;
}
.expiry-popover-cancel:hover { color: var(--text); }
.expiry-popover-set {
    padding: 6px 14px; font: inherit; font-size: .85em; font-weight: 600;
    color: var(--text-inverse); background: var(--brand-accent-strong);
    border: none; border-radius: 8px; cursor: pointer;
}
.expiry-popover-set:hover { filter: brightness(1.06); }
.expiry-popover-set:disabled { opacity: .6; cursor: default; }
@media (max-width: 600px) {
    .expiry-popover { left: 0; right: 0; min-width: 0; }
}
/* Expired delivery on the client side reads muted. */
.deliverable-row.is-expired { opacity: .72; }
/* Publish button never shrinks or wraps — long meta lines must not squeeze it. */
.deliverable-publish-btn { flex: 0 0 auto; white-space: nowrap; }
.deliverable-row-action {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    border: none;
    background: transparent;
    color: var(--text-light);
    border-radius: 8px;
    cursor: pointer;
    text-decoration: none;   /* the download action is an <a> — no underline */
}
.deliverable-row-action:hover { color: var(--text); background: var(--bg-soft); }
.deliverable-row-action-danger:hover { color: var(--danger); }

/* Leading select checkbox (draft rows) + aligned spacer (published rows). */
.deliverable-row-check {
    flex: 0 0 auto;
    width: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}
.deliverable-row-check input { width: 16px; height: 16px; cursor: pointer; accent-color: var(--brand-accent); }
.deliverable-row-check-spacer { cursor: default; }
/* Select column only appears when there are 2+ drafts to batch-publish. */
.deliverables-list:not(.is-multi) .deliverable-row-check { display: none; }

/* Bulk-select bar above the file list. */
.deliverables-bulkbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    padding: var(--sp-2) var(--sp-3);
    margin-bottom: var(--sp-3);
    background: var(--bg-soft);
    border: 1px solid var(--border);
    border-radius: 10px;
}
.deliverables-bulkbar[hidden] { display: none; }   /* author display:flex would otherwise beat [hidden] */
.deliverables-bulkbar-actions { display: flex; align-items: center; gap: var(--sp-3); }

/* "Publish all" bar — the prominent one-click path (drafts → one client .zip) */
.deliverables-publishall {
    display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4); margin-bottom: var(--sp-3);
    background: var(--brand-accent-tint, #fff2ec); border: 1px solid #ffd9c7; border-radius: 10px;
}
.deliverables-publishall[hidden] { display: none; }
.deliverables-publishall-info { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: .88rem; color: var(--text); }
.deliverables-publishall-info > i { color: var(--brand-accent-strong, #c2410c); }
.deliverables-publishall .btn { flex: 0 0 auto; }
@media (max-width: 599px) {
    .deliverables-publishall { flex-direction: column; align-items: stretch; }
    .deliverables-publishall .btn { width: 100%; }
}
.deliverable-row-name-input {
    width: 100%;
    font: inherit;
    font-weight: 600;
    color: var(--text);
    border: 1px solid var(--border-strong);
    border-radius: 6px;
    padding: 2px 6px;
    background: var(--bg);
}


/* ──────────────────────────────────────────────────────────────
   Global search: dropdown "view all" footer + full results page
   ────────────────────────────────────────────────────────────── */
.topbar-search-all {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 12px var(--sp-5);
    border-top: 1px solid var(--border-soft);
    color: var(--brand);
    font-size: .85rem;
    font-weight: 600;
    text-decoration: none;
}
.topbar-search-all:hover { background: var(--bg-soft); }

/* Results page */
.search-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-4);
    border-bottom: 1px solid var(--border);
    margin-bottom: var(--sp-5);
}
.search-tab {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 2px;
    margin-bottom: -1px;
    color: var(--text-light);
    text-decoration: none;
    font-size: .9rem;
    font-weight: 600;
    border-bottom: 2px solid transparent;
}
.search-tab:hover { color: var(--text); }
.search-tab.is-active { color: var(--brand-accent-strong); border-bottom-color: var(--brand-accent); }
.search-tab-count {
    font-size: .72rem;
    font-weight: 600;
    color: var(--text-light);
    background: var(--bg-muted);
    border-radius: 999px;
    padding: 1px 7px;
}

.search-group { margin-bottom: var(--sp-7); }
.search-group-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: var(--sp-3);
}
.search-group-title {
    display: flex; align-items: center; gap: 8px;
    font-size: 1rem; margin: 0;
}
.search-group-title i { color: var(--text-light); }

.search-result-list { display: flex; flex-direction: column; gap: var(--sp-2); }
.search-result {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3);
    border: 1px solid var(--border);
    border-radius: 10px;
    text-decoration: none;
    color: inherit;
}
.search-result:hover { border-color: var(--border-strong); background: var(--bg-soft); }
.search-result-thumb {
    width: 40px; height: 40px; flex: 0 0 auto;
    object-fit: cover; border-radius: 6px; background: var(--bg-muted);
}
.search-result-icon { width: 20px; text-align: center; color: var(--text-light); flex: 0 0 auto; }
.search-result-body { min-width: 0; display: flex; flex-direction: column; }
.search-result-label { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.search-result-sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Flex rows — reads left-to-right (1,2,3…) and keeps each photo's aspect.
   Fixed row height + natural width, wraps; ragged right edge.
   (CSS masonry columns ordered top-to-bottom per column, which broke
   reading order — 1, 18, 36 across the top.) */
.search-photo-grid { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.search-photo {
    display: block;
    height: 150px;
    flex: 0 0 auto;
    border-radius: 8px;
    overflow: hidden;
    background: var(--bg-muted);
    border: 1px solid var(--border);
}
.search-photo img { height: 100%; width: auto; display: block; }

.search-pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-4);
    margin-top: var(--sp-5);
}

/* ──────────────────────────────────────────────────────────────
   Gallery watermark (Tier 1 overlay) + settings panel
   ────────────────────────────────────────────────────────────── */
/* Tiled overlay above each image. --wm-tile is set inline on an ancestor. */
.wm-overlay {
    position: absolute;
    inset: 0;
    background-image: var(--wm-tile);
    background-repeat: repeat;
    /* --wm-tile-size is set (to the logical tile px) only when JS has swapped
       in a hi-DPI PNG tile; left unset it falls back to the SVG's natural size. */
    background-size: var(--wm-tile-size, auto);
    pointer-events: none;
    /* Print the watermark too (browsers skip background images otherwise). */
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
}
/* Hero image carries an explicit z-index:1, so the overlay must sit above it.
   Match the image's rounded corners so the tile doesn't square them off. */
.gallery-hero .wm-overlay { z-index: 2; border-radius: 6px; }
/* Search-result photo thumbs get the same overlay; keep the tile readable small. */
.search-photo { position: relative; }

/* Collapsible "advanced" section inside a settings form (Gallery look,
   Watermark, etc.). Native <details>; caret rotates when open. The whole pass
   toward "feel simple" reuses this to fold occasional knobs out of sight. */
.settings-disclosure {
    border-top: 1px solid var(--border);
    margin-top: var(--sp-4);
    padding-top: var(--sp-4);
}
.settings-disclosure-summary {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    min-height: 32px;
    cursor: pointer;
    list-style: none;
    font-weight: 600;
}
.settings-disclosure-summary::-webkit-details-marker { display: none; }
.settings-disclosure-summary::marker { content: ''; }
.settings-disclosure-caret { display: inline-flex; color: var(--text-muted); transition: transform .15s ease; }
.settings-disclosure[open] > .settings-disclosure-summary .settings-disclosure-caret { transform: rotate(90deg); }
.settings-disclosure-hint { margin-left: auto; font-weight: 400; font-size: var(--fs-sm); color: var(--text-muted); }
.settings-disclosure-body { padding-top: var(--sp-4); }

/* Visual gallery-template picker: radio cards, each an SVG mockup of the style. */
.theme-picker {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--sp-3);
    margin-top: var(--sp-2);
}
.theme-card { position: relative; cursor: pointer; display: block; }
.theme-card input { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }
.theme-card-box {
    display: block;
    border: 2px solid var(--border);
    border-radius: 10px;
    overflow: hidden;
    background: var(--surface, #fff);
    transition: border-color .12s ease, box-shadow .12s ease;
}
.theme-card:hover .theme-card-box { border-color: var(--text-muted); }
.theme-card input:checked ~ .theme-card-box {
    border-color: var(--brand-accent);
    box-shadow: 0 0 0 3px var(--brand-accent-soft, rgba(255, 107, 53, 0.15));
}
.theme-card input:focus-visible ~ .theme-card-box { border-color: var(--brand-accent); }
.theme-card-preview { display: block; }
.theme-card-preview svg { display: block; width: 100%; height: auto; }
.theme-card-label { display: block; padding: var(--sp-2) var(--sp-3); font-size: var(--fs-sm); font-weight: 600; }

.wm-enable-row { display: flex; align-items: center; gap: var(--sp-2); cursor: pointer; font-weight: 600; }
.wm-enable-row input { width: 16px; height: 16px; accent-color: var(--brand-accent); }
.wm-config { margin-top: var(--sp-4); display: grid; grid-template-columns: 1fr; gap: var(--sp-5); }
.wm-config.is-disabled { opacity: .45; pointer-events: none; }
.wm-controls { display: flex; flex-direction: column; gap: var(--sp-4); }
.wm-range { width: 100%; accent-color: var(--brand-accent); }
.wm-preview {
    position: relative;
    min-height: 220px;
    border-radius: 10px;
    overflow: hidden;
    border: 1px solid var(--border);
    background: linear-gradient(135deg, #5f6b78, #97a2ad 55%, #c8cfd6);
}
.wm-preview-label {
    position: absolute; left: 10px; bottom: 8px;
    font-size: .68rem; color: rgba(255,255,255,.85);
    letter-spacing: .05em; text-transform: uppercase;
}
@media (min-width: 700px) {
    .wm-config { grid-template-columns: 1fr 1fr; align-items: start; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   Landing / sales page (.lp-*) — public marketing page. Edit's light system:
   white base, near-black ink, orange accent, Inter. Mobile-first.
   ═══════════════════════════════════════════════════════════════════════════ */
.btn-pill { border-radius: var(--radius-pill); }

.lp { background: var(--bg); color: var(--text); overflow-x: clip; }
.lp .lp-accent { color: var(--brand-accent); font-style: italic; }
.lp-eyebrow {
    font-family: var(--font-mono);
    text-transform: uppercase;
    letter-spacing: .14em;
    font-size: .72rem;
    color: var(--brand-accent);
    margin: 0 0 var(--sp-4);
}
.lp-eyebrow::before { content: "— "; opacity: .8; }

/* ── Header ── */
.lp-header {
    position: sticky;
    top: 0;
    z-index: 50;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    padding: var(--sp-4) var(--sp-5);
    background: rgba(255,255,255,.82);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
    backdrop-filter: saturate(180%) blur(12px);
    border-bottom: 1px solid var(--border-soft);
}
.lp-brand { display: inline-flex; align-items: center; gap: var(--sp-3); text-decoration: none; }
/* In the landing header the brand sits in a space-between flex row, so a
   width change on reveal would shove the centered nav over. Pin the anchor at
   E-width and let the wordmark slide out absolutely (overflowing right) so the
   nav stays put. The reveal animation itself is unchanged. */
.lp-header .brand-toggle { position: relative; }
.lp-header .brand-toggle .brand-wordmark { position: absolute; left: 100%; top: 50%; transform: translateY(-50%); }
.lp-nav { display: none; gap: var(--sp-6); position: relative; }
.lp-nav-link { color: var(--text-med); text-decoration: none; font-size: .92rem; font-weight: 500; transition: color .2s ease; }
.lp-nav-link:hover { color: var(--text); }
.lp-nav-link.is-active { color: var(--brand-accent-strong); }
.lp-nav-underline { position: absolute; bottom: -7px; left: 0; width: 0; height: 2px; border-radius: 2px; background: var(--brand-accent); opacity: 0; transform: translateX(0); transition: transform .28s cubic-bezier(.4, 0, .2, 1), width .28s cubic-bezier(.4, 0, .2, 1), opacity .2s ease; pointer-events: none; }
.lp-nav-underline.is-visible { opacity: 1; }
.lp-header-cta { font-size: .85rem; padding: 8px 18px; }
.lp-header-actions { display: inline-flex; align-items: center; gap: var(--sp-3); }
.lp-login { display: inline-flex; align-items: center; gap: 6px; color: var(--text-med); text-decoration: none; font-size: .9rem; font-weight: 500; }
.lp-login:hover { color: var(--text); }
.lp-login i { font-size: 1.1rem; line-height: 1; }
.lp-login-label { display: none; }
@media (min-width: 700px) { .lp-login-label { display: inline; } }

/* ── Hero ── */
.lp-hero {
    max-width: 1200px;
    margin: 0 auto;
    padding: var(--sp-9) var(--sp-5) var(--sp-10);
    display: grid;
    /* minmax(0,1fr) (not bare 1fr) so the track can shrink below its content's
       natural width; otherwise the peek's fixed columns force the whole hero
       wider than a phone and everything clips off the right edge. */
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-9);
    align-items: center;
}
.lp-hero > * { min-width: 0; }
.lp-hero-headline {
    font-size: 2.6rem;
    font-weight: 800;
    line-height: 1.04;
    letter-spacing: -.03em;
    margin: 0 0 var(--sp-5);
    color: var(--brand);
}
.lp-hero-sub {
    font-size: 1.1rem;
    line-height: 1.55;
    color: var(--text-med);
    margin: 0 0 var(--sp-4);
    max-width: 34rem;
}
.lp-hero-sub strong { color: var(--text); font-weight: 600; }
/* The "next shoot, no migration" hook: a value-prop line, not fine print. Sits
   between the description and the primary CTA. */
.lp-hero-pitch {
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--text);
    margin: 0 0 var(--sp-7);
    max-width: 34rem;
}

/* ── Waitlist form ── */
.lp-waitlist { max-width: 34rem; }
.lp-waitlist .form-control { margin-bottom: var(--sp-3); }
/* minmax(0,1fr) + min-width:0 so a wide child (esp. the iOS <select>, which sizes
   to its longest option) can't expand the track past the viewport and force
   page-wide horizontal overflow. */
.lp-waitlist-row { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-3); }
.lp-waitlist-row > * { min-width: 0; max-width: 100%; }
.lp-waitlist-submit { margin-top: var(--sp-2); }
.lp-waitlist-note {
    font-family: var(--font-mono);
    font-size: .76rem;
    color: var(--text-light);
    margin: var(--sp-3) 0 0;
}
.lp-waitlist-note i { color: var(--success); margin-right: 4px; }

/* ── "Request private beta trial" CTA + modal ──────────────────────────────── */
.lp-beta-cta { margin: 0 0 var(--sp-6); }
.lp-beta-cta-note { margin: var(--sp-2) 0 0; color: var(--text-med); font-size: .85rem; }
/* The beta invite is the primary action: it pops in the brand accent and sits a
   size up from the demoted (secondary) waitlist button below it. */
.lp-beta-cta-btn { background: var(--brand-accent); border-color: var(--brand-accent); color: #0a0a0a; font-size: .98rem; padding: 12px 26px; }
.lp-beta-cta-btn:hover { background: #ff7d4d; border-color: #ff7d4d; }
.lp-waitlist-lead { margin: 0 0 var(--sp-3); color: var(--text-med); font-size: .9rem; font-weight: 600; }
/* Gray the source dropdown while it still shows the "(optional)" placeholder,
   so it reads muted like the text inputs beside it until a real option is picked. */
.lp-waitlist-source:has(option[value=""]:checked) { color: var(--text-light); }

/* Off-screen honeypot — present for bots, invisible + unfocusable for humans. */
.lp-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* CSS-only modal: shown when its id is the URL :target (the CTA links to it). */
.lp-modal {
    position: fixed; inset: 0; z-index: 1000;
    display: none; align-items: center; justify-content: center;
    padding: var(--sp-4);
}
.lp-modal:target { display: flex; }
.lp-modal-backdrop {
    position: absolute; inset: 0; background: rgba(10,10,10,.55);
    backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
}
.lp-modal-card {
    position: relative; z-index: 1; width: 100%; max-width: 440px;
    max-height: 92vh; overflow-y: auto;
    background: #fff; border-radius: 16px; padding: clamp(22px, 4vw, 36px);
    box-shadow: 0 24px 64px rgba(0,0,0,.32);
    animation: lp-modal-pop .18s ease;
}
@keyframes lp-modal-pop { from { transform: translateY(8px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
.lp-modal-close {
    position: absolute; top: 10px; right: 14px; z-index: 2;
    font-size: 1.7rem; line-height: 1; color: var(--text-light); text-decoration: none;
}
.lp-modal-close:hover { color: var(--text); }
.lp-modal-title { margin: 0 0 8px; font-size: 1.5rem; font-weight: 800; line-height: 1.15; }
.lp-modal-sub { margin: 0 0 var(--sp-4); color: var(--text-med); font-size: .95rem; line-height: 1.5; }
.lp-modal-form .form-field { margin-bottom: 12px; }
.lp-modal-fine { margin: var(--sp-3) 0 0; text-align: center; color: var(--text-light); font-size: .8rem; }
.lp-modal-grouphint { margin: 0 0 10px; color: var(--text-med); font-size: .85rem; }
.lp-modal-err { margin: 0 0 10px; color: var(--danger); font-size: .85rem; font-weight: 600; }
@media (prefers-reduced-motion: reduce) { .lp-modal-card { animation: none; } }

/* Phones: the beta form is tall (6 fields + copy). Tighten everything and let
   the card use the real (dynamic) viewport height so it fits with minimal
   scroll and the title stays visible on open. Inputs keep their 44px height. */
@media (max-width: 600px) {
    .lp-modal { padding: 10px; align-items: flex-start; }
    .lp-modal-card {
        max-height: calc(100dvh - 20px);
        padding: 20px 18px;
        border-radius: 14px;
    }
    .lp-modal-title { font-size: 1.25rem; margin: 0 0 6px; }
    .lp-modal-sub   { font-size: .9rem; margin: 0 0 14px; }
    .lp-modal-form .form-field  { margin-bottom: 9px; }
    .lp-modal-form .form-label  { margin-bottom: 4px; }
    .lp-modal-grouphint { margin: 2px 0 8px; }
    .lp-modal-fine  { margin: 10px 0 0; }
}

/* ── Product peek ── */
.lp-peek { display: flex; justify-content: center; }
.lp-peek-card {
    width: 100%;
    max-width: 30rem;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}
.lp-peek-bar {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: var(--sp-3) var(--sp-4);
    background: var(--bg-soft);
    border-bottom: 1px solid var(--border-soft);
}
.lp-peek-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--border-strong); }
.lp-peek-title { margin-left: var(--sp-3); font-family: var(--font-mono); font-size: .72rem; color: var(--text-light); }
/* Hero waitlist button is intentionally SECONDARY (quiet) so the orange beta
   invite above is the clear primary action. Look comes from .btn-secondary. */

/* Hero mockup: gentle entrance + a seamless "live feed" scroll of the rows. */
.lp-peek-card { animation: lpPeekIn .6s cubic-bezier(.2, .8, .2, 1) both; }
@keyframes lpPeekIn { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
.lp-peek-body {
    position: relative; height: 228px; overflow: hidden; padding: var(--sp-3) var(--sp-3) 0;
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 16px, #000 calc(100% - 20px), transparent 100%);
            mask-image: linear-gradient(to bottom, transparent 0, #000 16px, #000 calc(100% - 20px), transparent 100%);
}
.lp-peek-track { display: flex; flex-direction: column; will-change: transform; animation: lpPeekScroll 22s linear infinite; }
.lp-peek-track .lp-peek-row { margin-bottom: var(--sp-3); }
@keyframes lpPeekScroll { from { transform: translateY(0); } to { transform: translateY(-50%); } }
.lp-peek-card:hover .lp-peek-track { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
    .lp-peek-card { animation: none; }
    .lp-peek-track { animation: none; }
}
.lp-peek-row {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3);
    border: 1px solid var(--border-soft);
    border-radius: var(--radius);
}
.lp-peek-avatar {
    flex: 0 0 auto;
    width: 34px; height: 34px;
    border-radius: 8px;
    display: inline-flex; align-items: center; justify-content: center;
    background: rgba(255,107,53,.12);
    color: var(--brand-accent);
    font-weight: 700;
    font-size: .9rem;
}
.lp-peek-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.lp-peek-name { font-weight: 600; font-size: .9rem; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lp-peek-meta { font-family: var(--font-mono); font-size: .7rem; color: var(--text-light); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lp-peek-chip {
    flex: 0 0 auto;
    font-family: var(--font-mono);
    font-size: .62rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    font-weight: 600;
    padding: 4px 9px;
    border-radius: var(--radius-pill);
}
.lp-peek-chip-amber { background: rgba(255,107,53,.12); color: #b45309; }
.lp-peek-chip-green { background: var(--success-bg); color: var(--success); }
.lp-peek-chip-ink   { background: var(--brand); color: #fff; }
.lp-peek-foot {
    display: flex;
    justify-content: space-between;
    padding: var(--sp-3) var(--sp-4);
    border-top: 1px solid var(--border-soft);
    font-family: var(--font-mono);
    font-size: .68rem;
    color: var(--text-light);
}
/* Mock Projects screen: page header + per-row client line, compact status chip,
   and shoot date, so the peek reads like the real /portal/projects list. */
.lp-peek-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: var(--sp-4) var(--sp-4) var(--sp-3);
    border-bottom: 1px solid var(--border-soft);
}
.lp-peek-h1 { font-size: 1.05rem; font-weight: 800; color: var(--brand); letter-spacing: -.01em; }
.lp-peek-newbtn {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: .7rem; font-weight: 600;
    padding: 5px 11px; border-radius: var(--radius-pill);
    background: var(--brand-accent); color: #0a0a0a;
}
.lp-peek-newbtn i { width: 13px; height: 13px; }
.lp-peek-client {
    font-family: var(--font-mono); font-size: .7rem; color: var(--text-light);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.lp-peek-status.status-chip { flex: 0 0 auto; font-size: .64rem; padding: .22rem .55rem; font-weight: 600; }
.lp-peek-date {
    flex: 0 0 auto; width: 46px; text-align: right;
    font-family: var(--font-mono); font-size: .68rem; color: var(--text-light);
}

/* ── Demo video (vertical Reel, phone-framed) ── */
.lp-video { display: flex; justify-content: center; }
.lp-video-frame {
    position: relative;
    width: 100%;
    max-width: 320px;
    aspect-ratio: 9 / 16;
    border-radius: 26px;
    overflow: hidden;
    background: var(--bg-dark);
    border: 1px solid var(--border);
    box-shadow: var(--shadow-lg);
}
.lp-video-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

/* ── Replaces strip ── */
.lp-replaces {
    border-top: 1px solid var(--border-soft);
    border-bottom: 1px solid var(--border-soft);
    padding: var(--sp-6) var(--sp-5);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--sp-3) var(--sp-5);
    text-align: center;
}
.lp-replaces-label {
    font-family: var(--font-mono);
    text-transform: uppercase;
    letter-spacing: .12em;
    font-size: .7rem;
    color: var(--text);
    font-weight: 600;
}
.lp-replaces-list {
    list-style: none;
    margin: 0; padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-3) var(--sp-5);
    justify-content: center;
}
.lp-replaces-list li {
    font-family: var(--font-mono);
    font-size: .82rem;
    color: var(--text-light);
    text-decoration: line-through;
    text-decoration-color: var(--brand-accent);
}

/* ── Generic section ── */
.lp-section { max-width: 1100px; margin: 0 auto; padding: var(--sp-10) var(--sp-5); }
.lp-section-alt {
    max-width: none;
    background: var(--bg-soft);
    border-top: 1px solid var(--border-soft);
    border-bottom: 1px solid var(--border-soft);
}
.lp-section-alt > * { max-width: 1100px; margin-left: auto; margin-right: auto; }
.lp-section-head { max-width: 40rem; margin: 0 auto var(--sp-9); text-align: center; }
.lp-section-title {
    font-size: 2rem;
    font-weight: 800;
    letter-spacing: -.02em;
    line-height: 1.1;
    margin: 0 0 var(--sp-4);
    color: var(--brand);
}
.lp-section-sub { font-size: 1.05rem; color: var(--text-med); line-height: 1.5; margin: 0; }

/* Client-side section: copy + a client's-eye portal peek, side by side. */
.lp-clientside { display: grid; grid-template-columns: 1fr; gap: clamp(28px, 4vw, 56px); align-items: center; }
.lp-clientside-copy { text-align: left; }
.lp-clientside-copy .lp-section-title { text-align: left; }
.lp-clientside-list { list-style: none; margin: var(--sp-6) 0 0; padding: 0; display: grid; gap: var(--sp-4); }
.lp-clientside-list li { display: flex; gap: 12px; align-items: flex-start; font-size: 1rem; color: var(--text-med); line-height: 1.5; }
.lp-clientside-list li svg, .lp-clientside-list li i { width: 20px; height: 20px; color: var(--brand-accent); flex: 0 0 auto; margin-top: 3px; }
.lp-clientside-list b { color: var(--text); font-weight: 600; }
.lp-peek-static { animation: none !important; }
@media (min-width: 900px) { .lp-clientside { grid-template-columns: 1.02fr .98fr; } }

/* ── Features grid ── */
/* Flex (not grid) so a partial last row centers instead of left-aligning.
   Full rows still fill exactly: n*basis + (n-1)*gap = 100%. */
.lp-features { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-5); }
.lp-features > .lp-feature { flex: 0 1 100%; }
.lp-feature {
    padding: var(--sp-6);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: var(--bg);
}
.lp-feature-icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 42px; height: 42px;
    border-radius: 10px;
    background: rgba(255,107,53,.12);
    color: var(--brand-accent);
    font-size: 1.05rem;
    margin-bottom: var(--sp-4);
}
.lp-feature-title { font-size: 1.05rem; font-weight: 700; margin: 0 0 var(--sp-2); color: var(--brand); }
.lp-feature-blurb { font-size: .92rem; line-height: 1.5; color: var(--text-med); margin: 0; }

/* Cost comparison ("your whole stack, for half the price") */
.lp-compare-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-4);
    max-width: 56rem;
    margin: 0 auto var(--sp-7);
}
.lp-compare-card {
    position: relative;
    padding: var(--sp-6);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: var(--bg);
    text-align: left;
}
.lp-compare-card-featured { border: 2px solid var(--brand-accent); }
.lp-compare-badge {
    position: absolute;
    top: -10px;
    left: var(--sp-5);
    background: var(--brand-accent-tint);
    color: var(--brand-accent-strong);
    font-family: var(--font-mono);
    text-transform: uppercase;
    letter-spacing: .1em;
    font-size: .64rem;
    font-weight: 600;
    padding: 2px 10px;
    border-radius: 6px;
}
.lp-compare-head { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-4); }
.lp-compare-icon { color: var(--brand-accent-strong); font-size: 1.2rem; line-height: 1; }
.lp-compare-tier { font-weight: 700; font-size: 1.05rem; color: var(--text); }
.lp-compare-stack { font-size: .9rem; color: var(--text-med); margin: 0 0 var(--sp-2); }
.lp-compare-stack s { color: var(--text); font-weight: 600; }
.lp-compare-price { margin: 0 0 var(--sp-2); font-size: 2rem; font-weight: 800; letter-spacing: -.025em; color: var(--brand-accent-strong); line-height: 1; }
.lp-compare-price span { font-size: .9rem; font-weight: 500; color: var(--text-med); letter-spacing: 0; }
.lp-compare-save {
    display: inline-block;
    background: var(--brand-accent-tint);
    color: var(--brand-accent-strong);
    font-size: .85rem;
    font-weight: 700;
    padding: 3px 10px;
    border-radius: 6px;
    margin: 0 0 var(--sp-2);
}
.lp-compare-meta { font-size: .8rem; color: var(--text-med); margin: 0; font-family: var(--font-mono); }
.lp-compare-tagline { font-size: .85rem; color: var(--text-med); line-height: 1.4; margin: 0 0 var(--sp-4); min-height: 2.4rem; }
.lp-compare-specs {
    font-family: var(--font-mono);
    font-size: .76rem;
    color: var(--text-med);
    margin: var(--sp-4) 0 var(--sp-3);
    padding-top: var(--sp-4);
    border-top: 1px solid var(--border);
}
.lp-compare-lead { font-size: .82rem; font-weight: 600; color: var(--text); margin: 0 0 var(--sp-2); }
.lp-compare-features { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
.lp-compare-features li { display: flex; align-items: flex-start; gap: 7px; font-size: .88rem; color: var(--text-med); line-height: 1.4; }
.lp-compare-features li i { color: var(--brand-accent); font-size: .95rem; line-height: 1.4; flex-shrink: 0; }
.lp-compare-plan-features { margin-top: var(--sp-4); padding-top: var(--sp-4); border-top: 1px solid var(--border); }

/* Monthly / annual toggle + per-interval price visibility */
.lp-free-badge {
    display: flex;
    width: fit-content;
    align-items: center;
    gap: var(--sp-2);
    margin: 0 auto var(--sp-6);
    padding: var(--sp-2) var(--sp-4);
    border-radius: 999px;
    background: var(--brand-accent-tint);
    border: 1px solid var(--brand-accent);
    color: var(--brand-accent-strong);
    font-size: .9rem;
    line-height: 1.3;
    text-align: left;
}
.lp-free-badge i { width: 18px; height: 18px; flex: none; }
.lp-free-badge strong { font-weight: 700; }
/* First Light: cohort banner + filling spots meter on the pricing section. */
.lp-fl-banner { display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); margin: 0 auto var(--sp-6); }
.lp-free-badge-fl { margin-bottom: 0; }
.lp-fl-meter { width: 100%; max-width: 22rem; text-align: center; }
.lp-fl-meter-track { height: 8px; border-radius: 999px; background: var(--brand-accent-tint); overflow: hidden; }
.lp-fl-meter-fill { display: block; height: 100%; border-radius: 999px; background: var(--brand-accent); transition: width .4s ease; }
.lp-fl-meter-label { margin: var(--sp-2) 0 0; font-size: .82rem; color: var(--text-med); }
.lp-fl-meter-label strong { color: var(--brand-accent-strong); font-weight: 700; }
.lp-fl-hero-note { margin: var(--sp-3) 0 0; font-size: .85rem; color: var(--text-med); }
.lp-fl-hero-note strong { color: var(--brand-accent-strong); font-weight: 700; }
.lp-toggle-wrap { text-align: center; margin: 0 0 var(--sp-7); }
.lp-toggle { display: inline-flex; gap: 3px; padding: 3px; border: 1px solid var(--border); border-radius: 999px; }
.lp-toggle-btn { border: 0; background: transparent; border-radius: 999px; padding: 7px 18px; font-size: .9rem; font-weight: 600; color: var(--text-med); cursor: pointer; }
.lp-toggle-btn.is-active { background: var(--ink); color: #fff; }
.lp-toggle-note { font-size: .8rem; color: var(--text-med); margin: var(--sp-2) 0 0; }
.lp-pi-a { display: none; }
.lp-compare-billed { display: none; font-size: .72rem; color: var(--text-med); margin: -2px 0 var(--sp-2); }
#pricing.is-annual .lp-compare-price.lp-pi-m,
#pricing.is-annual .lp-compare-save.lp-pi-m { display: none; }
#pricing.is-annual .lp-compare-price.lp-pi-a { display: block; }
#pricing.is-annual .lp-compare-save.lp-pi-a { display: inline-block; }
#pricing.is-annual .lp-compare-billed { display: block; }

/* Free-first pricing (First Light / trial): a bold FREE hero leads the card,
   then the post-trial price. During First Light that price is the locked-in
   50%-off rate, with the full price struck through. */
.lp-compare-free { margin: 0; font-size: 1.9rem; font-weight: 800; letter-spacing: -.025em; line-height: 1.05; color: var(--brand-accent-strong); }
.lp-compare-free-sub { margin: 3px 0 var(--sp-3); font-size: .8rem; color: var(--text-med); }
.lp-compare-then { margin: 0 0 var(--sp-2); font-size: .95rem; color: var(--text-med); }
.lp-compare-then strong { color: var(--text); font-weight: 800; font-size: 1.15rem; }
.lp-compare-then .lp-then-per { font-size: .82rem; font-weight: 500; color: var(--text-med); }
.lp-compare-then s { color: var(--text-light); font-weight: 500; margin-left: 5px; }
.lp-compare-lockin { display: flex; align-items: center; gap: 5px; margin: 0 0 var(--sp-2); font-size: .78rem; font-weight: 700; color: var(--brand-accent-strong); }
.lp-compare-lockin i { width: 13px; height: 13px; flex: none; }
.lp-then-a { display: none; }
#pricing.is-annual .lp-then-m { display: none; }
#pricing.is-annual .lp-then-a { display: inline; }

/* Contextual "see more" link under a landing section: gives the on-page
   Features and Workflow teasers a clean path into the full /features and
   /getting-started pages. */
.lp-section-more { text-align: center; margin: var(--sp-7) 0 0; }
.lp-more-link { display: inline-flex; align-items: center; gap: 6px; font-size: .95rem; font-weight: 600; color: var(--brand-accent-strong); text-decoration: none; }
.lp-more-link i { width: 17px; height: 17px; transition: transform .15s ease; }
.lp-more-link:hover { text-decoration: underline; }
.lp-more-link:hover i { transform: translateX(3px); }
.lp-compare-footnote { max-width: 44rem; margin: var(--sp-5) auto 0; text-align: center; font-size: .78rem; color: var(--text-med); line-height: 1.5; }
.lp-compare-replaces {
    list-style: none;
    margin: 0 auto var(--sp-6);
    padding: 0;
    max-width: 56rem;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--sp-3) var(--sp-5);
}
.lp-compare-replaces li { display: flex; align-items: center; gap: 7px; font-size: .92rem; color: var(--text-med); }
.lp-compare-replaces li i { color: var(--brand-accent); font-size: 1rem; line-height: 1; }
.lp-compare-seat {
    max-width: 44rem;
    margin: 0 auto var(--sp-7);
    display: flex;
    gap: 9px;
    align-items: flex-start;
    justify-content: center;
    text-align: left;
    font-size: .92rem;
    color: var(--text-med);
    line-height: 1.5;
}
.lp-compare-seat i { color: var(--brand-accent); font-size: 1.1rem; flex-shrink: 0; line-height: 1.5; }
.lp-compare-cta { text-align: center; }
.lp-compare-cta-note { font-size: .85rem; color: var(--text-med); margin: var(--sp-3) 0 0; }

/* First Light scarcity above the CTA: a finite, 20-seat deal. Bold accent text
   with a soft pulsing dot (not a pill, so it doesn't echo the button below it). */
.lp-fl-scarcity {
    display: flex;
    width: fit-content;
    align-items: center;
    gap: 8px;
    margin: 0 auto var(--sp-3);
    color: var(--brand-accent-strong);
    font-size: 1.08rem;
    font-weight: 700;
    letter-spacing: -.01em;
}
.lp-fl-scarcity strong { font-weight: 800; }
.lp-fl-scarcity-dot {
    width: 9px; height: 9px; flex: none; border-radius: 50%;
    background: var(--brand-accent-strong);
    animation: lpScarcityPulse 2s ease-out infinite;
}
@keyframes lpScarcityPulse {
    0%   { box-shadow: 0 0 0 0 rgba(255, 107, 53, .55); }
    70%  { box-shadow: 0 0 0 9px rgba(255, 107, 53, 0); }
    100% { box-shadow: 0 0 0 0 rgba(255, 107, 53, 0); }
}
@media (prefers-reduced-motion: reduce) {
    .lp-fl-scarcity-dot { animation: none; }
}

/* RAW Traffic — US heatmap container */
.traffic-map { width: 100%; height: 460px; }
@media (min-width: 760px) {
    .lp-compare-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ── Workflow steps ── */
.lp-steps { list-style: none; margin: 0 auto; padding: 0; max-width: 1000px; display: grid; grid-template-columns: 1fr; gap: var(--sp-6); }
.lp-step { position: relative; }
.lp-step-num {
    display: inline-flex; align-items: center; justify-content: center;
    width: 32px; height: 32px;
    border-radius: 50%;
    background: var(--brand);
    color: #fff;
    font-weight: 700;
    font-size: .9rem;
    margin-bottom: var(--sp-3);
}
.lp-step-title { font-size: 1.05rem; font-weight: 700; margin: 0 0 var(--sp-1); color: var(--brand); }
.lp-step-blurb { font-size: .9rem; line-height: 1.5; color: var(--text-med); margin: 0; }

/* ── Why grid ── */
.lp-why { display: grid; grid-template-columns: 1fr; gap: var(--sp-6); }
.lp-why-title { font-size: 1.05rem; font-weight: 700; margin: 0 0 var(--sp-2); color: var(--brand); display: flex; align-items: center; gap: var(--sp-3); }
.lp-why-title i { color: var(--brand-accent); font-size: .95rem; }
.lp-why-blurb { font-size: .95rem; line-height: 1.55; color: var(--text-med); margin: 0; }

/* ── Founder ── */
.lp-founder {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--sp-6);
    align-items: center;
    max-width: 56rem;
    margin: 0 auto;
    text-align: center;
}
.lp-founder-photo { display: flex; justify-content: center; }
.lp-founder-photo img {
    width: 160px; height: 160px;
    border-radius: 50%;
    object-fit: cover;
    border: 4px solid var(--bg);
    box-shadow: var(--shadow-lg);
}
.lp-founder-title { font-size: 1.6rem; font-weight: 800; letter-spacing: -.02em; line-height: 1.14; margin: 0 0 var(--sp-4); color: var(--brand); }
.lp-founder-text { font-size: 1rem; line-height: 1.6; color: var(--text-med); margin: 0 0 var(--sp-4); }
.lp-founder-sign { font-size: .95rem; color: var(--text); font-weight: 600; margin: var(--sp-3) 0 0; }
.lp-founder-sign a { color: var(--brand-accent); text-decoration: none; }
.lp-founder-sign a:hover { text-decoration: underline; }

/* ── No-migration / "try your next shoot" CTA ── */
.lp-noswitch-cta { text-align: center; margin-top: var(--sp-2); }

/* ── Closing CTA (dark band) ── */
.lp-cta {
    background: var(--bg-dark);
    color: var(--text-inverse);
    text-align: center;
    padding: var(--sp-11) var(--sp-5);
}
.lp-cta-title { font-size: 2rem; font-weight: 800; letter-spacing: -.02em; margin: 0 0 var(--sp-4); color: #fff; }
.lp-cta-sub { font-size: 1.05rem; color: rgba(255,255,255,.7); margin: 0 0 var(--sp-7); }
.lp-cta-form {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    max-width: 30rem;
    margin: 0 auto var(--sp-4);
}
.lp-cta-form .form-control { background: #fff; border-color: transparent; }
.lp-cta-submit { background: var(--brand-accent); border-color: var(--brand-accent); color: #0a0a0a; }
.lp-cta-submit:hover { background: #ff7d4d; border-color: #ff7d4d; }
.lp-cta .lp-waitlist-note { color: rgba(255,255,255,.55); }

/* ── Footer ── */
.lp-footer {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-8) var(--sp-5);
    text-align: center;
}
.lp-footer-tag { font-family: var(--font-mono); font-size: .76rem; color: var(--text-light); }
.lp-footer-built { margin-top: var(--sp-3); font-size: .8rem; color: var(--text-med); }
.lp-footer-heart { color: var(--brand-accent); font-style: normal; }
.lp-footer-copy { font-size: .72rem; color: var(--text-light); }
.lp-footer-author { color: var(--text-med); text-decoration: underline; text-underline-offset: 2px; }
.lp-footer-author:hover { color: var(--brand-accent); }

/* ── Responsive ── */
@media (min-width: 480px) {
    .lp-waitlist-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
    .lp-cta-form { flex-direction: row; }
    .lp-cta-form .form-control { flex: 1; }
}
@media (min-width: 700px) {
    .lp-features > .lp-feature { flex-basis: calc((100% - var(--sp-5)) / 2); }
    .lp-why { grid-template-columns: 1fr 1fr; }
    .lp-steps { grid-template-columns: repeat(5, 1fr); gap: var(--sp-5); }
    .lp-hero-headline { font-size: 3.2rem; }
    .lp-section-title, .lp-cta-title { font-size: 2.4rem; }
    .lp-founder { grid-template-columns: 184px 1fr; gap: var(--sp-8); text-align: left; }
    .lp-founder-photo img { width: 184px; height: 184px; }
    .lp-founder-title { font-size: 2rem; }
}
@media (min-width: 900px) {
    .lp-nav { display: flex; }
    .lp-hero { grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr); padding-top: var(--sp-11); padding-bottom: var(--sp-12); }
    .lp-hero-headline { font-size: 3.6rem; }
}
@media (min-width: 1000px) {
    .lp-features > .lp-feature { flex-basis: calc((100% - 3 * var(--sp-5)) / 4); }
}

/* Landing page: smooth anchor scrolling + offset for the sticky header. */
html:has(.lp) { scroll-behavior: smooth; }
.lp [id] { scroll-margin-top: 76px; }

/* ── Storage usage card (Studio settings) ───────────────────────────────── */
.usage-card {
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: var(--sp-5) var(--sp-6);
    margin-bottom: var(--sp-6);
    max-width: 940px;
}
.usage-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sp-4);
    margin-bottom: var(--sp-4);
}
.usage-title { font-size: 1rem; font-weight: 700; margin: 0; color: var(--text); }
.usage-sub { font-size: .85rem; color: var(--text-light); margin: 2px 0 0; }
.usage-figure { text-align: right; white-space: nowrap; }
.usage-used { font-size: 1.15rem; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; }
.usage-quota { font-size: .82rem; color: var(--text-light); margin-left: 4px; }
.usage-meter {
    height: 10px;
    border-radius: var(--radius-pill);
    background: var(--bg-muted);
    overflow: hidden;
}
.usage-bar {
    height: 100%;
    border-radius: var(--radius-pill);
    background: var(--brand);                 /* ink fill (calm/neutral) */
    transition: width .3s ease;
}
/* Guarantee a visible sliver whenever there's any usage, even sub-1%. */
.usage-bar-nonzero { min-width: 4px; }
.usage-warn .usage-bar { background: var(--warn); }
.usage-over .usage-bar { background: var(--danger); }
.usage-detail { font-size: .85rem; color: var(--text); margin: var(--sp-3) 0 0; }
.usage-over-note { display: block; color: var(--danger); margin-top: 4px; font-weight: 600; }

/* ── Calendar (studio) ────────────────────────────────────────────────────── */
.cal-nav { gap: var(--sp-2); align-items: center; flex-wrap: wrap; }
.cal-monthnav { display: inline-flex; align-items: center; gap: var(--sp-2); }
.cal-month-label { font-weight: 600; min-width: 9.5rem; text-align: center; }

.cal-legend { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap; margin-bottom: var(--sp-4); }
.cal-legend-item { display: inline-flex; align-items: center; gap: 6px; font-size: .8rem; color: var(--text-med); }
.cal-legend-hint { margin-left: auto; }

/* Subscribe-to-calendar modal */
.cal-sub-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.cal-sub-actions .btn { flex: 1 1 auto; justify-content: center; }
.cal-sub-foot { justify-content: space-between; }
[data-cal-feed-url] { flex: 1 1 auto; min-width: 0; }

.cal-dot { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; }
.cal-dot-shoot    { background: var(--info); }
.cal-dot-deadline { background: var(--brand-accent); }
.cal-dot-invoice  { background: var(--danger); }
.cal-dot-job      { background: var(--brand-accent); }

/* Month grid — 1px gap on a border-colored backdrop draws the gridlines. */
.cal-grid {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 1px;
    background: var(--border);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    overflow: hidden;
}
.cal-weekday {
    background: var(--bg-soft);
    padding: var(--sp-2);
    text-align: center;
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    font-weight: 600;
    color: var(--text-light);
}
.cal-cell {
    background: #fff;
    min-height: 7.25rem;
    padding: var(--sp-2);
    display: flex;
    flex-direction: column;
    gap: 4px;
    cursor: pointer;
    transition: background .12s ease;
}
.cal-cell:hover { background: var(--bg-soft); }
.cal-cell:focus-visible { outline: 2px solid var(--brand-accent); outline-offset: -2px; }
.cal-cell-empty { background: var(--bg-soft); cursor: default; }
.cal-cell-empty:hover { background: var(--bg-soft); }
.cal-daynum {
    font-size: .8rem;
    font-weight: 500;
    color: var(--text-med);
    align-self: flex-start;
}
.cal-cell.is-today .cal-daynum {
    background: var(--brand);
    color: #fff;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.cal-events { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.cal-ev {
    display: block;
    font-size: .72rem;
    line-height: 1.3;
    padding: 2px 6px;
    border-radius: 5px;
    text-decoration: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.cal-ev-shoot           { background: var(--info-bg);   color: var(--info); }
.cal-ev-shoot-tentative { background: var(--bg-muted);  color: var(--text-med); }
.cal-ev-deadline        { background: var(--brand-accent-tint); color: var(--brand-accent-strong); }
.cal-ev-invoice         { background: var(--danger-bg); color: var(--danger); }
.cal-ev-job             { background: var(--brand-accent-tint); color: var(--brand-accent-strong); font-weight: 600; }
/* Custom studio note — a button, so reset the button chrome and match .cal-ev. */
.cal-ev-note {
    background: var(--warn-bg);
    color: var(--warn);
    border: 1px solid var(--warn-border);
    width: 100%;
    text-align: left;
    font: inherit;
    font-size: .72rem;
    cursor: pointer;
}
.cal-ev-note:hover { filter: brightness(.97); }
.cal-dot-note { background: var(--warn); }

/* Live month swap: dim the grid while the new month loads. */
.cal-grid.is-loading { opacity: .5; pointer-events: none; transition: opacity .12s ease; }

/* Job: "add existing projects" checklist. */
.job-pick-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.job-pick-list > li { margin: 0; }
.job-pick-list .form-check-row { padding: 8px 10px; border-radius: var(--radius-sm); }
.job-pick-list .form-check-row:hover { background: var(--bg-soft); }
.cal-more { font-size: .68rem; color: var(--text-light); padding: 0 6px; }

@media (max-width: 700px) {
    .cal-cell { min-height: 4.75rem; padding: 4px; }
    .cal-weekday { font-size: .6rem; padding: 4px; }
    .cal-ev { font-size: .62rem; padding: 1px 4px; }
    .cal-legend-hint { display: none; }

    /* Buttons (Subscribe/Today) on one row; month nav wraps to its own full
       row beneath so nothing runs off-screen. */
    .cal-nav { row-gap: var(--sp-3); }
    .cal-monthnav { flex: 1 1 100%; justify-content: space-between; }
    .cal-month-label { flex: 1 1 auto; }
}

/* Day → create modal actions */
.cal-day-actions { display: flex; flex-direction: column; gap: var(--sp-3); }
.cal-day-action {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    padding: var(--sp-4);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    text-decoration: none;
    color: var(--text);
    transition: background .12s ease, border-color .12s ease;
    /* Used on both <a> and <button>: reset button chrome so they match. */
    width: 100%;
    background: none;
    font: inherit;
    text-align: left;
    cursor: pointer;
}
.cal-day-action:hover { background: var(--bg-soft); border-color: var(--border-strong); }
.cal-day-action > i { font-size: 1.15rem; color: var(--brand-accent-strong); width: 1.4rem; text-align: center; flex-shrink: 0; }
.cal-day-action span { display: flex; flex-direction: column; }
.cal-day-action strong { font-weight: 600; }
.cal-day-action small { color: var(--text-light); font-size: .8rem; }

/* Note editor: keep Delete on the left, Cancel/Save on the right. */
.cal-note-foot { display: flex; align-items: center; gap: var(--sp-3); }
.cal-note-foot-spacer { flex: 1 1 auto; }

/* Upcoming list under the calendar — count selector + JS row limiting */
.cal-upcoming-count { font-size: .8rem; color: var(--text-light); display: inline-flex; align-items: center; gap: var(--sp-2); }
.cal-upcoming-count select {
    font: inherit;
    font-size: .82rem;
    padding: 2px 1.6rem 2px var(--sp-2);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background-color: #fff;
    color: var(--text);
    cursor: pointer;
}
.dash-week-item-hidden { display: none; }
/* Category filter pills above the Upcoming list. Scrolls sideways on a phone
   rather than wrapping the segmented control. */
.cal-upcoming-filters {
    margin: var(--sp-2) 0 var(--sp-4);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
.cal-upcoming-filters .segmented { min-width: max-content; }

/* ═══════════════════════════════════════════════════════════════════════════
   Collapsible sidebar + master "+ New" quick-create launcher.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Collapse chevron — centered below the studio name (desktop only) ── */
.portal-sidebar-header { display: flex; flex-direction: column; }
.portal-collapse {
    display: none; align-self: center; margin-top: var(--sp-3);
    width: 30px; height: 30px; border: 0; background: transparent;
    color: var(--text-light); border-radius: var(--radius); cursor: pointer;
    align-items: center; justify-content: center;
}
@media (min-width: 900px) { .portal-collapse { display: inline-flex; } }
/* Client portal: center the brand + "Client Portal" label in the header. */
.portal-sidebar-header-client { align-items: center; }
.portal-sidebar-header-client .brand-sidebar { align-items: center; text-align: center; }
.portal-collapse:hover { background: var(--bg-muted); color: var(--text); }
.portal-collapse i { font-size: 1.15rem; transition: transform .18s ease; }

/* Collapsed → icon rail. The grid column tracks --portal-sidebar-w, so shrinking
   the variable collapses the whole layout; we then hide the text bits. */
@media (min-width: 900px) {
    body.portal-nav-collapsed { --portal-sidebar-w: 4.5rem; }
    body.portal-nav-collapsed .portal-sidebar { transition: none; }

    body.portal-nav-collapsed .brand-wordmark,
    body.portal-nav-collapsed .brand-separator,
    body.portal-nav-collapsed .brand-id-name,
    body.portal-nav-collapsed .brand-sublabel { display: none; }
    body.portal-nav-collapsed .brand-sidebar { align-items: center; }
    body.portal-nav-collapsed .brand-id-frame { justify-content: center; }
    body.portal-nav-collapsed .brand-logo { max-width: 36px; max-height: 36px; object-fit: contain; }
    body.portal-nav-collapsed .portal-sidebar-header { padding-left: 0; padding-right: 0; align-items: center; }
    /* Keep the white-label hero logo in the topbar-height band (so it still
       aligns with the page title) but centered in the narrow rail. */
    body.portal-nav-collapsed .portal-sidebar-header .brand-sidebar > .brand-logo {
        height: var(--admin-topbar-h); max-height: none; width: 100%;
        object-position: center; margin: 0; padding: 12px 0;
    }
    body.portal-nav-collapsed .portal-collapse i { transform: rotate(180deg); }

    body.portal-nav-collapsed .portal-sidenav-label,
    body.portal-nav-collapsed .portal-sidenav-locktag { display: none; }
    /* Icon-rail: the link fills the (narrow) sidebar content box and the icon is
       dead-centered inside it via text-align on a full-width icon — so the active
       background square always has its glyph centered, regardless of flex quirks. */
    body.portal-nav-collapsed .portal-sidebar { padding-left: var(--sp-2); padding-right: var(--sp-2); }
    body.portal-nav-collapsed .portal-sidenav-link { position: relative; display: flex; width: 100%; height: 42px; margin: 0; padding: 0; align-items: center; justify-content: center; }
    body.portal-nav-collapsed .portal-sidenav-icon { width: 100%; margin: 0; text-align: center; line-height: 1; }
    /* Collapsed: the numeric unread badge + the alert dot both pin to the corner. */
    body.portal-nav-collapsed .nav-unread-badge { position: absolute; top: 5px; right: 7px; margin: 0; width: 8px; height: 8px; min-width: 0; padding: 0; font-size: 0; line-height: 0; border-radius: 50%; }
    body.portal-nav-collapsed .nav-alert-dot { position: absolute; top: 5px; right: 7px; margin: 0; }

    body.portal-nav-collapsed .portal-user-name,
    body.portal-nav-collapsed .portal-user-role,
    body.portal-nav-collapsed .portal-usage { display: none; }
    body.portal-nav-collapsed .portal-user-card { padding: 0; align-items: center; }
    body.portal-nav-collapsed .portal-sidebar-footer { align-items: center; }
}

/* ── Quick-create "+ New" launcher ── */
.qc { position: relative; display: inline-flex; }
.qc-btn .fa-plus { transition: transform .2s ease; }
.qc.is-open .qc-btn .fa-plus { transform: rotate(45deg); }
.qc-menu {
    position: absolute; top: calc(100% + 8px); right: 0; z-index: 50; min-width: 220px;
    padding: 6px; background: var(--bg); border: 1px solid var(--border);
    border-radius: var(--radius-lg); box-shadow: 0 12px 32px rgba(0, 0, 0, .12);
    visibility: hidden; opacity: 0; transform: translateY(-6px) scale(.98); transform-origin: top right;
    transition: opacity .15s ease, transform .15s ease, visibility .15s;
}
.qc.is-open .qc-menu { visibility: visible; opacity: 1; transform: translateY(0) scale(1); }
/* Mobile: right-align the "+ New" button so its menu (which opens leftward,
   right:0) stays on screen. The max-width cap guarantees the menu can never be
   wider than the viewport regardless of where the button sits. */
@media (max-width: 600px) {
    .portal-page-header .action-row:has(.qc) { width: 100%; justify-content: flex-end; }
    .qc-menu { max-width: calc(100vw - 24px); }
}
.qc-item {
    display: flex; align-items: center; gap: var(--sp-3);
    padding: .6rem .7rem; border-radius: var(--radius);
    color: var(--text); text-decoration: none; font-size: .95rem; font-weight: 500;
    cursor: pointer; white-space: nowrap;
}
.qc-item:hover { background: var(--bg-muted); }
.qc-item:focus { outline: none; background: var(--bg-muted); }
.qc-item:focus-visible { outline: 2px solid var(--brand-accent); outline-offset: -2px; }
.qc-item > i:first-child { width: 1.1rem; text-align: center; color: var(--text-light); font-size: .95rem; }
.qc-item:hover > i:first-child { color: var(--brand-accent); }
.qc-item-locked { opacity: .7; }
.qc-lock { margin-left: auto; font-size: .72rem; color: var(--text-light); }

/* ═══════════════════════════════════════════════════════════════════════════
   Messages center — two-pane conversations + thread/composer.
   ═══════════════════════════════════════════════════════════════════════════ */
.msg-center { display: grid; grid-template-columns: 320px 1fr; gap: var(--sp-5); align-items: start; }

.msg-convos { background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; }
.msg-convos-head { display: flex; align-items: center; justify-content: space-between; padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--border); }
.msg-convos-title { font-size: 1rem; font-weight: 700; margin: 0; }
.msg-convos-list { max-height: 72vh; overflow-y: auto; }
.msg-convos-empty { padding: var(--sp-5) var(--sp-4); }
.msg-convo { position: relative; display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3) var(--sp-4); text-decoration: none; color: var(--text); border-bottom: 1px solid var(--border-soft); }
.msg-convo:hover { background: var(--bg-soft); }
.msg-convo.is-active { background: var(--bg-muted); }
.msg-convo-avatar { flex: 0 0 auto; }
.msg-convo-body { min-width: 0; flex: 1 1 auto; }
.msg-convo-top { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-2); }
.msg-convo-name { font-weight: 600; font-size: .92rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.msg-convo-time { flex: 0 0 auto; }
.msg-convo-preview { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.msg-convo.is-unread .msg-convo-name { font-weight: 700; }
.msg-convo.is-unread .msg-convo-preview { color: var(--text); }
.msg-convo-dot { position: absolute; right: var(--sp-3); top: 50%; transform: translateY(-50%); width: 9px; height: 9px; border-radius: 50%; background: var(--brand-accent); }

.msg-thread-pane { display: flex; flex-direction: column; gap: var(--sp-4); background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--sp-5); min-height: 62vh; }
.msg-thread-head { display: flex; align-items: center; gap: var(--sp-3); padding-bottom: var(--sp-4); border-bottom: 1px solid var(--border); }
.msg-thread-head-new { gap: var(--sp-2); }
.msg-thread-name { font-size: 1.1rem; font-weight: 700; margin: 0; display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.msg-thread-project { display: inline-flex; align-items: center; gap: 5px; font-size: .78rem; font-weight: 600; color: var(--text-med); text-decoration: none; background: var(--bg-muted); padding: 2px 9px; border-radius: 999px; }
.msg-thread-project:hover { color: var(--brand-accent-strong); background: var(--bg-soft); }
.msg-thread-project i { font-size: .72rem; color: var(--text-light); }
.msg-thread-list { max-height: 56vh; }

.msg-compose .form-control { width: 100%; }
.msg-compose label { display: block; }
.msg-compose label > span { display: block; margin-bottom: 4px; }
.msg-compose-row { margin-bottom: var(--sp-3); }
.msg-compose-row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }
.msg-compose-proj { max-width: 360px; }

/* Cleaned composer: message field is primary; the project tag is a subtle pill. */
.msg-compose .messages-compose-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.msg-to { display: block; margin-bottom: var(--sp-3); max-width: 360px; }
.msg-to > span { display: block; margin-bottom: 4px; }
.msg-tag { display: inline-flex; align-items: center; gap: 7px; max-width: 240px; padding: 5px 12px; border: 1px solid var(--border); border-radius: 999px; background: var(--bg-soft); color: var(--text-med); cursor: pointer; }
.msg-tag:hover { border-color: var(--border-strong); }
.msg-tag i { font-size: .78rem; color: var(--text-light); }
.msg-tag-select { border: 0; background: transparent; outline: none; cursor: pointer; font-size: .82rem; color: var(--text); padding: 0; max-width: 180px; }
.msg-tag-select:focus { outline: none; }

.message-project-tag { display: inline-flex; align-items: center; gap: 4px; font-size: .72rem; color: var(--text-med); text-decoration: none; background: var(--bg-muted); padding: 1px 8px; border-radius: 999px; margin-left: var(--sp-2); }
.message-project-tag:hover { color: var(--brand-accent-strong); background: var(--bg-soft); }

@media (max-width: 899px) {
    .msg-center { grid-template-columns: 1fr; }
    .msg-convos-list { max-height: 40vh; }
    .msg-compose-row-2 { grid-template-columns: 1fr; }
}

/* ── Analytics ──────────────────────────────────────────────────────────── */
/* Filter toolbar: selects grouped left, Export pinned right; stacks on mobile. */
.analytics-toolbar {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
    margin-bottom: var(--sp-5);
}
.analytics-toolbar-filters { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.analytics-toolbar .form-control { min-height: 44px; min-width: 200px; }
.analytics-toolbar #af-range { min-width: 160px; }
.analytics-export { margin-left: auto; height: 42px; display: inline-flex; align-items: center; gap: 8px; }
@media (max-width: 599px) {
    .analytics-toolbar { flex-direction: column; align-items: stretch; }
    .analytics-toolbar-filters { flex-direction: column; align-items: stretch; }
    .analytics-toolbar .form-control { width: 100%; min-width: 0; }
    .analytics-export { margin-left: 0; width: 100%; justify-content: center; }
}

/* Metric cards */
.analytics-cards {
    display: grid; gap: var(--sp-3); margin-bottom: var(--sp-5);
    grid-template-columns: repeat(2, 1fr);
}
.metric-card {
    background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-lg);
    padding: var(--sp-4) var(--sp-4); display: flex; flex-direction: column; gap: 4px; min-width: 0;
}
.metric-label { font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: var(--text-light); }
.metric-value { font-size: 1.5rem; font-weight: 800; line-height: 1.1; color: var(--text); overflow-wrap: anywhere; }
.metric-sub   { font-size: .72rem; color: var(--text-light); }

/* Section headings that group the page into Financial / Clients & projects /
   Content & storage. */
.analytics-section {
    font-size: 1.05rem; font-weight: 750; color: var(--text);
    margin: var(--sp-6) 0 var(--sp-4); padding-bottom: var(--sp-2);
    border-bottom: 1px solid var(--border);
}
.analytics-section:first-of-type { margin-top: var(--sp-2); }

/* ---------- STORAGE: near-full banner + Add-storage modal ---------- */
.storage-alert {
    display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap;
    background: var(--warn-bg); border: 1px solid var(--warn-border, #fed7aa); border-left: 4px solid var(--warn);
    border-radius: var(--radius); padding: var(--sp-3) var(--sp-4); margin-bottom: var(--sp-4);
}
.storage-alert-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.storage-alert-title { font-weight: 700; color: var(--warn); display: flex; align-items: center; gap: 7px; }
.storage-alert-title i { width: 16px; height: 16px; }
.storage-alert-sub { font-size: .85rem; color: var(--text); }
.storage-alert-actions { flex: 0 0 auto; display: flex; align-items: center; gap: var(--sp-2); }
.storage-alert-x { background: none; border: 0; font-size: 1.4rem; line-height: 1; color: var(--warn); cursor: pointer; opacity: .7; padding: 0 4px; }
.storage-alert-x:hover { opacity: 1; }
@media (max-width: 599px) {
    .storage-alert-actions { width: 100%; justify-content: space-between; }
}

.storage-modal { max-width: 480px; width: 100%; }
.storage-modal-usage { margin: var(--sp-2) 0 var(--sp-4); }
.storage-modal-usage-head { display: flex; justify-content: space-between; align-items: baseline; font-size: .9rem; margin-bottom: 6px; }
.storage-modal-bar { display: flex; height: 22px; border-radius: 6px; overflow: hidden; border: 1px solid var(--border); background: var(--border); }
.sm-seg { height: 100%; transition: width .18s ease; }
.sm-seg-used { background: #3b82f6; }
.sm-seg-free { background: var(--border); }
.sm-seg-new  { background: #16a34a; }
.storage-modal-pick { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-4); }
.storage-modal-pick-label { font-weight: 600; }
.storage-modal-cost { border-top: 1px solid var(--border); padding-top: var(--sp-3); display: flex; flex-direction: column; gap: 6px; }
.storage-modal-cost-row { display: flex; justify-content: space-between; align-items: baseline; font-size: .9rem; color: var(--text-light); }
.storage-modal-cost-recurring { font-weight: 700; color: var(--text); }
.storage-modal-cost-recurring .t-mono { font-size: 1.05rem; }

/* Money summary hero (Paid + Outstanding = Total invoiced) and the Storage &
   content panel share one two-column card layout. */
.analytics-money, .analytics-storage {
    display: grid; grid-template-columns: 1fr; gap: var(--sp-4);
    background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-lg);
    padding: var(--sp-5); margin-bottom: var(--sp-5);
}
.am-eyebrow { font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--text-light); display: block; }
.am-big { font-size: 2.2rem; font-weight: 800; line-height: 1.05; letter-spacing: -.02em; color: var(--text); display: block; margin-top: 2px; }
.am-sub { font-size: .78rem; color: var(--text-light); display: block; margin-top: 4px; }
.am-bar { display: flex; height: 26px; border-radius: 7px; overflow: hidden; border: 1px solid var(--border); background: var(--border); }
.am-seg { display: block; height: 100%; }
.am-seg-paid  { background: #16a34a; }
.am-seg-out   { background: #d97706; }
.am-seg-empty { background: var(--border); }
.am-legend { display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-6); margin-top: var(--sp-4); }
.am-leg { display: flex; flex-direction: column; gap: 2px; text-decoration: none; color: inherit; }
.am-leg:hover .am-leg-v { text-decoration: underline; }
.am-leg-k { display: flex; align-items: center; gap: 7px; font-size: .78rem; font-weight: 600; color: var(--text-light); }
.am-dot { width: 9px; height: 9px; border-radius: 3px; display: inline-block; }
.am-dot-paid { background: #16a34a; }
.am-dot-out  { background: #d97706; }
.am-leg-v { font-size: 1.35rem; font-weight: 750; letter-spacing: -.01em; color: var(--text); }
.am-leg-pct { font-size: .72rem; font-weight: 600; color: var(--text-light); }
.am-foot { margin-top: var(--sp-4); margin-bottom: 0; font-size: .78rem; color: var(--text-light); }
.am-chip { background: var(--warn-bg); color: var(--warn); font-weight: 700; padding: 2px 9px; border-radius: 999px; font-size: .72rem; margin-right: 4px; }
@media (min-width: 900px) {
    .analytics-money, .analytics-storage { grid-template-columns: minmax(190px, 250px) 1fr; align-items: center; gap: var(--sp-6); }
}
.chart-bar-revenue { fill: #16a34a; }
/* Storage & content panel: images / delivery files / free space. */
.am-seg-img  { background: #3b82f6; }
.am-seg-del  { background: #8b5cf6; }
.am-seg-free { background: var(--border); }
.am-dot-img  { background: #3b82f6; }
.am-dot-del  { background: #8b5cf6; }
.am-dot-free { background: var(--border); }

/* Chart cards + 2-col grid */
.analytics-grid2 { display: grid; grid-template-columns: 1fr; gap: var(--sp-4); margin-bottom: var(--sp-5); }
/* A full-width chart card that stands alone (not inside a grid) needs its own
   bottom margin so it doesn't butt up against the next block. Matches the
   vertical rhythm of the hero / card grids / stat grids (all --sp-5). */
.chart-card-solo { margin-bottom: var(--sp-5); }
.chart-card {
    background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-lg);
    padding: var(--sp-4) var(--sp-5); min-width: 0;
}
.chart-card-title { font-size: .95rem; font-weight: 700; margin: 0 0 var(--sp-3); color: var(--text); }
.chart-empty { color: var(--text-light); font-size: .85rem; padding: var(--sp-5) 0; text-align: center; }

/* SVG chart primitives */
.chart { width: 100%; height: auto; display: block; }
.chart-grid    { stroke: var(--border); stroke-width: 1; }
.chart-axis-x, .chart-axis-y { fill: var(--text-light); font-size: 11px; font-family: var(--font-sans, system-ui); }
.chart-bar     { fill: var(--brand-accent); }
.chart-bar-alt { fill: #3b82f6; }
.chart-area-fill { fill: rgba(22,163,74,.12); }
.chart-area-line { stroke: #16a34a; stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.chart-area-dot  { fill: #16a34a; }

/* Donut */
.donut-wrap { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4) var(--sp-5); }
.chart-donut { width: 180px; height: 180px; flex: 0 0 auto; }
.donut-track { stroke: var(--bg-muted); }
.donut-seg   { transition: none; }
.donut-c1 { stroke: #3b82f6; } .donut-c1.donut-key { background: #3b82f6; }
.donut-c2 { stroke: #f59e0b; } .donut-c2.donut-key { background: #f59e0b; }
.donut-c3 { stroke: #8b5cf6; } .donut-c3.donut-key { background: #8b5cf6; }
.donut-c4 { stroke: #16a34a; } .donut-c4.donut-key { background: #16a34a; }
.donut-center     { fill: var(--text); font-size: 28px; font-weight: 800; }
.donut-center-sub { fill: var(--text-light); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
.donut-legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; flex: 1 1 160px; min-width: 140px; }
.donut-legend li { display: flex; align-items: center; gap: 8px; font-size: .85rem; }
.donut-key { width: 12px; height: 12px; border-radius: 3px; flex: 0 0 auto; }
.donut-legend-label { color: var(--text-med); }
.donut-legend-val { margin-left: auto; font-weight: 700; color: var(--text); }

.analytics-table { width: 100%; }

@media (min-width: 600px) {
    .analytics-cards { grid-template-columns: repeat(4, 1fr); }
    /* Per-section grids fill the full width by matching column count to card
       count (e.g. the 3 Financial cards span 1/3 each, not 1/4). */
    .analytics-cards.cols-3 { grid-template-columns: repeat(3, 1fr); }
    .analytics-cards.cols-2 { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 900px) {
    .analytics-grid2 { grid-template-columns: 1fr 1fr; }
}

/* Chart load-in animations — skipped entirely for reduced-motion users, who get
   the fully-drawn static charts (geometry is already set on the elements). */
@media (prefers-reduced-motion: no-preference) {
    .chart-bars rect {
        transform-box: fill-box;
        transform-origin: center bottom;
        animation: chartGrowUp .55s cubic-bezier(.2,.7,.3,1) backwards;
        animation-delay: calc(var(--i, 0) * 45ms);
    }
    @keyframes chartGrowUp { from { transform: scaleY(0); } to { transform: scaleY(1); } }

    .chart-area-line {
        stroke-dasharray: 1;
        stroke-dashoffset: 1;
        animation: chartDrawLine 1s ease forwards;
    }
    @keyframes chartDrawLine { to { stroke-dashoffset: 0; } }

    .chart-area-fill { opacity: 0; animation: chartFadeIn .6s ease .5s forwards; }
    .chart-area-dot  { opacity: 0; animation: chartFadeIn .4s ease .95s forwards; }
    @keyframes chartFadeIn { to { opacity: 1; } }

    .chart-donut .donut-seg {
        animation: donutSweep .8s cubic-bezier(.2,.7,.3,1) backwards;
        animation-delay: calc(var(--i, 0) * 130ms);
    }
    @keyframes donutSweep {
        from { stroke-dasharray: 0 var(--circ); }
        to   { stroke-dasharray: var(--len) var(--rest); }
    }
}

/* Chart hover/tap tooltip + hit targets */
.chart-tooltip {
    position: fixed; z-index: 70; pointer-events: none;
    background: var(--bg-dark, #0a0a0a); color: #fff;
    padding: 7px 10px; border-radius: 8px; line-height: 1.25; max-width: 220px;
    display: flex; flex-direction: column; gap: 1px;
    box-shadow: 0 6px 20px rgba(0,0,0,.18);
}
.chart-tooltip[hidden] { display: none; }
.chart-tooltip-label { opacity: .72; font-size: .68rem; text-transform: uppercase; letter-spacing: .03em; }
.chart-tooltip-val   { font-weight: 700; font-size: .92rem; }

.chart-hit { fill: transparent; cursor: pointer; }
.chart-hit:hover { fill: rgba(10, 10, 10, .045); }

.chart-area-dot { transform-box: fill-box; transform-origin: center; transition: transform .12s ease; }
.chart-area-dot.is-active { transform: scale(2.1); }

.donut-seg { cursor: pointer; transition: stroke-width .12s ease; }
.donut-seg:hover { stroke-width: 34; }

/* Metric-card deltas + drill-down */
.metric-foot { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-top: 2px; }
.metric-delta { font-size: .72rem; font-weight: 700; white-space: nowrap; }
.metric-delta.is-good { color: #16a34a; }
.metric-delta.is-bad  { color: #dc2626; }
.metric-delta.is-flat { color: var(--text-light); font-weight: 600; }
.metric-delta-cmp { color: var(--text-light); font-weight: 500; }
a.metric-card-link { text-decoration: none; color: inherit; transition: border-color .12s, box-shadow .12s, transform .12s; }
a.metric-card-link:hover { border-color: var(--brand-accent); box-shadow: 0 2px 10px rgba(0,0,0,.06); transform: translateY(-1px); }

/* Stat rows (AR aging / bookkeeping / conversion cards) */
.stat-rows { list-style: none; margin: 0 0 var(--sp-3); padding: 0; display: flex; flex-direction: column; }
.stat-rows li { display: flex; justify-content: space-between; gap: var(--sp-3); padding: 9px 0; border-bottom: 1px solid var(--border); font-size: .9rem; }
.stat-rows li:last-child { border-bottom: 0; }
.stat-rows li > span:last-child { font-weight: 700; }
.stat-rows .stat-warn   > span:last-child { color: #b45309; }
.stat-rows .stat-danger > span:last-child { color: #dc2626; }
.stat-link { font-size: .82rem; font-weight: 600; }
.stat-foot { font-size: .72rem; color: var(--text-light); text-transform: uppercase; letter-spacing: .03em; }

/* 3-up detail grid */
.analytics-grid3 { display: grid; grid-template-columns: 1fr; gap: var(--sp-4); margin-bottom: var(--sp-5); }
@media (min-width: 900px) { .analytics-grid3 { grid-template-columns: repeat(3, 1fr); } }

/* ── Radar ──────────────────────────────────────────────────────────────── */
.radar-grid { display: grid; grid-template-columns: 1fr; gap: var(--sp-4); }
@media (min-width: 900px) { .radar-grid { grid-template-columns: 1fr 1fr; } }

.radar-card {
    background: var(--bg); border: 1px solid var(--border); border-left-width: 4px;
    border-radius: var(--radius-lg); padding: var(--sp-4) var(--sp-5); min-width: 0;
}
.radar-card.radar-danger { border-left-color: #dc2626; }
.radar-card.radar-warn   { border-left-color: #f59e0b; }
.radar-card.radar-info   { border-left-color: #3b82f6; }

.radar-card-head { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.radar-ico { width: 30px; height: 30px; border-radius: 8px; flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; font-size: .9rem; }
.radar-danger .radar-ico { background: #fef2f2; color: #dc2626; }
.radar-warn   .radar-ico { background: #fffbeb; color: #b45309; }
.radar-info   .radar-ico { background: #eff6ff; color: #2563eb; }
.radar-card-title { font-size: .95rem; font-weight: 700; margin: 0; flex: 1 1 auto; min-width: 0; }
.radar-count { font-size: .78rem; font-weight: 800; min-width: 22px; height: 22px; padding: 0 7px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; background: var(--bg-muted); color: var(--text-med); }
.radar-danger .radar-count { background: #fee2e2; color: #b91c1c; }
.radar-warn   .radar-count { background: #fef3c7; color: #92400e; }
.radar-info   .radar-count { background: #dbeafe; color: #1d4ed8; }

.radar-items { list-style: none; margin: 0; padding: 0; }
.radar-item {
    display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 1px var(--sp-3);
    padding: 10px 0; border-top: 1px solid var(--border); text-decoration: none; color: inherit;
}
.radar-items li:first-child .radar-item { border-top: 0; }
.radar-item-main { font-weight: 600; font-size: .9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.radar-item-meta { grid-column: 1; font-size: .8rem; color: var(--text-light); }
.radar-item-chev { grid-column: 2; grid-row: 1 / span 2; color: var(--text-light); font-size: .75rem; opacity: 0; transition: opacity .12s; }
.radar-item:hover .radar-item-chev { opacity: 1; }
.radar-item:hover .radar-item-main { color: var(--brand-accent-strong, #c2410c); }
.radar-more { display: inline-block; margin-top: var(--sp-3); font-size: .82rem; font-weight: 600; }

.radar-clear { text-align: center; padding: var(--sp-7, 56px) var(--sp-5); background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-lg); }
.radar-clear-ico { font-size: 2.4rem; color: #16a34a; }
.radar-clear-title { margin: var(--sp-3) 0 6px; font-size: 1.15rem; font-weight: 800; }
.radar-clear-sub { color: var(--text-med); max-width: 460px; margin: 0 auto; }

/* Radar — relationship pulse */
.radar-section-label { font-size: 1.05rem; font-weight: 800; margin: var(--sp-6) 0 var(--sp-3); }
.radar-rel {
    background: var(--bg); border: 1px solid var(--border); border-left-width: 4px;
    border-radius: var(--radius-lg); padding: var(--sp-4) var(--sp-5); margin-bottom: var(--sp-4);
}
.radar-rel.radar-danger { border-left-color: #dc2626; }
.radar-rel.radar-warn   { border-left-color: #f59e0b; }
.radar-rel.radar-info   { border-left-color: #3b82f6; }
.radar-rel-head { display: flex; gap: var(--sp-2); align-items: flex-start; margin-bottom: var(--sp-3); }
.radar-rel-title { font-size: .95rem; font-weight: 700; margin: 0; display: flex; align-items: center; gap: 8px; }
.radar-rel-desc { font-size: .8rem; color: var(--text-light); margin: 2px 0 0; }
.radar-rel-grid { display: grid; grid-template-columns: 1fr; gap: var(--sp-3); }
@media (min-width: 700px)  { .radar-rel-grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1200px) { .radar-rel-grid { grid-template-columns: 1fr 1fr 1fr; } }
.radar-rel-card {
    border: 1px solid var(--border); border-radius: var(--radius-md, 10px);
    padding: var(--sp-3) var(--sp-4); display: flex; flex-direction: column; gap: 8px; min-width: 0;
}
.radar-rel-card-top { display: flex; gap: var(--sp-2); align-items: flex-start; }
.radar-rel-id { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.radar-rel-name { font-weight: 700; text-decoration: none; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.radar-rel-name:hover { color: var(--brand-accent-strong, #c2410c); }
.radar-rel-reason { font-size: .8rem; color: var(--text-med); }
.radar-rel-meta { font-size: .75rem; color: var(--text-light); }
.radar-rel-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 2px; }
.radar-snooze-form { margin: 0; }
.radar-checkin-modal #rc-body { font-family: inherit; line-height: 1.5; resize: vertical; }

/* Pulse — service-watch status dots */
.pulse-dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: 7px; vertical-align: middle; }
.pulse-ok    { background: #16a34a; }
.pulse-watch { background: #f59e0b; }
.pulse-soon  { background: #dc2626; }

/* Radar — premium "listening" empty state */
.radar-listening {
    background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-lg);
    padding: var(--sp-7, 56px) var(--sp-6); text-align: center;
}
.radar-listening-ico {
    display: inline-flex; align-items: center; justify-content: center;
    width: 64px; height: 64px; border-radius: 50%; background: #eff6ff; color: #2563eb;
    font-size: 1.55rem; margin-bottom: var(--sp-4);
}
.radar-listening-title { font-size: 1.35rem; font-weight: 800; margin: 0 0 8px; }
.radar-listening-lead { color: var(--text-med); max-width: 560px; margin: 0 auto var(--sp-6); line-height: 1.55; }
.radar-signals {
    list-style: none; margin: 0 auto; padding: 0; max-width: 720px; text-align: left;
    display: grid; grid-template-columns: 1fr; gap: var(--sp-3);
}
@media (min-width: 700px) { .radar-signals { grid-template-columns: 1fr 1fr; } }
.radar-signals li {
    display: flex; gap: 12px; align-items: flex-start;
    background: var(--bg-soft); border: 1px solid var(--border);
    border-radius: var(--radius-md, 10px); padding: 12px 14px;
}
.radar-signals li > i { color: #2563eb; margin-top: 2px; width: 18px; text-align: center; flex: 0 0 auto; }
.radar-signals li div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.radar-signals li strong { font-size: .9rem; }
.radar-signals li span { font-size: .82rem; color: var(--text-light); }
.radar-listening-foot { color: var(--text-light); font-size: .82rem; margin: var(--sp-6) auto 0; max-width: 560px; }

/* ── PWA app shell — bottom tab bar + create sheet (installed standalone only) ──
   The tab bar is hidden by default and only revealed when the page is running
   as an installed app (html.pwa-standalone, set in head_open). Mobile web is
   left exactly as-is. */
.pwa-tabbar { display: none; }
html.pwa-standalone .pwa-tabbar {
    display: flex;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
    align-items: stretch;
    height: calc(58px + env(safe-area-inset-bottom));
    padding-bottom: env(safe-area-inset-bottom);
    background: rgba(255,255,255,.94);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
    backdrop-filter: saturate(180%) blur(12px);
    border-top: 1px solid var(--border);
}
.pwa-tab {
    flex: 1 1 0; min-width: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
    font-size: .64rem; font-weight: 600;
    color: var(--text-light); text-decoration: none;
    background: none; border: 0; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}
.pwa-tab i { font-size: 1.18rem; }
.pwa-tab.is-active { color: var(--brand-accent-strong); }
.pwa-tab-create { position: relative; }
.pwa-tab-fab {
    width: 48px; height: 48px; margin-top: -16px; border-radius: 50%;
    background: var(--brand-accent); color: #fff;
    display: flex; align-items: center; justify-content: center; font-size: 1.25rem;
    box-shadow: 0 6px 18px -4px rgba(0,0,0,.35);
}
/* Keep page content + the feedback FAB clear of the fixed tab bar. */
html.pwa-standalone .portal-content { padding-bottom: calc(78px + env(safe-area-inset-bottom)); }
html.pwa-standalone .feedback-fab { bottom: calc(72px + env(safe-area-inset-bottom)); }

/* Create sheet (slides up from the + tab). */
.pwa-sheet { position: fixed; inset: 0; z-index: 80; }
.pwa-sheet[hidden] { display: none; }
.pwa-sheet-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,.42); opacity: 0; transition: opacity .2s ease; }
.pwa-sheet.is-open .pwa-sheet-backdrop { opacity: 1; }
.pwa-sheet-panel {
    position: absolute; left: 0; right: 0; bottom: 0;
    background: #fff; border-radius: 18px 18px 0 0;
    padding: 10px 18px calc(22px + env(safe-area-inset-bottom));
    transform: translateY(100%); transition: transform .22s ease;
    box-shadow: 0 -8px 30px rgba(0,0,0,.18);
}
.pwa-sheet.is-open .pwa-sheet-panel { transform: translateY(0); }
.pwa-sheet-grip { width: 38px; height: 4px; border-radius: 2px; background: var(--border); margin: 4px auto 14px; }
.pwa-sheet-title { font-size: .72rem; text-transform: uppercase; letter-spacing: .06em; color: var(--text-light); margin: 0 0 6px; }
.pwa-sheet-item {
    display: flex; align-items: center; gap: 14px;
    padding: 15px 4px; font-size: 1rem; font-weight: 600; color: var(--text); text-decoration: none;
    border-top: 1px solid var(--border);
}
.pwa-sheet-item i:first-child { width: 22px; text-align: center; color: var(--brand-accent-strong); }
.pwa-sheet-item.is-locked { color: var(--text-light); }
.pwa-sheet-lock { margin-left: auto; font-size: .8rem; }
body.pwa-sheet-open { overflow: hidden; }

/* ── Install-app banner (top of portal content; hidden until installable) ── */
.pwa-install {
    display: flex; align-items: center; gap: 12px;
    margin-bottom: var(--sp-5);
    padding: 12px 14px;
    border: 1px solid var(--border); border-radius: 12px;
    background: var(--bg);
    box-shadow: 0 2px 12px -4px rgba(0,0,0,.08);
    opacity: 0; transform: translateY(-6px);
    transition: opacity .25s ease, transform .25s ease;
}
.pwa-install.is-open { opacity: 1; transform: none; }
.pwa-install[hidden] { display: none; }
.pwa-install-icon { color: var(--brand-accent-strong); font-size: 1.25rem; flex: 0 0 auto; }
.pwa-install-text, .pwa-install-ios { font-size: .9rem; line-height: 1.4; flex: 1 1 auto; min-width: 0; }
.pwa-install-btn { flex: 0 0 auto; white-space: nowrap; }
/* .btn sets display:inline-flex, which outranks the UA [hidden]{display:none} —
   so hiding the button on iOS (no programmatic install) was a no-op and left a
   dead "Install" button. Make hidden actually hide it. */
.pwa-install-btn[hidden] { display: none; }
.pwa-install-x {
    flex: 0 0 auto; background: none; border: 0; cursor: pointer;
    font-size: 1.5rem; line-height: 1; color: var(--text-light); padding: 0 4px;
}
.pwa-install-x:hover { color: var(--text); }
/* Don't show the install nudge inside the installed app itself. */
html.pwa-standalone .pwa-install { display: none !important; }

/* Dependent fields under an off toggle — grayed + non-interactive (values still
   submit, so configured numbers persist). Makes it obvious at a glance whether
   a setting like a surcharge % is actually active. */
.fields-gated-off { opacity: .42; pointer-events: none; transition: opacity .15s ease; }

/* Avatar cropper zoom slider — a visible cue to resize/reposition the photo
   (scroll-to-zoom alone wasn't discoverable). Small→large image icons flank a
   range input wired to Cropper.zoomTo(). */
.avatar-cropper-zoom {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    max-width: 320px;
    margin: 14px auto 0;
}
.avatar-cropper-zoom .avatar-cropper-zoom-sm { font-size: 12px; }
.avatar-cropper-zoom .avatar-cropper-zoom-lg { font-size: 19px; }
.avatar-cropper-zoom-sm,
.avatar-cropper-zoom-lg { color: var(--text-muted, #8a8f98); flex: 0 0 auto; }
.avatar-cropper-zoom input[type="range"] {
    flex: 1 1 auto;
    -webkit-appearance: none;
    appearance: none;
    height: 4px;
    border-radius: 999px;
    background: var(--border, #d8dadf);
    cursor: pointer;
    margin: 0;
}
.avatar-cropper-zoom input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #fff;
    border: 2px solid var(--accent, #2f6df6);
    box-shadow: 0 1px 3px rgba(0,0,0,.25);
    cursor: grab;
}
.avatar-cropper-zoom input[type="range"]::-webkit-slider-thumb:active { cursor: grabbing; }
.avatar-cropper-zoom input[type="range"]::-moz-range-thumb {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #fff;
    border: 2px solid var(--accent, #2f6df6);
    box-shadow: 0 1px 3px rgba(0,0,0,.25);
    cursor: grab;
}
.avatar-cropper-zoom input[type="range"]::-moz-range-track {
    height: 4px;
    border-radius: 999px;
    background: var(--border, #d8dadf);
}

/* ── Quiet mode card (Settings → Notifications) ─────────────────────────────
   A standalone, intentional card (it sits outside the prefs form, so it can't
   borrow the form-section card styling). Calm slate by default; warm amber when
   actively paused. */
.quiet-card {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
    flex-wrap: wrap;
    background: var(--surface, #fff);
    border: 1px solid var(--border, #e7e7ea);
    border-radius: 16px;
    padding: 22px 26px;
    margin-bottom: 28px;
    box-shadow: 0 1px 2px rgba(10, 10, 10, 0.04);
}
.quiet-card.is-active {
    border-color: #f1d488;
    background: #fffdf6;
}
.quiet-card-main {
    display: flex;
    align-items: flex-start;
    gap: 16px;
    flex: 1 1 420px;
    min-width: 0;
}
.quiet-card-icon {
    flex: 0 0 auto;
    width: 46px;
    height: 46px;
    border-radius: 13px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 19px;
    background: var(--bg-soft, #f3f3f5);
    color: var(--text-muted, #6b7280);
}
.quiet-card.is-active .quiet-card-icon {
    background: #fdf0c8;
    color: #a9770a;
}
.quiet-card-title {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin: 2px 0 5px;
    font-size: 16px;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--text, #0a0a0a);
}
.quiet-card-pill {
    font-size: 10.5px;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #a9770a;
    background: #fdf0c8;
    padding: 3px 10px;
    border-radius: 999px;
}
.quiet-card-desc {
    margin: 0;
    color: var(--text-muted, #6b7280);
    font-size: 13.5px;
    line-height: 1.55;
    max-width: 64ch;
}
.quiet-card-action { flex: 0 0 auto; }
.quiet-card-form {
    display: flex;
    align-items: center;
    gap: 10px;
}
.quiet-card-select {
    width: auto;
    min-width: 116px;
}
@media (max-width: 640px) {
    .quiet-card { flex-direction: column; align-items: stretch; justify-content: flex-start; flex-wrap: nowrap; }
    /* In column mode the desktop 420px flex-basis would become a HEIGHT, padding
       the card with dead space. Reset it so the text block hugs its content. */
    .quiet-card-main { flex: 0 0 auto; }
    .quiet-card-action, .quiet-card-form { width: 100%; }
    .quiet-card-form .btn { flex: 1; }
}

/* Quiet-mode topbar indicator — subtle amber pill shown across the studio
   portal while client notifications are paused. Links to Settings to manage. */
.quiet-pill {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    height: 32px;
    padding: 0 12px;
    border-radius: 999px;
    background: #fdf0c8;
    border: 1px solid #f1d488;
    color: #946704;
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1;
    text-decoration: none;
    white-space: nowrap;
    transition: background .15s ease, border-color .15s ease;
}
.quiet-pill:hover { background: #fbe7a8; border-color: #e9c25f; }
.quiet-pill i { font-size: 12px; }
.quiet-pill-time {
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    opacity: .85;
    padding-left: 2px;
    border-left: 1px solid rgba(148, 103, 4, .25);
    margin-left: 1px;
    padding-left: 8px;
}
@media (max-width: 700px) {
    .quiet-pill-label { display: none; }   /* icon + time only on small screens */
}

/* ══ Photographer's picks (studio favorites) ════════════════════════════════ */

/* ── Studio gallery manager: pick badge + pick action button ── */
.gallery-tile-pick-badge {
    /* Bottom-left: top-left is the select checkbox, top-right the cover badge,
       bottom-right the hover actions — so the pick badge never covers them. */
    position: absolute; bottom: 8px; left: 8px;
    width: 24px; height: 24px; border-radius: 999px;
    display: none; align-items: center; justify-content: center;
    background: var(--brand-accent); color: #fff;
    font-size: .72rem;
    box-shadow: 0 2px 5px rgba(0,0,0,.25);
    z-index: 2;
}
.gallery-tile.is-studio-picked .gallery-tile-pick-badge { display: inline-flex; }
.gallery-tile.is-studio-picked { box-shadow: 0 0 0 2px var(--brand-accent); border-radius: 4px; }
.gallery-tile.is-studio-picked .gallery-tile-action-pick {
    background: var(--brand-accent); color: #fff; border-color: var(--brand-accent);
}
.gallery-tile-action-pick:hover { color: var(--brand-accent-strong); }
/* The base .gallery-tile-img sets pointer-events:none; re-enable it on the
   manager image so clicking opens the curation lightbox (drag still works, it's
   initiated on the draggable tile). */
.gallery-tile-img[data-tile-zoom] { pointer-events: auto; cursor: zoom-in; }

/* ── Studio curation lightbox (P to pick, arrows to navigate) ── */
body.gallery-lb-open { overflow: hidden; }
.gallery-lb {
    position: fixed; inset: 0; z-index: 1000;
    display: flex; align-items: center; justify-content: center;
    background: rgba(10,10,10,.92);
    padding: 56px 64px 88px;
}
.gallery-lb[hidden] { display: none; }
.gallery-lb-img {
    max-width: 100%; max-height: 100%;
    object-fit: contain;
    border-radius: 4px;
    box-shadow: 0 8px 40px rgba(0,0,0,.5);
}
/* Nav + close: icon-only, transparent, drop-shadowed — matches the client
   gallery lightbox (no pill backgrounds; scale up on hover). */
.gallery-lb-close {
    position: absolute; top: 20px; right: 24px;
    width: 44px; height: 44px; padding: 0;
    background: transparent; color: rgba(255,255,255,.95); border: none;
    font-size: 1.4rem; line-height: 1; cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    text-shadow: 0 1px 3px rgba(0,0,0,.5); opacity: .85;
    transition: opacity .15s ease, transform .15s ease;
}
.gallery-lb-close:hover { opacity: 1; transform: scale(1.18); }
.gallery-lb-nav {
    position: absolute; top: 50%; transform: translateY(-50%);
    width: 44px; height: 44px; padding: 0;
    background: transparent; color: rgba(255,255,255,.95); border: none;
    font-size: 1.4rem; cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    text-shadow: 0 1px 3px rgba(0,0,0,.5); opacity: .85;
    transition: opacity .15s ease, transform .15s ease;
}
.gallery-lb-nav:hover { opacity: 1; transform: translateY(-50%) scale(1.18); }
.gallery-lb-prev { left: 18px; }
.gallery-lb-next { right: 18px; }
/* Centered caption stack: counter above the filename, then one clean action. */
.gallery-lb-bar {
    position: absolute; left: 0; right: 0; bottom: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
    padding: 16px 24px 22px;
    background: linear-gradient(to top, rgba(0,0,0,.55), transparent);
    text-align: center;
}
.gallery-lb-counter {
    color: rgba(255,255,255,.55); font-size: .78rem;
    font-variant-numeric: tabular-nums; letter-spacing: .04em;
}
.gallery-lb-name { color: rgba(255,255,255,.9); font-size: .9rem; }
.gallery-lb-pick {
    display: inline-flex; align-items: center; gap: 9px;
    padding: 9px 16px; border-radius: 999px;
    background: rgba(255,255,255,.14); color: #fff;
    border: 1px solid rgba(255,255,255,.2);
    font-size: .9rem; font-weight: 600; cursor: pointer;
    transition: background .12s ease, border-color .12s ease;
}
.gallery-lb-pick:hover { background: rgba(255,255,255,.24); }
.gallery-lb-pick.is-active {
    background: var(--brand-accent); border-color: var(--brand-accent); color: #fff;
}
.gallery-lb-pick kbd {
    font-family: inherit; font-size: .72rem; font-weight: 700;
    background: rgba(0,0,0,.28); border-radius: 5px; padding: 2px 7px;
}

/* ── Client gallery: layered bottom-right pick stack ─────────────────────────
   Small circular markers sitting in a horizontal row, each with a drop shadow
   so the set reads as layered + dimensional, and a small gap between them so
   they stay clearly separated (no rings/borders):
     thumbs-up (brand) = the photographer's pick
     star (amber)      = a guest's favorite
   Heart (top-left) + check (top-right) remain the larger interactive toggles.
   The interactive share is a blank slate and renders none of these. */
.gallery-pick-stack {
    position: absolute; right: 9px; bottom: 9px; z-index: 3;
    display: flex; align-items: center;
    pointer-events: none;
}
.gallery-studio-pick-badge,
.gallery-guest-fav-badge {
    width: 24px; height: 24px; border-radius: 999px;
    display: inline-flex; align-items: center; justify-content: center;
    color: #fff; font-size: .62rem; line-height: 1;
    /* drop-shadow (not box-shadow) so it follows the mask cutout below */
    filter: drop-shadow(0 1px 2px rgba(0,0,0,.45));
}
.gallery-studio-pick-badge { background: var(--brand-accent); }
.gallery-guest-fav-badge   { background: var(--gallery-star); }
/* The right badge (photographer's pick) overlaps onto the left and paints on top. */
.gallery-pick-stack > * + * { margin-left: -8px; position: relative; z-index: 1; }
/* True cutout: when both badges show, carve a notch out of the LEFT (back) badge
   so the photo shows through as a real gap between the two circles. The notch is
   centered on the front badge (its center sits at 100% + 4px of the 24px back
   badge) and its radius is ~3px larger than the front badge, which IS the gap. */
.gallery-pick-stack > :first-child:not(:last-child) {
    -webkit-mask: radial-gradient(circle at calc(100% + 4px) 50%, transparent 15px, #000 16px);
            mask: radial-gradient(circle at calc(100% + 4px) 50%, transparent 15px, #000 16px);
}
/* Lightbox stack sits a touch further off the (much larger) image edge. */
.gallery-pick-stack-lb { right: 14px; bottom: 14px; }
.gallery-pick-stack-lb[hidden] { display: none; }
.gallery-filter-tab-picks i { color: var(--brand-accent); }
.gallery-filter-tab-guest i { color: var(--gallery-star); }
.gallery-filter-tab-matched i { color: var(--gallery-match); }
.gallery-filter-tab-fav i { color: var(--gallery-heart); }
.gallery-filter-tab-sel i { color: var(--gallery-check); }
/* Hover affordance on every tab: a subtle lift plus a small icon zoom. */
.gallery-filter-tab i { transition: transform .12s ease, color .12s ease; }
.gallery-filter-tab:hover i { transform: scale(1.18); }
.gallery-filter-tab:hover:not(.is-active) { background: rgba(0,0,0,.05); }
/* Thin separator between filter groups: picks/guests | favorites/selections | matched */
.gallery-filter-divider {
    flex: 0 0 auto;
    width: 1px; height: 18px;
    align-self: center;
    margin: 0 3px;
    background: var(--border);
}
.gallery-guest-hint { color: var(--text-med); white-space: nowrap; }
.gallery-guest-hint i { color: var(--gallery-star); }

/* Share page: the guest's only control — a star toggle, top-left, fills amber. */
.gallery-icon-star { top: 8px; left: 8px; bottom: auto; }
.gallery-icon-star.is-active i {
    color: var(--gallery-star);
    -webkit-text-stroke: 1px rgba(0,0,0,.18);
}
/* On a guest share a "favorited" tile is the guest's star pick — highlight amber. */
.gallery-shell--guest .gallery-tile.is-favorited {
    border-color: var(--gallery-star);
    box-shadow: 0 0 0 1px var(--gallery-star) inset;
}

@media (max-width: 700px) {
    .gallery-lb { padding: 44px 12px 80px; }
}

/* ── Client lightbox: photographer's-pick badge (bottom-left of the image) ── */
.gallery-lightbox-pick {
    position: absolute; left: 12px; bottom: 12px; z-index: 1001;
    display: inline-flex; align-items: center; gap: 7px;
    padding: 7px 13px; border-radius: 999px;
    background: var(--brand-accent); color: #fff;
    font-size: .8rem; font-weight: 600; line-height: 1;
    box-shadow: 0 2px 8px rgba(0,0,0,.32);
    pointer-events: none;
}
.gallery-lightbox-pick[hidden] { display: none; }

/* ── Heart/check as filled chips (match the studio pick-button style), with
   their respective colors when active: rose heart (favorite), green check
   (selection). Overrides the earlier minimalist bare-icon styling. ── */
.gallery-icon {
    background: rgba(255,255,255,.92);
    border-radius: 10px;
    box-shadow: 0 2px 6px rgba(0,0,0,.20);
}
.gallery-icon i {
    color: #2a2a2a;
    font-size: 18px;
    -webkit-text-stroke: 0;
    text-shadow: none;
}
.gallery-icon:hover { background: #fff; }
.gallery-icon:hover i { color: #111; }
.gallery-icon-heart.is-active { background: var(--gallery-heart); box-shadow: 0 2px 8px rgba(225,29,72,.42); }
.gallery-icon-check.is-active { background: var(--gallery-check); box-shadow: 0 2px 8px rgba(22,163,74,.42); }
.gallery-icon-heart.is-active i,
.gallery-icon-check.is-active i { color: #fff; -webkit-text-stroke: 0; }
.gallery-icon-lb i { font-size: 22px; }   /* lightbox icons a touch larger */

/* Condense the gallery filter bar: only the ACTIVE tab shows its text label;
   inactive tabs collapse to icon + count (keeps the bar short, esp. on mobile,
   now that "Photographer's picks" is in the mix). title attr gives a hover/
   screen-reader name for the collapsed tabs. */
.gallery-filter-label { display: none; }
.gallery-filter-tab.is-active .gallery-filter-label { display: inline; }
/* On small / small-mid screens, even the active tab is icon-only: a long active
   label (e.g. "Photographer's picks") otherwise expands and shoves the other
   tabs off the right edge. The active tab stays obvious via its highlight; the
   title attr still names it for hover/screen readers. */
@media (max-width: 768px) {
    .gallery-filter-tab.is-active .gallery-filter-label { display: none; }
}

/* Heart/check to EXACTLY match the studio action buttons: white box + solid ink
   icon at rest; icon shows its color on hover; on activate the box fills with the
   color + white icon (rose heart = favorite, green check = selection). */
.gallery-icon {
    background: rgba(255,255,255,.95);
    border: 1px solid rgba(0,0,0,.08);
    border-radius: 11px;
    box-shadow: 0 2px 5px rgba(0,0,0,.18);
}
.gallery-icon i { color: var(--ink, #1a1a1a); }
.gallery-icon:hover { background: #fff; }
.gallery-icon-heart:hover i { color: var(--gallery-heart); }
.gallery-icon-check:hover i { color: var(--gallery-check); }
.gallery-icon-heart.is-active,
.gallery-icon-heart.is-active:hover { background: var(--gallery-heart); border-color: var(--gallery-heart); }
.gallery-icon-check.is-active,
.gallery-icon-check.is-active:hover { background: var(--gallery-check); border-color: var(--gallery-check); }
.gallery-icon-heart.is-active i,
.gallery-icon-check.is-active i { color: #fff; }

/* Dial the favorite/selection chips down a touch — they read a bit heavy.
   Smaller box + noticeably smaller glyph so they feel like refined controls. */
.gallery-icon { width: 34px; height: 34px; border-radius: 10px; }
.gallery-icon i { font-size: 15px; }
.gallery-icon-lb { width: 38px; height: 38px; }
.gallery-icon-lb i { font-size: 17px; }

/* ── Square transaction lookup (mark-paid) ───────────────────────────────── */
.square-lookup-hint { margin-left: 10px; }
.square-lookup-panel {
    margin-top: 12px;
    border: 1px solid var(--border, #e7e7ea);
    border-radius: 12px;
    padding: 12px 14px;
    background: var(--bg-soft, #f7f7f8);
}
.square-lookup-status { margin: 0 0 8px; }
.square-lookup-list { display: flex; flex-direction: column; gap: 6px; max-height: 320px; overflow-y: auto; }
.square-pay-row {
    display: flex; flex-direction: column; gap: 2px; align-items: flex-start;
    width: 100%; text-align: left;
    padding: 10px 12px; border-radius: 9px;
    background: #fff; border: 1px solid var(--border, #e7e7ea);
    cursor: pointer; transition: border-color .12s ease, box-shadow .12s ease;
}
.square-pay-row:hover { border-color: #bdbdc4; box-shadow: 0 1px 4px rgba(0,0,0,.08); }
.square-pay-row.is-match { border-color: var(--brand-accent, #ff6b35); background: var(--brand-accent-tint, #fff2ec); }
.square-pay-amt { font-weight: 700; font-variant-numeric: tabular-nums; }
.square-pay-meta { font-size: 12.5px; color: var(--text-muted, #6b7280); }
.square-pay-badge {
    font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em;
    color: #fff; background: var(--brand-accent, #ff6b35);
    padding: 2px 7px; border-radius: 999px; margin-left: 6px; vertical-align: middle;
}
.square-lookup-chosen {
    margin-top: 12px; padding: 10px 13px; border-radius: 10px;
    background: #ecfdf3; border: 1px solid #abefc6; color: #067647;
    font-size: 13.5px; display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
}
.square-lookup-chosen a { color: #067647; font-weight: 600; }
.square-pay-clear {
    margin-left: auto; background: none; border: 0; cursor: pointer;
    color: #067647; text-decoration: underline; font-size: 12.5px;
}

/* [hidden] must beat the flex display, or the empty "chosen" bar shows prematurely. */
.square-lookup-chosen[hidden] { display: none; }

/* Invoice "Opened" column — open/sent state, distinct from the financial status. */
.invoice-opened { font-size: 13px; font-weight: 500; white-space: nowrap; }
.invoice-opened i { font-size: 11px; margin-right: 1px; }
.invoice-opened-opened { color: #0f7c50; }
.invoice-opened-muted  { color: var(--text-muted, #8a8f98); }

/* Redeem-a-discount-code row in the billing manage panel (existing subscribers). */
.billing-redeem-row { display: flex; gap: 8px; align-items: center; }
.billing-redeem-input { width: 150px; text-transform: uppercase; }
.billing-redeem-input::placeholder { text-transform: none; }
.billing-promo-msg { display: block; margin-top: 6px; }
.billing-promo-msg.is-ok  { color: #067647; }
.billing-promo-msg.is-err { color: #b42318; }
.billing-promo-msg[hidden] { display: none; }
/* First Light early-access callout on the billing page. */
.billing-fl-callout {
    display: flex; gap: var(--sp-3); align-items: flex-start;
    margin: 0 0 var(--sp-5); padding: var(--sp-4);
    border: 1px solid var(--brand-accent); border-radius: var(--radius, 10px);
    background: var(--brand-accent-tint);
}
.billing-fl-callout > i { width: 22px; height: 22px; flex: none; color: var(--brand-accent-strong); margin-top: 1px; }
.billing-fl-title { margin: 0 0 2px; font-weight: 700; color: var(--brand-accent-strong); }
.billing-fl-sub { margin: 0; font-size: .9rem; color: var(--text-med); line-height: 1.45; }

/* "Unopened" state — subtle amber so an unseen invoice catches the eye. */
.invoice-opened-unopened { color: #b54708; }

/* ── Branded "studio not found" page (includes/studio_not_found.php) ──────────
   Shown when a link resolves to no studio. Mobile-first: centered card, finder. */
.studio-nf-body { background: var(--surface-2, #f4f4f5); }
.studio-nf {
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}
.studio-nf-card {
    width: 100%;
    max-width: 460px;
    background: #fff;
    border: 1px solid var(--border, #e5e5e5);
    border-radius: 16px;
    padding: 32px 24px;
    text-align: center;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 12px 32px rgba(0, 0, 0, .06);
}
.studio-nf-brand {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    text-decoration: none;
    color: inherit;
    margin-bottom: 20px;
}
.studio-nf-title {
    font-size: 1.35rem;
    font-weight: 800;
    letter-spacing: -.01em;
    margin: 0 0 8px;
    color: var(--ink, #0a0a0a);
}
.studio-nf-sub {
    margin: 0 0 18px;
    color: var(--ink-soft, #555);
    line-height: 1.5;
}
.studio-nf-note {
    background: #fff7ed;
    border: 1px solid #fed7aa;
    color: #9a3412;
    border-radius: 10px;
    padding: 10px 12px;
    margin: 0 0 16px;
    font-size: .92rem;
    line-height: 1.45;
    text-align: left;
}
.studio-nf-form {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin: 0 0 16px;
}
.studio-nf-submit { width: 100%; }
.studio-nf-help { margin: 0; font-size: .9rem; color: var(--ink-soft, #555); }

/* Roomier card + side-by-side finder once there's width for it. */
@media (min-width: 600px) {
    .studio-nf-card { padding: 40px 36px; }
    .studio-nf-form { flex-direction: row; }
    .studio-nf-input { flex: 1 1 auto; }
    .studio-nf-submit { width: auto; flex: 0 0 auto; white-space: nowrap; }
}

/* ── Lucide icons (render-time swap from Font Awesome; see assets/js/icons.js) ──
 * Sized to the surrounding font like the icon font they replace; color inherits
 * via currentColor (Lucide strokes use currentColor by default). The -0.125em
 * vertical-align mirrors Font Awesome's baseline so inline icon+text stays aligned. */
svg.lucide {
    width: 1em;
    height: 1em;
    display: inline-block;
    vertical-align: -0.125em;
    flex-shrink: 0;
}

/* ── Collapsed-rail nav tooltip (driven by portal.js) ──────────────────────────
 * Fixed-position so it escapes the sidebar's overflow clipping; positioned at the
 * hovered nav item's right edge. */
.nav-rail-tooltip {
    position: fixed;
    transform: translateY(-50%);
    background: #0a0a0a;
    color: #fff;
    font-size: .8rem;
    font-weight: 500;
    line-height: 1;
    padding: 7px 10px;
    border-radius: 7px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, .18);
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    z-index: 4000;
    transition: opacity .12s ease;
}
.nav-rail-tooltip.is-shown { opacity: 1; }

/* ── Gallery icon emphasis (state = fill) ──────────────────────────────────────
 * FA glyphs were solid; Lucide are outline. Keep the resting state outline (the
 * conventional "not selected" look) and FILL the active state so a favorited /
 * starred photo reads as a solid glyph. fill:currentColor inherits the same color
 * the existing .is-active rules set on the icon (white on the colored pill). The
 * studio-pick + guest-fav badges only ever show when set, so they're always solid.
 * (Selections use square-check, which looks wrong filled, so it stays outline.) */
.gallery-icon-heart.is-active svg.lucide,
.gallery-icon-star.is-active svg.lucide,
.gallery-icon-lb-heart.is-active svg.lucide,
.gallery-icon-lb-star.is-active svg.lucide,
.gallery-studio-pick-badge svg.lucide,
.gallery-guest-fav-badge svg.lucide { fill: currentColor; }

/* ══════════════════════════════════════════════════════════════════════════
   Premium gallery markers (2026 redesign)
   Supersedes every earlier .gallery-icon / badge / buy-pill treatment above
   (bare-white glyphs → white boxes → saturated square fills → frosted glass,
   accreted over several passes). The reference is what the giants actually do
   for these exact two controls:
     • Selection check  → Google Photos / Apple Photos: a solid, opaque, CRISP
       circle + white check. Kept the circle (it's the universal "selected"
       affordance), swapped the fill to a premium emerald green.
     • Favorite heart   → Pixieset / Apple Photos / Instagram: same crisp disc,
       muted rose when active.
   Rules:
     • SHARP, not hazy: no backdrop blur, no soft halo. Solid fills + one tight
       shadow for edge separation on a photo.
     • Two semantic colours only, both muted/premium: rose (favorite), emerald
       (selection). No orange/green/amber rainbow; informational badges go
       neutral dark.
     • Inactive controls HOVER-reveal; only real state persists at rest.
     • The TILE echoes selection (green ring + inset) so state reads at a glance.
   ══════════════════════════════════════════════════════════════════════════ */

/* Resting chip: crisp dark disc (no blur), sharp white glyph. Applies to heart,
   check, guest star and download, plus their larger lightbox variants. */
.gallery-icon {
    width: 32px; height: 32px;
    border-radius: 999px;
    background: rgba(17, 17, 19, .5);
    border: 0;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .35);
    -webkit-backdrop-filter: none;
            backdrop-filter: none;
    transition: background .13s ease, transform .12s ease, opacity .15s ease;
}
.gallery-icon i {
    color: #fff;
    font-size: 16px;
    -webkit-text-stroke: 0;
    text-shadow: none;
}
.gallery-icon:hover { background: rgba(17, 17, 19, .72); }
.gallery-icon:hover i { color: #fff; }
.gallery-icon-lb { width: 38px; height: 38px; }
.gallery-icon-lb i { font-size: 18px; }

/* Hover-reveal the INACTIVE heart/check on the grid (the download already does
   this). Active state and touch devices (no hover) keep them visible. The
   lightbox chips live outside .gallery-tile, so they're never hidden. */
.gallery-tile .gallery-icon-heart:not(.is-active),
.gallery-tile .gallery-icon-check:not(.is-active) { opacity: 0; }
.gallery-tile:hover .gallery-icon-heart,
.gallery-tile:hover .gallery-icon-check,
.gallery-tile:focus-within .gallery-icon-heart,
.gallery-tile:focus-within .gallery-icon-check { opacity: 1; }
@media (hover: none) {
    .gallery-tile .gallery-icon-heart,
    .gallery-tile .gallery-icon-check { opacity: 1; }
    /* Keep the ≥44px touch target (this block is later in the file than the
       32px base above, which would otherwise shrink it on phones). */
    .gallery-icon { width: 44px; height: 44px; }
}

/* Favorite — muted rose disc, white heart. Crisp, opaque. */
.gallery-icon-heart.is-active,
.gallery-icon-heart.is-active:hover {
    background: #d1466a;
    box-shadow: 0 1px 4px rgba(0, 0, 0, .3);
}
.gallery-icon-heart.is-active i { color: #fff; -webkit-text-stroke: 0; }

/* Selection — premium emerald disc, white check (the Google/Apple pattern). */
.gallery-icon-check.is-active,
.gallery-icon-check.is-active:hover {
    background: #0f9d6b;
    box-shadow: 0 1px 4px rgba(0, 0, 0, .3);
}
.gallery-icon-check.is-active i { color: #fff; -webkit-text-stroke: 0; }

/* Guest star (share page) — muted amber disc, same crisp shape. */
.gallery-icon-star.is-active,
.gallery-icon-star.is-active:hover {
    background: #d3963c;
    box-shadow: 0 1px 4px rgba(0, 0, 0, .3);
}
.gallery-icon-star.is-active i { color: #fff; -webkit-text-stroke: 0; }

/* Photographer's pick: NO container. A bare, prominent Edit-orange thumbs-up
   (filled via fill:currentColor) with a crisp shadow so it reads on any photo.
   Bigger than the old disc glyph for the emphasis Tyler wanted. */
.gallery-studio-pick-badge {
    width: auto; height: auto;
    background: transparent;
    color: var(--brand-accent, #ff6b35);
    font-size: 15px;
    -webkit-backdrop-filter: none;
            backdrop-filter: none;
    filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .5));
}
/* Guest-fav star keeps a small crisp neutral dark disc (white star). */
.gallery-guest-fav-badge {
    background: rgba(17, 17, 19, .62);
    -webkit-backdrop-filter: none;
            backdrop-filter: none;
}
/* The old stack overlapped two equal circles with a mask cutout. The pick is
   now a bare glyph, so drop the overlap + mask and just space them with a gap. */
.gallery-pick-stack > * + * { margin-left: 5px; }
.gallery-pick-stack > :first-child:not(:last-child) {
    -webkit-mask: none;
            mask: none;
}

/* Buy pill + purchased badge: crisp solid dark, no blur. The confirmed "In
   cart" state keeps its solid brand-accent fill (defined earlier, intact) —
   commerce accent stays distinct from the emerald selection state. */
.gallery-buy-btn,
.gallery-purchased-badge {
    background: rgba(17, 17, 19, .66);
    border: 0;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .35);
    -webkit-backdrop-filter: none;
            backdrop-filter: none;
}
.gallery-buy-btn:not(.is-in-cart):hover { background: rgba(17, 17, 19, .82); }

/* The tile echoes selection: emerald ring + faint inset so the pick reads at a
   glance. Favorite gets a soft rose ring. Selection wins visually. */
.gallery-tile.is-favorited {
    border-color: #d1466a;
}
.gallery-tile.is-selected-pick {
    border-color: #0f9d6b;
    box-shadow: 0 0 0 2px #0f9d6b inset;
}

/* ── Lucide spinner (replaces fa-spin after the Font Awesome removal) ──────────
 * Applied to an <i class="icon-spin" data-lucide="loader-circle">; the shim puts
 * the svg inside, and rotating the <i> rotates the glyph. */
@keyframes ed-spin { to { transform: rotate(360deg); } }
.icon-spin { display: inline-flex; animation: ed-spin .8s linear infinite; }

svg.brand-icon { width: 1em; height: 1em; display: inline-block; vertical-align: -0.125em; }

/* ============================================================================
   Guide section — public /getting-started, /features, /faq
   Shared chrome from includes/guide.php. Mobile-first.
   ============================================================================ */
.guide-page { min-height: 100vh; background: var(--bg); color: var(--text); }

/* Header */
.guide-header {
    position: sticky; top: 0; z-index: 20;
    display: flex; align-items: center; gap: var(--sp-4);
    flex-wrap: wrap;
    padding: var(--sp-3) var(--sp-4);
    background: color-mix(in srgb, var(--bg) 88%, transparent);
    backdrop-filter: saturate(1.4) blur(8px);
    border-bottom: 1px solid var(--border);
}
/* Edit logo: the "E" mark with the wordmark sliding out from behind it on hover,
   absolutely positioned so it never shifts the nav (mirrors the landing header). */
.guide-header .brand-toggle { flex: 0 0 auto; position: relative; }
.guide-header .brand-toggle .brand-wordmark { position: absolute; left: 100%; top: 50%; transform: translateY(-50%); }
.guide-nav {
    display: flex; gap: var(--sp-1); order: 3; flex: 1 1 100%;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    margin-top: var(--sp-2);
}
.guide-nav-link {
    padding: 6px 10px; border-radius: var(--radius-sm, 8px);
    font-size: .9rem; font-weight: 600; color: var(--text-med);
    text-decoration: none; white-space: nowrap;
    transition: background .12s, color .12s;
}
.guide-nav-link:hover { background: var(--bg-soft); color: var(--text); }
.guide-nav-link.is-active { background: var(--bg-muted); color: var(--text); }
.guide-header-cta { display: flex; align-items: center; gap: var(--sp-3); margin-left: auto; order: 2; }
.guide-login { font-size: .9rem; font-weight: 600; color: var(--text-med); text-decoration: none; }
.guide-login:hover { color: var(--text); }

@media (min-width: 900px) {
    .guide-header { flex-wrap: nowrap; padding: var(--sp-3) var(--sp-6); }
    .guide-nav { order: 0; flex: 0 1 auto; margin: 0 auto; overflow: visible; }
    .guide-header-cta { order: 0; margin-left: 0; }
}

/* Main + hero */
.guide-main { margin: 0 auto; padding: var(--sp-6) var(--sp-5) var(--sp-8); }
.guide-main-prose { max-width: 760px; }
.guide-main-wide  { max-width: 1120px; }
.guide-hero { margin-bottom: var(--sp-6); }
.guide-title { font-size: clamp(1.8rem, 5vw, 2.6rem); font-weight: 900; letter-spacing: -.02em; margin: 0 0 var(--sp-3); }
.guide-lead { font-size: 1.08rem; line-height: 1.6; color: var(--text-med); margin: 0; max-width: 680px; }

/* Table of contents (Getting Started) */
.guide-toc {
    background: var(--bg-soft); border: 1px solid var(--border);
    border-radius: var(--radius); padding: var(--sp-4) var(--sp-5); margin-bottom: var(--sp-6);
}
.guide-toc-title { font-size: .8rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--text-light); margin: 0 0 var(--sp-3); }
.guide-toc-list { margin: 0; padding-left: 1.2em; columns: 2; column-gap: var(--sp-6); }
.guide-toc-list li { margin-bottom: 6px; break-inside: avoid; }
.guide-toc-list a { color: var(--brand-accent-strong, #c2410c); text-decoration: none; }
.guide-toc-list a:hover { text-decoration: underline; }
@media (max-width: 600px) { .guide-toc-list { columns: 1; } }

/* Prose sections */
.guide-section { margin-bottom: var(--sp-7); scroll-margin-top: 96px; }
.guide-section h2 { font-size: 1.4rem; font-weight: 800; letter-spacing: -.01em; margin: 0 0 var(--sp-3); padding-top: var(--sp-2); }
.guide-section p { line-height: 1.7; color: var(--text-med); margin: 0 0 var(--sp-3); }
.guide-section a { color: var(--brand-accent-strong, #c2410c); }
.guide-steps, .guide-list { margin: var(--sp-3) 0; padding-left: 1.3em; }
.guide-steps li, .guide-list li { line-height: 1.6; color: var(--text-med); margin-bottom: var(--sp-3); }
.guide-steps strong, .guide-list strong { color: var(--text); }
.guide-note {
    background: var(--warn-bg); border-left: 3px solid var(--warn-border);
    padding: var(--sp-3) var(--sp-4); border-radius: var(--radius-sm, 8px);
    font-size: .95rem; color: var(--text-med) !important;
}

/* Section mockups (inline SVG frames of the real UI) */
.guide-figure { margin: var(--sp-5) 0 var(--sp-6); }
.guide-svg { display: block; width: 100%; height: auto; max-width: 600px; margin: 0 auto; filter: drop-shadow(0 8px 24px rgba(0,0,0,.06)); }
.guide-figcaption { font-size: .82rem; color: var(--text-light); text-align: center; margin-top: var(--sp-3); }

/* Features grid */
.guide-feature-grid { display: grid; grid-template-columns: 1fr; gap: var(--sp-4); }
@media (min-width: 700px)  { .guide-feature-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1100px) { .guide-feature-grid { grid-template-columns: repeat(3, 1fr); } }
.guide-feature-card { background: var(--bg-soft); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--sp-5); }
.guide-feature-title { font-size: 1.05rem; font-weight: 800; margin: 0 0 var(--sp-4); display: flex; align-items: center; gap: 9px; }
.guide-feature-title svg, .guide-feature-title i { width: 19px; height: 19px; color: var(--brand-accent); flex: 0 0 auto; }
.guide-feature-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.guide-feature-list li { display: flex; flex-direction: column; gap: 2px; }
.guide-feature-name { font-weight: 600; font-size: .95rem; color: var(--text); }
.guide-feature-desc { font-size: .88rem; line-height: 1.5; color: var(--text-light); }
.guide-feature-foot { margin-top: var(--sp-6); text-align: center; color: var(--text-med); }
.guide-feature-foot a { color: var(--brand-accent-strong, #c2410c); }

/* FAQ */
.guide-faq-search { position: sticky; top: 72px; z-index: 10; background: var(--bg); padding: var(--sp-3) 0 var(--sp-4); margin-bottom: var(--sp-4); }
.guide-faq-input { font-size: 1rem; }
.guide-faq-count { font-size: .82rem; color: var(--text-light); margin: var(--sp-2) 0 0; }
.guide-faq-group { margin-bottom: var(--sp-6); }
.guide-faq-group-title { font-size: 1.05rem; font-weight: 800; color: var(--text-med); margin: 0 0 var(--sp-3); text-transform: uppercase; letter-spacing: .04em; }
.guide-faq-item { border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: var(--sp-3); background: var(--bg-soft); overflow: hidden; }
.guide-faq-q { cursor: pointer; padding: var(--sp-4); font-weight: 600; list-style: none; position: relative; padding-right: 2.4rem; }
.guide-faq-q::-webkit-details-marker { display: none; }
.guide-faq-q::after { content: '+'; position: absolute; right: var(--sp-4); top: 50%; transform: translateY(-50%); font-size: 1.3rem; color: var(--text-light); line-height: 1; }
.guide-faq-item[open] .guide-faq-q::after { content: '\2013'; }
.guide-faq-q:hover { background: var(--bg-muted); }
.guide-faq-a { padding: 0 var(--sp-4) var(--sp-4); line-height: 1.65; color: var(--text-med); }
.guide-faq-a a { color: var(--brand-accent-strong, #c2410c); }
.guide-faq-empty { text-align: center; color: var(--text-med); padding: var(--sp-5); }
.guide-faq-empty a { color: var(--brand-accent-strong, #c2410c); }

/* Footer */
.guide-footer { border-top: 1px solid var(--border); padding: var(--sp-6) var(--sp-5); text-align: center; }
.guide-footer-nav { display: flex; justify-content: center; flex-wrap: wrap; gap: var(--sp-4); margin-bottom: var(--sp-3); }
.guide-footer-nav a { color: var(--text-med); text-decoration: none; font-weight: 600; font-size: .9rem; }
.guide-footer-nav a:hover { color: var(--text); }
.guide-footer-cta { color: var(--text-med); margin: 0 0 var(--sp-2); }
.guide-footer-cta a { color: var(--brand-accent-strong, #c2410c); }
.guide-footer-legal { font-size: .82rem; color: var(--text-light); margin: 0; }
.guide-footer-legal a { color: var(--text-light); }


/* ============================================================================
   Read-only demo chrome — the demo bar (Studio/Client toggle) + toast.
   Rendered by includes/{portal,client}_layout.php when the session is a public
   demo visitor; driven by assets/js/demo-portal.js.
   ============================================================================ */
/* The demo chrome lives INSIDE the app topbar (no separate stacked bar that
   would add height and push the page down). On desktop the topbar becomes a
   3-column grid so the Studio/Client toggle sits dead-center between the page
   title and the right-hand cluster; on mobile it reflows to flex. */
@media (min-width: 641px) {
    .admin-topbar-demo {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
        gap: var(--sp-3);
    }
    .admin-topbar-demo .topbar-page-title { justify-self: start; }
    .admin-topbar-demo .demo-switch-wrap  { justify-self: center; }
    .admin-topbar-demo .topbar-right      { justify-self: end; }
}
.demo-switch-wrap { display: inline-flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.demo-switch-label {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: .8rem; font-weight: 600; color: var(--text-light); white-space: nowrap;
}
.demo-switch-dot { width: 8px; height: 8px; border-radius: 50%; background: #22c55e; box-shadow: 0 0 0 3px rgba(34,197,94,.2); }
.demo-switch {
    display: inline-flex; gap: 2px; padding: 3px;
    background: rgba(15,23,42,.06); border: 1px solid var(--border);
    border-radius: var(--radius-pill);
}
.demo-switch-tab {
    padding: 5px 14px; border-radius: var(--radius-pill);
    font-size: .82rem; font-weight: 600; line-height: 1;
    color: var(--text-light); text-decoration: none; white-space: nowrap;
}
.demo-switch-tab.is-active { background: var(--brand); color: var(--text-inverse); }
.demo-topbar-cta { white-space: nowrap; }
@media (max-width: 640px) {
    /* Phone: free up the row — the toggle is the point, the title + trial CTA
       are not (the demo toast still drives the trial conversion). */
    .admin-topbar-demo .topbar-page-title { display: none; }
    .demo-switch-label { display: none; }
    .demo-topbar-cta   { display: none; }
    .demo-switch-tab   { padding: 5px 11px; }
}

/* Demo toast (assets/js/demo-portal.js) */
.demo-toast {
    position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%) translateY(10px);
    background: var(--brand); color: var(--text-inverse); padding: .7rem 1.1rem; border-radius: var(--radius);
    font-size: .88rem; font-weight: 600; box-shadow: var(--shadow-lg); z-index: 300;
    max-width: 90vw; text-align: center; opacity: 0; pointer-events: none;
    transition: opacity .22s ease, transform .22s ease;
}
.demo-toast.is-shown { opacity: 1; transform: translateX(-50%) translateY(0); }

/* Agreement view: vertical stack of action buttons with even spacing. */
.ag-action-stack { display: flex; flex-direction: column; gap: var(--sp-3); }
