/* Ohmaishoot Photographer Portal — light, compact, Photohawk-style dashboard.
   Deliberately separate from admin-v2.css's dark admin theme: this is an
   external-facing surface for photographers, not the internal admin tool. */

:root {
    --ph-bg: #f6f7f9;
    --ph-surface: #ffffff;
    --ph-border: #e5e7eb;
    --ph-text: #1f2430;
    --ph-text-dim: #6b7280;
    --ph-text-mute: #9ca3af;
    --ph-accent: #4f46e5;
    --ph-accent-soft: #eef2ff;
    --ph-success: #16a34a;
    --ph-warn: #d97706;
    --ph-radius: 10px;
    --ph-radius-sm: 7px;

    /* Aliases matching admin-v2.css's variable names, used inline in
       photographer.js/index.html — keeps those inline styles working without
       touching every call site now that this page has its own light theme. */
    --text-dim: var(--ph-text-dim);
    --border: var(--ph-border);
}

* { box-sizing: border-box; }

body {
    margin: 0;
    /* Emoji-aware font stack: emoji glyphs (achievement icons, status
       badges, nav icons) need a dedicated font fallback because Inter
       + system sans don't ship with the colour-emoji range. Listing
       "Segoe UI Emoji" (Win10/11), "Apple Color Emoji" (macOS/iOS),
       "Noto Color Emoji" (Linux/Chromium fallback), and the older
       Twemoji-Mozilla variant ensures the 📅 / 🛒 / 🏁 glyphs render
       instead of falling through to missing-glyph "tofu" boxes. */
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI Emoji', 'Segoe UI Symbol', 'Apple Color Emoji', 'Noto Color Emoji', 'Twemoji Mozilla', sans-serif;
    background: var(--ph-bg);
    color: var(--ph-text);
    font-size: 14px;
}

code {
    background: var(--ph-accent-soft);
    color: var(--ph-accent);
    padding: .1rem .35rem;
    border-radius: 4px;
    font-size: .85em;
}

/* ── Login ── */
.login-screen {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--ph-bg);
}
.login-box {
    background: var(--ph-surface);
    border: 1px solid var(--ph-border);
    border-radius: var(--ph-radius);
    padding: 2.25rem 2rem;
    width: 100%;
    max-width: 340px;
    text-align: center;
    box-shadow: 0 1px 3px rgba(0,0,0,0.04);
}
.login-box h1 { margin: 0 0 .15rem; font-size: 1.3rem; font-weight: 700; }
.login-box p { margin: 0 0 1.5rem; color: var(--ph-text-dim); font-size: .85rem; }
.login-box input {
    width: 100%;
    padding: .65rem .8rem;
    margin-bottom: .6rem;
    border: 1px solid var(--ph-border);
    border-radius: var(--ph-radius-sm);
    background: var(--ph-surface);
    color: var(--ph-text);
    font-size: .88rem;
}
.login-box input:focus { outline: none; border-color: var(--ph-accent); }
.login-box button {
    width: 100%;
    padding: .65rem;
    margin-top: .3rem;
    border: none;
    border-radius: var(--ph-radius-sm);
    background: var(--ph-accent);
    color: #fff;
    font-weight: 600;
    font-size: .88rem;
    cursor: pointer;
}
.login-box button:hover { background: #4338ca; }
.login-error { color: #dc2626; font-size: .8rem; margin: .75rem 0 0; min-height: 1em; }

/* ── Layout ── */
#photographer-app { display: flex; min-height: 100vh; }

.sidebar {
    width: 210px;
    flex-shrink: 0;
    background: var(--ph-surface);
    border-right: 1px solid var(--ph-border);
    padding: 1.25rem 0;
    display: flex;
    flex-direction: column;
}
.sidebar-brand { padding: 0 1.25rem 1.25rem; border-bottom: 1px solid var(--ph-border); margin-bottom: .75rem; }
.sidebar-brand h1 { margin: 0; font-size: 1.05rem; font-weight: 700; }
.sidebar-brand span { font-size: .75rem; color: var(--ph-text-dim); }
.sidebar-nav { display: flex; flex-direction: column; gap: .1rem; padding: 0 .6rem; }
.nav-item {
    display: flex;
    align-items: center;
    gap: .55rem;
    width: 100%;
    padding: .55rem .65rem;
    border: none;
    background: transparent;
    border-radius: var(--ph-radius-sm);
    color: var(--ph-text-dim);
    font-size: .85rem;
    font-weight: 500;
    cursor: pointer;
    text-align: left;
}
.nav-item:hover { background: var(--ph-bg); color: var(--ph-text); }
.nav-item.active { background: var(--ph-accent-soft); color: var(--ph-accent); }
.nav-icon { font-size: .95rem; width: 1.1em; text-align: center; }

/* Pill-style notification badge for nav items. Pushed to the right edge of
   the button via margin-left:auto. Empty badge = hidden (no DOM noise when
   there are zero items to surface). */
.nav-badge {
    margin-left: auto;
    min-width: 1.25rem;
    height: 1.25rem;
    padding: 0 .4rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: #ef4444;
    color: #fff;
    font-size: .68rem;
    font-weight: 700;
    border-radius: 999px;
    line-height: 1;
    /* Tabular numerals so 9→10→99 doesn't shift the button width. */
    font-variant-numeric: tabular-nums;
}
.nav-badge:empty { display: none; }

.main-content { flex: 1; padding: 1.75rem 2rem; min-width: 0; /* let it shrink inside flex without forcing overflow */ }

.tab-content { display: none; }
.tab-content.active { display: block; }

.page-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1.25rem;
}
.page-header h2 { margin: 0 0 .2rem; font-size: 1.15rem; font-weight: 700; }
.page-header p { margin: 0; font-size: .82rem; color: var(--ph-text-dim); }

/* ── Date-range segmented control ──
   Shared between the Dashboard and Orders tabs so a range picked in one
   stays in sync in the other. .range-label surfaces the active window
   next to the section title so it's obvious which slice the numbers cover. */
.range-tabs {
    display: inline-flex;
    background: var(--ph-surface);
    border: 1px solid var(--ph-border);
    border-radius: var(--ph-radius-sm);
    padding: 2px;
    gap: 1px;
}
.range-tab {
    border: none;
    background: transparent;
    color: var(--ph-text-dim);
    font-size: .78rem;
    font-weight: 600;
    padding: .35rem .65rem;
    border-radius: 5px;
    cursor: pointer;
    transition: background-color .12s ease, color .12s ease;
    min-width: 2.4rem;
}
.range-tab:hover:not(.active) { background: var(--ph-bg); color: var(--ph-text); }
.range-tab.active {
    background: var(--ph-accent);
    color: #fff;
}
.range-label {
    color: var(--ph-text-mute);
    font-weight: 500;
    font-size: .8em;
}

/* ── Compact stat cards (Photohawk-style: small label, bold value, no big icon block) ── */
.stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: .7rem;
    margin-bottom: 1.25rem;
}
.stat-card {
    background: var(--ph-surface);
    border: 1px solid var(--ph-border);
    border-radius: var(--ph-radius);
    padding: .85rem .95rem;
}
.stat-card .stat-icon {
    font-size: .82rem;
    color: var(--ph-text-dim);
    margin-bottom: .3rem;
    display: block;
}
.stat-value { font-size: 1.3rem; font-weight: 700; line-height: 1.2; }
.stat-label { font-size: .72rem; color: var(--ph-text-dim); margin-top: .15rem; }
/* Stat-card variant for the "what you keep" figure — mirrors the green
   profit column in the data tables so the colour cue stays consistent
   across the dashboard. */
.stat-card.stat-profit { border-color: #16a34a; }
.stat-card.stat-profit .stat-value { color: #15803d; }

/* Sparkline inside each stat card — small line graph showing the same
   metric over the selected range. Fills the card width, 30px tall.
   Coloured line matches the metric (red for deductions, green for profit). */
.stat-sparkline {
    width: 100%;
    height: 28px;
    margin-top: .35rem;
    display: block;
    overflow: visible;
}
.sparkline-line {
    fill: none;
    stroke: var(--ph-text-dim);
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
    /* Subtle fill area under the line for visual weight, even on tiny
       cards. Fades to transparent so the line stays the focal point. */
}
.sparkline-line.sparkline-deduct { stroke: #dc2626; }
.sparkline-line.sparkline-profit { stroke: #16a34a; }

/* ── Today callout ──
   Compact horizontal strip showing today's profit + order count + a
   comparison to yesterday. Stays out of the way above the stats grid —
   the photographer glances at it once and knows how the day is going. */
.today-callout {
    display: flex;
    align-items: center;
    gap: 1.25rem;
    padding: .85rem 1.1rem;
    margin-bottom: 1rem;
    background: linear-gradient(135deg, #f0fdf4 0%, var(--ph-surface) 70%);
    border: 1px solid #16a34a;
    border-radius: var(--ph-radius);
    box-shadow: 0 1px 3px rgba(22, 163, 74, .08);
    flex-wrap: wrap;
}
.today-callout-main {
    display: flex;
    flex-direction: column;
    gap: .1rem;
}
.today-callout-amount {
    font-size: 1.5rem;
    font-weight: 700;
    color: #15803d;
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
}
.today-callout-label {
    font-size: .72rem;
    color: var(--ph-text-dim);
    text-transform: uppercase;
    letter-spacing: .05em;
    font-weight: 600;
}
.today-callout-divider {
    width: 1px;
    align-self: stretch;
    background: var(--ph-border);
}
.today-callout-stats {
    display: flex;
    gap: 1.5rem;
    flex: 1;
}
.today-callout-stat {
    display: flex;
    flex-direction: column;
    gap: .1rem;
}
.today-callout-stat-label {
    font-size: .72rem;
    color: var(--ph-text-dim);
    text-transform: uppercase;
    letter-spacing: .04em;
    font-weight: 600;
}
.today-callout-stat-value {
    font-size: 1.1rem;
    font-weight: 700;
    color: var(--ph-text);
    font-variant-numeric: tabular-nums;
}
.today-callout-stat-value.up { color: #16a34a; }
.today-callout-stat-value.down { color: #dc2626; }

/* ── Order detail modal + photo lightbox ──
   One overlay hosts two stacked views: the order detail (photo grid +
   money chain) and the per-photo lightbox (large image + download). The
   lightbox replaces the detail view inline rather than nesting, so the
   "back" button is a clear "← Back to order" affordance. */
.order-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, .55);
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    animation: backdrop-fade-in .15s ease-out;
}
@keyframes backdrop-fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
}
.order-modal {
    background: var(--ph-surface);
    border-radius: var(--ph-radius);
    width: 100%;
    max-width: 920px;
    max-height: 90vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    box-shadow: 0 20px 60px rgba(0, 0, 0, .25);
    animation: modal-rise .2s ease-out;
}
@keyframes modal-rise {
    from { transform: translateY(20px); opacity: 0; }
    to { transform: translateY(0); opacity: 1; }
}
.order-modal-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--ph-border);
}
.order-modal-header h3 { margin: 0 0 .15rem; font-size: 1rem; font-weight: 700; }
.order-modal-meta {
    font-size: .78rem;
    color: var(--ph-text-dim);
}
.order-modal-close {
    background: none;
    border: none;
    font-size: 1.4rem;
    color: var(--ph-text-dim);
    cursor: pointer;
    line-height: 1;
    padding: 0 .25rem;
}
.order-modal-close:hover { color: var(--ph-text); }
.order-modal-summary {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
    gap: .55rem;
    padding: .85rem 1.25rem;
    background: var(--ph-bg);
    border-bottom: 1px solid var(--ph-border);
}
.order-modal-summary-item { display: flex; flex-direction: column; gap: .1rem; }
.order-modal-summary-label {
    font-size: .68rem;
    color: var(--ph-text-dim);
    text-transform: uppercase;
    letter-spacing: .04em;
    font-weight: 600;
}
.order-modal-summary-value {
    font-size: .95rem;
    font-weight: 700;
    color: var(--ph-text);
    font-variant-numeric: tabular-nums;
}
.order-modal-summary-value.deduct { color: #dc2626; }
.order-modal-summary-value.profit { color: #16a34a; }
.order-modal-body {
    padding: 1rem 1.25rem;
    overflow-y: auto;
    flex: 1;
}
.order-modal-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: .65rem;
}
.order-modal-photo {
    position: relative;
    cursor: pointer;
    background: var(--ph-bg);
    border: 1px solid var(--ph-border);
    border-radius: var(--ph-radius-sm);
    overflow: hidden;
    aspect-ratio: 1;
    transition: border-color .12s ease, transform .12s ease;
}
.order-modal-photo:hover {
    border-color: var(--ph-accent);
    transform: translateY(-1px);
}
.order-modal-photo img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.order-modal-photo-name {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    padding: .25rem .4rem;
    background: linear-gradient(0deg, rgba(0, 0, 0, .75) 0%, transparent 100%);
    color: #fff;
    font-size: .68rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Lightbox — replaces the order detail inside the same overlay. */
.lightbox {
    background: var(--ph-surface);
    border-radius: var(--ph-radius);
    width: 100%;
    max-width: 1100px;
    max-height: 92vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    box-shadow: 0 20px 60px rgba(0, 0, 0, .3);
    animation: modal-rise .2s ease-out;
}
.lightbox-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: .65rem 1rem;
    border-bottom: 1px solid var(--ph-border);
    background: var(--ph-bg);
}
.lightbox-back {
    background: none;
    border: 1px solid var(--ph-border);
    color: var(--ph-text);
    border-radius: var(--ph-radius-sm);
    padding: .35rem .65rem;
    font-size: .82rem;
    font-weight: 600;
    cursor: pointer;
}
.lightbox-back:hover { background: var(--ph-surface); }
.lightbox-image-wrap {
    flex: 1;
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #0f172a;
    overflow: hidden;
}
.lightbox-image {
    max-width: 100%;
    max-height: 70vh;
    object-fit: contain;
}
.lightbox-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: .75rem 1rem;
    border-top: 1px solid var(--ph-border);
    background: var(--ph-surface);
    flex-wrap: wrap;
}
.lightbox-filename {
    font-size: .85rem;
    color: var(--ph-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 1;
    min-width: 0;
}
.lightbox-actions {
    display: flex;
    gap: .5rem;
}
.data-table tbody tr { cursor: pointer; }
.data-table tbody tr:hover { background: rgba(79, 70, 229, .04); }

/* ── Insights panel (Dashboard) ──
   Auto-computed tips for the photographer. Each insight is a small card
   with an emoji, a short statement, and one or two CTAs (share gallery,
   create promo, etc.). Shown only when there's enough data to make a
   meaningful suggestion — empty state explains the panel is "learning"
   instead of just showing blank cards.
*/
.insights-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: .65rem;
}
.insight-card {
    display: flex;
    flex-direction: column;
    gap: .45rem;
    padding: .8rem .9rem;
    background: var(--ph-surface);
    border: 1px solid var(--ph-border);
    border-left-width: 3px;
    border-radius: var(--ph-radius-sm);
}
.insight-card.good { border-left-color: #16a34a; }
.insight-card.warn { border-left-color: #f59e0b; }
.insight-card.info { border-left-color: var(--ph-accent); }
.insight-icon {
    font-size: 1.25rem;
    line-height: 1;
}
.insight-title {
    font-size: .88rem;
    font-weight: 700;
    color: var(--ph-text);
    line-height: 1.3;
}
.insight-text {
    font-size: .8rem;
    color: var(--ph-text-dim);
    line-height: 1.45;
}
.insight-actions {
    display: flex;
    gap: .4rem;
    flex-wrap: wrap;
    margin-top: .15rem;
}

/* ── Inline order expand (per photographer feedback) ──
   Click an order row → a second <tr> below it appears, filling the
   table width with colspan. The expand row shows META (date, buyer
   email, money chain) and a LIST OF FILE NAMES (not images — the
   photographer said showing images immediately was overwhelming).
   Clicking a file name opens the lightbox preview.
   Multiple expand rows are allowed; the photographer can keep several
   orders open at once while comparing. */
.order-expand-row { background: var(--ph-bg); }
.order-expand-row > td { padding: 0; border-top: 1px solid var(--ph-border); }
.order-expand-body {
    padding: .85rem 1rem 1rem;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
}
.order-expand-meta { display: flex; flex-direction: column; gap: .5rem; }
.order-expand-meta-row {
    display: flex;
    align-items: baseline;
    gap: .5rem;
    font-size: .82rem;
    color: var(--ph-text-dim);
    flex-wrap: wrap;
}
.order-expand-meta-label {
    font-weight: 600;
    color: var(--ph-text-dim);
    text-transform: uppercase;
    letter-spacing: .04em;
    font-size: .68rem;
}
.order-expand-summary {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: .5rem;
    margin-top: .35rem;
}
.order-expand-summary-item { display: flex; flex-direction: column; gap: .1rem; }
.order-expand-summary-label {
    font-size: .66rem;
    color: var(--ph-text-dim);
    text-transform: uppercase;
    letter-spacing: .04em;
    font-weight: 600;
}
.order-expand-summary-value {
    font-size: .88rem;
    font-weight: 700;
    color: var(--ph-text);
    font-variant-numeric: tabular-nums;
}
.order-expand-summary-value.deduct { color: #dc2626; }
.order-expand-summary-value.profit { color: #16a34a; }
.order-expand-files {
    display: flex;
    flex-direction: column;
    gap: .35rem;
    min-width: 0;
}
.order-expand-files-title {
    font-size: .72rem;
    color: var(--ph-text-dim);
    text-transform: uppercase;
    letter-spacing: .04em;
    font-weight: 600;
}
.order-expand-file {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .35rem .5rem;
    background: var(--ph-surface);
    border: 1px solid var(--ph-border);
    border-radius: var(--ph-radius-sm);
    font-size: .82rem;
    cursor: pointer;
    color: var(--ph-text);
    transition: background-color .12s ease, border-color .12s ease;
    text-align: left;
    width: 100%;
}
.order-expand-file:hover {
    background: var(--ph-accent-soft);
    border-color: var(--ph-accent);
    color: var(--ph-accent);
}
.order-expand-file-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.order-expand-file-icon {
    flex-shrink: 0;
    font-size: .85rem;
    opacity: .6;
}
.order-expand-empty {
    color: var(--ph-text-dim);
    font-size: .82rem;
    font-style: italic;
}
@media (max-width: 768px) {
    .order-expand-body { grid-template-columns: 1fr; }
    .order-expand-summary { grid-template-columns: repeat(2, 1fr); }
}

/* ── Panels ── */
.premium-panel {
    background: var(--ph-surface);
    border: 1px solid var(--ph-border);
    border-radius: var(--ph-radius);
    padding: 1.1rem 1.2rem;
}
.panel-heading { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: .8rem; flex-wrap: wrap; }
.panel-heading h3 { margin: 0; font-size: .92rem; font-weight: 700; }

.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem; margin-bottom: .1rem; }
.form-group { margin-bottom: .8rem; }
.form-group label { display: block; font-size: .76rem; font-weight: 600; color: var(--ph-text-dim); margin-bottom: .3rem; }
.form-group input, .form-group select {
    width: 100%;
    padding: .55rem .7rem;
    border: 1px solid var(--ph-border);
    border-radius: var(--ph-radius-sm);
    background: var(--ph-surface);
    color: var(--ph-text);
    font-size: .85rem;
}
.form-group input:focus, .form-group select:focus { outline: none; border-color: var(--ph-accent); }

.btn {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .55rem 1rem;
    border-radius: var(--ph-radius-sm);
    font-size: .82rem;
    font-weight: 600;
    cursor: pointer;
    border: 1px solid transparent;
}
.btn-primary { background: var(--ph-accent); color: #fff; }
.btn-primary:hover { background: #4338ca; }
.btn-secondary { background: var(--ph-surface); color: var(--ph-text); border-color: var(--ph-border); }
.btn-secondary:hover { background: var(--ph-bg); }

/* ── Record rows (galleries, orders, payouts) ── */
.admin-record-card {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    padding: .7rem .1rem;
    border-top: 1px solid var(--ph-border);
    font-size: .85rem;
}
.admin-record-card:first-child { border-top: none; }
.admin-record-card strong { font-weight: 600; }

/* ── Order rows ──
   One line per order so a long list stays scannable instead of running three
   lines deep per entry. Title and date sit inline on the left, the money chain
   (gross → fee → commission → share) inline on the right. */
.order-row { padding: .5rem .1rem; }
.order-row-main {
    display: flex;
    align-items: baseline;
    gap: .5rem;
    min-width: 0;               /* lets the title ellipsize instead of shoving the figures */
}
.order-row-main strong {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.order-row-date {
    color: var(--text-dim);
    font-size: .78rem;
    white-space: nowrap;
}
.order-row-figures {
    display: flex;
    align-items: baseline;
    gap: .85rem;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;   /* columns of digits line up down the list */
}
.order-row-figures i {
    font-style: normal;
    color: var(--text-dim);
    font-size: .72rem;
    margin-left: .25rem;
}
.order-row-deduct { color: var(--text-dim); }

table { font-size: .85rem; }

/* ── Skeleton placeholders ──
   Shown in place of "Loading..." text on first paint of each panel. Same
   shape as the eventual real content so the layout doesn't jump when data
   lands. The shimmer is a single linear-gradient slide — no images, no JS. */
@keyframes skeleton-shimmer {
    0% { background-position: -200px 0; }
    100% { background-position: calc(200px + 100%) 0; }
}
.skeleton {
    display: block;
    height: .85rem;
    background-color: #eef0f3;
    background-image: linear-gradient(90deg, #eef0f3 0%, #f7f8fa 50%, #eef0f3 100%);
    background-size: 200px 100%;
    background-repeat: no-repeat;
    border-radius: 4px;
    color: transparent;
    animation: skeleton-shimmer 1.4s infinite linear;
    /* Avoid the skeleton inheriting the surrounding text color if a caller
       accidentally wraps it in a colored span. */
    -webkit-user-select: none; user-select: none;
}
.skeleton.skeleton-block { height: 1rem; }
.skeleton.skeleton-thumb { aspect-ratio: 1; height: auto; border-radius: 8px; }
.skeleton-table { display: grid; gap: .7rem; }
.skeleton-table-row {
    display: flex;
    gap: 1rem;
    align-items: center;
    padding: .35rem 0;
    border-top: 1px solid var(--ph-border);
}
.skeleton-table-row:first-child { border-top: none; }
.skeleton-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: .75rem;
}
.skeleton-card {
    display: flex;
    flex-direction: column;
    gap: .45rem;
    padding: .6rem 0;
    border-top: 1px solid var(--ph-border);
}
.skeleton-card:first-child { border-top: none; }
.skeleton-card-row {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    align-items: center;
}

/* ── Toast notifications ──
   Stacked bottom-right container. Each toast is its own element; the
   container handles positioning and spacing, individual toasts handle
   their own slide-in/out animation. Max 4 visible at once to keep the
   corner from becoming a wall of notifications. */
.toast-container {
    position: fixed;
    bottom: 1.25rem;
    right: 1.25rem;
    z-index: 1000;
    display: flex;
    flex-direction: column-reverse;
    gap: .55rem;
    pointer-events: none;
    max-width: 360px;
}
.toast {
    display: flex;
    align-items: flex-start;
    gap: .6rem;
    padding: .7rem .85rem;
    background: var(--ph-surface);
    border: 1px solid var(--ph-border);
    border-left-width: 3px;
    border-radius: var(--ph-radius-sm);
    box-shadow: 0 4px 14px rgba(0, 0, 0, .08);
    font-size: .85rem;
    color: var(--ph-text);
    pointer-events: auto;
    cursor: pointer;
    animation: toast-slide-in .25s ease-out;
}
.toast.toast-exit { animation: toast-slide-out .2s ease-in forwards; }
.toast-icon {
    font-size: 1rem;
    line-height: 1.2;
    flex-shrink: 0;
}
.toast-body { flex: 1; min-width: 0; }
.toast-title { font-weight: 600; }
.toast-text { color: var(--ph-text-dim); font-size: .8rem; margin-top: .1rem; }
.toast.toast-success { border-left-color: #16a34a; }
.toast.toast-success .toast-icon { color: #16a34a; }
.toast.toast-error { border-left-color: #dc2626; }
.toast.toast-error .toast-icon { color: #dc2626; }
.toast.toast-info { border-left-color: var(--ph-accent); }
.toast.toast-info .toast-icon { color: var(--ph-accent); }
.toast.toast-celebrate {
    border-left-color: #f59e0b;
    background: linear-gradient(135deg, #fffbeb 0%, var(--ph-surface) 50%);
}
.toast.toast-celebrate .toast-icon { color: #f59e0b; }
@keyframes toast-slide-in {
    from { transform: translateX(110%); opacity: 0; }
    to { transform: translateX(0); opacity: 1; }
}
@keyframes toast-slide-out {
    from { transform: translateX(0); opacity: 1; }
    to { transform: translateX(110%); opacity: 0; }
}

/* ── Confetti (first-sale celebration) ──
   One-shot animated overlay. 60 coloured squares drop from the top with
   random delays/durations. The container auto-removes after 4.5s so the
   DOM stays clean. */
.confetti-container {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 1100;
    overflow: hidden;
}
.confetti-piece {
    position: absolute;
    top: -10px;
    width: 8px;
    height: 14px;
    border-radius: 1.5px;
    animation: confetti-fall 3s ease-in forwards;
}
@keyframes confetti-fall {
    0%   { transform: translateY(0) rotate(0deg); opacity: 1; }
    100% { transform: translateY(110vh) rotate(720deg); opacity: 0; }
}

/* ── Empty states ──
   Centred icon + title + helper text + optional CTA. The dim color keeps
   the panel feeling "empty" rather than "broken" when there's just no
   data yet. The button uses the same .btn classes as the rest of the page. */
.empty-state {
    text-align: center;
    padding: 1.5rem 1rem;
    color: var(--ph-text-dim);
}
.empty-state-icon {
    font-size: 2.2rem;
    margin-bottom: .5rem;
    opacity: .75;
    line-height: 1;
}
.empty-state-title {
    font-weight: 600;
    color: var(--ph-text);
    margin-bottom: .25rem;
    font-size: .92rem;
}
.empty-state-text {
    font-size: .85rem;
    margin: 0 auto 1rem;
    max-width: 32ch;
    line-height: 1.45;
}
.empty-state .btn { margin-top: .25rem; }

/* ── Achievements panel (Dashboard) ──
   Game-style progression: three LADDERS (Sales, Profit, Galleries),
   each a vertical chain of tiers. The "active" tier — the first one
   the photographer hasn't yet earned — is highlighted as the current
   target with a NEXT badge and a coloured progress bar. Earned tiers
   collapse to a compact "✓ Unlocked" line so the active target stays
   the visual focal point. Total XP across all tiers drives the
   photographer's overall LEVEL (Rookie → Legend) shown in the header.
   The dual ladder (per-category tier + global level) is what makes
   it feel like a game: short-term per-tier goals + long-term career
   progression. */
.achievements-panel { padding: .85rem 1rem 1rem; }
.achievements-heading {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: .85rem;
}
.achievements-heading-main { display: flex; align-items: baseline; gap: .65rem; min-width: 0; flex: 1; }
.achievements-heading-main h3 { margin: 0; font-size: 1rem; display: flex; align-items: center; }
.achievements-level {
    font-size: .78rem;
    color: var(--ph-accent, #4f46e5);
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    gap: .25rem;
    white-space: nowrap;
}
.achievements-level-icon { font-size: .9rem; }
.achievements-xp-wrap {
    flex: 1;
    min-width: 180px;
    max-width: 280px;
    display: flex;
    flex-direction: column;
    gap: .25rem;
}
.achievements-xp-text {
    font-size: .7rem;
    color: var(--ph-text-dim);
    text-align: right;
    font-variant-numeric: tabular-nums;
}
.achievements-xp-track {
    height: 6px;
    background: var(--ph-bg);
    border-radius: 999px;
    overflow: hidden;
    border: 1px solid var(--ph-border, rgba(0,0,0,.06));
}
.achievements-xp-bar {
    height: 100%;
    background: linear-gradient(90deg, #4f46e5, #7c3aed);
    border-radius: 999px;
    transition: width .4s cubic-bezier(.22,.61,.36,1);
}
.achievements-count {
    font-size: .76rem;
    font-weight: 600;
    color: var(--ph-text-dim);
    background: var(--ph-bg);
    border-radius: 999px;
    padding: .1rem .55rem;
    margin-left: .5rem;
}
.achievements-ladders {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: .75rem;
}
.ladder {
    background: var(--ph-bg);
    border: 1px solid var(--ph-border);
    border-radius: var(--ph-radius-sm);
    padding: .65rem .75rem .85rem;
    display: flex;
    flex-direction: column;
    gap: .45rem;
}
.ladder-header {
    display: flex;
    align-items: center;
    gap: .45rem;
    padding-bottom: .35rem;
    border-bottom: 1px solid var(--ph-border);
}
.ladder-icon { font-size: 1.05rem; }
.ladder-name {
    font-weight: 700;
    font-size: .82rem;
    color: var(--ph-text);
    flex: 1;
}
.ladder-progress-count {
    font-size: .68rem;
    font-weight: 600;
    color: var(--ph-text-dim);
    background: var(--ph-surface, #fff);
    border: 1px solid var(--ph-border);
    border-radius: 999px;
    padding: .08rem .5rem;
    font-variant-numeric: tabular-nums;
}
.ladder-tiers {
    display: flex;
    flex-direction: column;
    gap: .35rem;
}
.tier {
    display: grid;
    grid-template-columns: 22px 28px 1fr;
    align-items: center;
    gap: .5rem;
    padding: .4rem .5rem;
    border-radius: 6px;
    border: 1px solid transparent;
    background: var(--ph-surface, #fff);
    transition: background .15s ease, border-color .15s ease;
    position: relative;
}
.tier.earned {
    background: rgba(22, 163, 74, .06);
    border-color: rgba(22, 163, 74, .18);
    opacity: .85;
}
.tier.earned .tier-num { color: #15803d; }
.tier.earned .tier-icon { filter: none; }
.tier.active {
    background: linear-gradient(180deg, rgba(254, 243, 199, .45), rgba(255, 255, 255, 0));
    border-color: rgba(245, 158, 11, .5);
    box-shadow: 0 0 0 2px rgba(245, 158, 11, .12);
}
.tier.active .tier-icon { filter: none; }
.tier-num {
    font-size: .7rem;
    font-weight: 700;
    color: var(--ph-text-dim);
    text-align: center;
    font-variant-numeric: tabular-nums;
}
.tier-icon {
    font-size: 1.05rem;
    line-height: 1;
    text-align: center;
    /* Locked future tiers (not active, not earned) are dimmed. */
    filter: grayscale(1) opacity(.35);
}
.tier-body { min-width: 0; }
.tier-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: .35rem;
}
.tier-name {
    font-size: .78rem;
    font-weight: 600;
    color: var(--ph-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.tier.active .tier-name { color: var(--ph-accent, #4f46e5); }
.tier-progress {
    font-size: .68rem;
    color: var(--ph-text-dim);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    flex-shrink: 0;
}
.tier-progress.is-done { color: #15803d; font-weight: 600; }
.tier-bar {
    margin-top: .25rem;
    width: 100%;
    height: 4px;
    background: rgba(0,0,0,.06);
    border-radius: 999px;
    overflow: hidden;
}
.tier.earned .tier-bar { display: none; }
.tier-bar-fill {
    height: 100%;
    background: linear-gradient(90deg, #f59e0b, #fb923c);
    border-radius: 999px;
    transition: width .4s cubic-bezier(.22,.61,.36,1);
}
.tier-next-badge {
    position: absolute;
    top: -.35rem;
    right: -.35rem;
    background: #f59e0b;
    color: #fff;
    font-size: .58rem;
    font-weight: 700;
    letter-spacing: .04em;
    padding: .12rem .42rem;
    border-radius: 999px;
    box-shadow: 0 1px 3px rgba(0,0,0,.15);
}

/* ── Payout request panel ──
   Distinct from regular history: shows a pending request prominently (if
   any) with cancel option, otherwise shows a form to request a new one.
   The amount input enforces a max via JS to prevent over-requesting. */
.payout-request-form {
    display: flex;
    flex-direction: column;
    gap: .65rem;
}
.payout-request-row {
    display: flex;
    gap: .5rem;
    align-items: center;
    flex-wrap: wrap;
}
.payout-request-row input[type="number"] {
    flex: 1;
    min-width: 120px;
    max-width: 200px;
    padding: .55rem .7rem;
    border: 1px solid var(--ph-border);
    border-radius: var(--ph-radius-sm);
    background: var(--ph-surface);
    color: var(--ph-text);
    font-size: .9rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.payout-request-row input[type="number"]:focus { outline: none; border-color: var(--ph-accent); }
.payout-request-row input[type="text"] {
    flex: 2;
    min-width: 200px;
    padding: .55rem .7rem;
    border: 1px solid var(--ph-border);
    border-radius: var(--ph-radius-sm);
    background: var(--ph-surface);
    color: var(--ph-text);
    font-size: .85rem;
}
.payout-request-row input[type="text"]:focus { outline: none; border-color: var(--ph-accent); }
.payout-request-hint {
    font-size: .76rem;
    color: var(--ph-text-dim);
}
.payout-pending {
    display: flex;
    align-items: center;
    gap: .85rem;
    padding: .7rem .85rem;
    background: linear-gradient(135deg, #fffbeb 0%, var(--ph-surface) 100%);
    border: 1px solid #f59e0b;
    border-radius: var(--ph-radius-sm);
    flex-wrap: wrap;
}
.payout-pending-info { flex: 1; min-width: 0; }
.payout-pending-amount {
    font-size: 1.2rem;
    font-weight: 700;
    color: #b45309;
    font-variant-numeric: tabular-nums;
}
.payout-pending-meta {
    font-size: .78rem;
    color: var(--ph-text-dim);
    margin-top: .15rem;
}
.payout-pending-badge {
    background: #f59e0b;
    color: #fff;
    font-size: .7rem;
    font-weight: 700;
    padding: .15rem .55rem;
    border-radius: 999px;
    text-transform: uppercase;
    letter-spacing: .04em;
}
.payout-row-pending { opacity: .85; }
.payout-row-pending .payout-row-amount { color: #b45309; }

/* ── Top photos (Most Popular Photos panel) ──
   Square thumbnail grid with view count + album caption below. The first
   photo (highest view count) gets a gold border + "MVP" badge so the eye
   lands on the photographer's top performer first — the one worth
   featuring on social/portfolio. */
.top-photos-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
    gap: .75rem;
}
.top-photo {
    position: relative;
    text-align: center;
}
.top-photo-img {
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: 8px;
    border: 1px solid var(--ph-border);
    display: block;
}
.top-photo-meta {
    margin-top: .3rem;
    font-size: .78rem;
    color: var(--ph-text-dim);
}
.top-photo-album {
    font-size: .72rem;
    color: var(--ph-text-dim);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.top-photo-mvp .top-photo-img {
    border-color: #f59e0b;
    box-shadow: 0 0 0 2px rgba(245, 158, 11, .15);
}
.top-photo-badge {
    position: absolute;
    top: 4px;
    left: 4px;
    background: linear-gradient(135deg, #f59e0b, #d97706);
    color: #fff;
    font-size: .68rem;
    font-weight: 700;
    padding: .15rem .45rem;
    border-radius: 999px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .15);
    z-index: 1;
}
.top-photo-share {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 26px;
    height: 26px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, .95);
    border: 1px solid var(--ph-border);
    border-radius: 50%;
    font-size: .85rem;
    cursor: pointer;
    opacity: 0;
    transform: scale(.9);
    transition: opacity .15s ease, transform .15s ease, background-color .12s ease;
    z-index: 1;
}
.top-photo:hover .top-photo-share { opacity: 1; transform: scale(1); }
.top-photo-share:hover { background: var(--ph-accent); color: #fff; border-color: var(--ph-accent); }

/* ── Activity feed (Dashboard) ──
   Compact single-line entries: icon on the left, title + meta stacked on the
   right. The relative time ("2h ago") is right-aligned in muted color so the
   eye can scan newest → oldest quickly. */
.activity-item {
    display: flex;
    align-items: flex-start;
    gap: .7rem;
    padding: .55rem 0;
    border-top: 1px solid var(--ph-border);
    font-size: .85rem;
}
.activity-item:first-child { border-top: none; }
.activity-icon {
    flex-shrink: 0;
    width: 1.7rem;
    height: 1.7rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--ph-accent-soft);
    border-radius: 50%;
    font-size: .9rem;
    line-height: 1;
}
.activity-body {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: .15rem;
}
.activity-title {
    color: var(--ph-text);
    /* Truncate long album titles so the time column stays on the right edge. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.activity-meta {
    display: flex;
    gap: .5rem;
    color: var(--ph-text-dim);
    font-size: .78rem;
}
.activity-time {
    margin-left: auto;
    color: var(--ph-text-mute);
    font-size: .76rem;
    white-space: nowrap;
    flex-shrink: 0;
}

/* ── Gallery cards (My Galleries tab) ──
   Card grid layout: 2x2 thumbnail strip on top, body + action below. The
   #gallery-list container itself is a CSS grid that auto-fills with
   minmax(280px, 1fr) columns so 2-3 cards per row at typical widths.
   Padding/border on the card itself replaces the old top-divider pattern
   that only worked for a single-column list. */
#gallery-list {
    display: grid;
    /* 4 cards per row at full ~1600px width, scaling down to 3/2/1 as the
       viewport narrows. minmax(360px) keeps each card wide enough for the
       2x2 thumbnail grid + readable body text. */
    grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
    gap: 1rem;
}
.gallery-card {
    display: flex;
    flex-direction: column;
    background: var(--ph-surface);
    border: 1px solid var(--ph-border);
    border-radius: var(--ph-radius);
    overflow: hidden;
    transition: border-color .12s ease, box-shadow .12s ease;
}
.gallery-card:hover {
    border-color: var(--ph-accent);
    box-shadow: 0 1px 4px rgba(79, 70, 229, .08);
}
.gallery-card-thumbs {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    grid-template-rows: repeat(2, 1fr);
    gap: 2px;
    aspect-ratio: 2 / 1;
    background: var(--ph-bg);
}
.gallery-card-thumb {
    width: 100%;
    height: 100%;
    object-fit: cover;
    background: #eef0f3;
    /* lazy so 20+ galleries don't all hammer the API on first paint */
    loading: lazy;
}
.gallery-card-thumb-empty {
    background: #eef0f3;
    /* Subtle pattern to read as "no photo yet" rather than a broken image. */
    background-image: linear-gradient(45deg, #e8eaee 25%, transparent 25%, transparent 75%, #e8eaee 75%),
                      linear-gradient(45deg, #e8eaee 25%, transparent 25%, transparent 75%, #e8eaee 75%);
    background-size: 12px 12px;
    background-position: 0 0, 6px 6px;
}
.gallery-card-body {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: .25rem;
    padding: .7rem .8rem .5rem;
}
.gallery-card-title {
    font-weight: 600;
    font-size: .92rem;
    color: var(--ph-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.gallery-card-meta {
    font-size: .78rem;
    color: var(--ph-text-dim);
}
.gallery-card-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding: 0 .8rem .7rem;
}

/* ── Custom cover image ──
   When the photographer has set a custom cover, replace the 2x2 grid with
   a single full-width image + an edit button that appears on hover. When
   no cover is set, show the grid PLUS a "+ Set cover" button overlaid so
   the photographer can opt in to picking one. */
.gallery-card-cover {
    position: relative;
    aspect-ratio: 2 / 1;
    background: var(--ph-bg);
    overflow: hidden;
}
.gallery-card-cover-empty {
    /* Photographer doesn't manage covers; if admin hasn't set one yet,
       the card area is a blank muted surface with a small hint so the
       photographer knows it's waiting for admin. View → still works
       and the public page will show whatever default the platform
       uses for an album without a cover. */
    background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    gap: .25rem;
    color: var(--ph-text-mute);
}
.gallery-card-cover-empty::before {
    content: "📷";
    font-size: 1.4rem;
    opacity: .35;
}
.gallery-card-cover-empty::after {
    content: "Cover not set yet";
    font-size: .68rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    opacity: .55;
}
.gallery-card-cover-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.gallery-card-cover-edit {
    position: absolute;
    top: 6px;
    right: 6px;
    background: rgba(255, 255, 255, .95);
    border: 1px solid var(--ph-border);
    border-radius: 50%;
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: .8rem;
    color: var(--ph-text);
    cursor: pointer;
    opacity: 0;
    transition: opacity .15s ease;
}
.gallery-card-cover:hover .gallery-card-cover-edit { opacity: 1; }
.gallery-card-cover-edit:hover { background: var(--ph-accent); color: #fff; border-color: var(--ph-accent); }
.gallery-card-thumbs-edit {
    position: absolute;
    bottom: 6px;
    right: 6px;
    background: rgba(255, 255, 255, .95);
    border: 1px solid var(--ph-border);
    border-radius: var(--ph-radius-sm);
    padding: .25rem .55rem;
    font-size: .72rem;
    font-weight: 600;
    color: var(--ph-text);
    cursor: pointer;
    z-index: 1;
    opacity: 0.85;
    transition: opacity .15s ease;
}
.gallery-card-thumbs:hover .gallery-card-thumbs-edit { opacity: 1; }
.gallery-card-thumbs-edit:hover { background: var(--ph-accent); color: #fff; border-color: var(--ph-accent); }

/* ── Cover picker modal ──
   Grid of selectable photos for the gallery cover. Same overlay machinery
   as the order modal but simpler: just a grid + a "Clear cover" button +
   the gallery title for context. */
.cover-picker-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    gap: .55rem;
}
.cover-picker-item {
    position: relative;
    aspect-ratio: 1;
    background: var(--ph-bg);
    border: 2px solid var(--ph-border);
    border-radius: var(--ph-radius-sm);
    overflow: hidden;
    cursor: pointer;
    transition: border-color .12s ease, transform .12s ease;
}
.cover-picker-item:hover {
    border-color: var(--ph-accent);
    transform: translateY(-1px);
}
.cover-picker-item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.cover-picker-item.selected {
    border-color: #16a34a;
    box-shadow: 0 0 0 3px rgba(22, 163, 74, .2);
}
.cover-picker-empty {
    text-align: center;
    padding: 1.5rem 1rem;
    color: var(--ph-text-dim);
}
@media (max-width: 768px) {
    /* On narrow screens drop back to one column and tighten gutters. */
    #gallery-list { grid-template-columns: 1fr; gap: .75rem; }
}

/* ── Data table (Orders, dashboard By Gallery) ──
   Shared styling for all `<table class="data-table">` instances so column
   headers / cells / numeric alignment stay consistent across tabs.
   Numeric columns use tabular-nums so digits line up across rows. Density
   is tight: photographer wants to see as many rows as possible per page
   without scrolling — table is the working surface, not a marketing page. */
.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: .78rem;
    line-height: 1.35;
}
.data-table thead th {
    text-align: left;
    font-weight: 600;
    font-size: .68rem;
    color: var(--ph-text-dim);
    text-transform: uppercase;
    letter-spacing: .04em;
    padding: .4rem .55rem;
    border-bottom: 1px solid var(--ph-border);
    background: var(--ph-bg);
    white-space: nowrap;
}
.data-table tbody td {
    padding: .35rem .55rem;
    border-top: 1px solid var(--ph-border);
    vertical-align: middle;
}
.data-table tbody tr:first-child td { border-top: none; }
.data-table tbody tr:hover { background: rgba(79, 70, 229, .03); }
.data-table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Red text for any money the platform is taking out of the photographer's
   gross (gateway fees + ohmaishoot commission). The photographer sees
   these as costs; green = what they actually keep. */
.data-table .num-deduct { color: #dc2626; }
.data-table .num-profit { color: #15803d; font-weight: 700; }
/* The profit column gets a subtle background + accent border so the eye
   lands on the bottom-line number first, which is the only figure the
   photographer actually gets paid. */
.data-table .col-profit {
    background: rgba(22, 163, 74, 0.05);
    box-shadow: inset 2px 0 0 #16a34a;
}
.data-table thead .col-profit {
    background: rgba(22, 163, 74, 0.08);
}
.data-table .col-title {
    max-width: 280px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.data-table .col-date {
    color: var(--ph-text-dim);
    white-space: nowrap;
    font-size: .74rem;
}
.data-table-wrap {
    /* Horizontal scroll on narrow screens so the table never gets squished
       into unreadable widths — photographer can swipe sideways. */
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

/* Toolbar above the orders table — small inline select groups that don't
   steal vertical space but stay accessible. The label is the visible
   caption ("Sort" / "Gallery"), the native <select> handles the dropdown. */
.orders-toolbar {
    display: flex;
    align-items: center;
    gap: .8rem;
    flex-wrap: wrap;
}
.orders-toolbar-field {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    font-size: .78rem;
    color: var(--ph-text-dim);
    font-weight: 600;
}
.orders-toolbar-field select {
    padding: .3rem .55rem;
    border: 1px solid var(--ph-border);
    border-radius: var(--ph-radius-sm);
    background: var(--ph-surface);
    color: var(--ph-text);
    font-size: .78rem;
    font-weight: 500;
    cursor: pointer;
}
.orders-toolbar-field select:focus { outline: none; border-color: var(--ph-accent); }

/* ── Responsive (mobile) ──
   Sidebar flips to a horizontal top bar, forms stack, headers stack.
   Tables already sit inside overflow-x:auto wrappers. */
@media (max-width: 768px) {
    #photographer-app { flex-direction: column; }
    .sidebar {
        width: 100%;
        flex-direction: row;
        align-items: center;
        gap: .5rem;
        padding: .6rem .9rem;
        border-right: none;
        border-bottom: 1px solid var(--ph-border);
    }
    .sidebar-brand { padding: 0; border-bottom: none; margin-bottom: 0; margin-right: auto; }
    .sidebar-brand h1 { font-size: .95rem; }
    .sidebar-nav { flex-direction: row; overflow-x: auto; padding: 0; gap: .25rem; -webkit-overflow-scrolling: touch; }
    .nav-item { white-space: nowrap; width: auto; padding: .4rem .6rem; font-size: .8rem; }
    .main-content { padding: 1rem 1rem 2rem; }
    .page-header { flex-direction: column; align-items: stretch; gap: .75rem; }
    .page-header > div:last-child { display: flex; flex-direction: column; }
    .page-header .btn { justify-content: center; }
    .form-row { grid-template-columns: 1fr; }
    .stats-grid { grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: .5rem; }
    .admin-record-card { flex-wrap: wrap; }
    /* Too narrow for one line: the money chain drops under the title rather
       than pushing the row into a horizontal scroll. */
    .order-row-main { width: 100%; }
    .order-row-figures { width: 100%; gap: .6rem; font-size: .8rem; }
    .premium-panel { padding: .9rem 1rem; }
    .data-table { font-size: .82rem; }
    .data-table thead th, .data-table tbody td { padding: .5rem .55rem; }
}

/* ── Orders pagination strip ──────────────────────────────────────────
   Sits below the Recent Orders table. Two zones: count summary on the
   left, prev/next + page-of on the right. The summary uses muted color
   so the buttons are the visual anchor. Disabled buttons keep their
   layout slot (greyed, no pointer) instead of collapsing — the strip
   shouldn't shift on page 1 vs page 5. */
.orders-pagination {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: .75rem 1rem;
    margin-top: .5rem;
    border-top: 1px solid var(--border-soft, rgba(0,0,0,.08));
    flex-wrap: wrap;
    gap: .5rem;
}
.orders-pagination[hidden] { display: none; }
.orders-pagination-info {
    color: var(--text-dim, #6b7280);
    font-size: .85rem;
}
.orders-pagination-info strong { color: var(--text, #111); }
.orders-pagination-controls {
    display: flex;
    align-items: center;
    gap: .5rem;
}
.orders-pagination-page {
    color: var(--text-dim, #6b7280);
    font-size: .85rem;
    padding: 0 .25rem;
    min-width: 80px;
    text-align: center;
}
.btn-sm {
    padding: .35rem .65rem;
    font-size: .82rem;
}
.btn-secondary[disabled],
.btn-secondary:disabled {
    opacity: .45;
    cursor: not-allowed;
}

/* ── Live indicator (sidebar) ──
   Small "● Live" pill under the photographer name. The dot is solid
   green when auto-refresh is active and pulses briefly on each refresh
   so the photographer has a visible "yes, data is fresh" signal
   without a noisy countdown. Hidden on login screen via the parent
   sidebar being display:none. */
.live-indicator {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    margin-top: .35rem;
    padding: .15rem .55rem;
    border-radius: 999px;
    background: rgba(22, 163, 74, .08);
    font-size: .68rem;
    font-weight: 600;
    color: #15803d;
    letter-spacing: .03em;
    text-transform: uppercase;
}
.live-indicator-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #16a34a;
    box-shadow: 0 0 0 0 rgba(22, 163, 74, .6);
}
@keyframes live-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(22, 163, 74, .55); }
    70%  { box-shadow: 0 0 0 6px rgba(22, 163, 74, 0); }
    100% { box-shadow: 0 0 0 0 rgba(22, 163, 74, 0); }
}
.live-indicator-dot.live-pulse { animation: live-pulse .8s ease-out; }

/* ── User avatar + dropdown menu (sidebar) ──
   The avatar is a circular 40px tile. When a profile photo is set it's
   the photo cropped to a circle; otherwise the first letter of the
   photographer's name in a brand-coloured circle (deterministic colour
   from name so it stays stable across logins). Click toggles a dropdown
   menu (Settings + Log Out).

   The "Ohmaishoot" brand is intentionally a separate row above the user
   button — the user button shows just the photographer's identity (avatar
   + name + live indicator). Mixing the brand into the user button was
   visually busy and crowded the dropdown trigger. */
.sidebar-user {
    position: relative; /* anchors the absolute-positioned dropdown */
    padding: 0 .6rem;
    margin-top: -.25rem;
}
.sidebar-user-button {
    display: flex;
    align-items: center;
    gap: .65rem;
    width: 100%;
    padding: .45rem .55rem;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 10px;
    cursor: pointer;
    text-align: left;
    font: inherit;
    color: inherit;
    transition: background .15s ease, border-color .15s ease;
    user-select: none;
}
.sidebar-user-button:hover { background: rgba(79, 70, 229, .06); }
.sidebar-user-button[aria-expanded="true"] {
    background: rgba(79, 70, 229, .08);
    border-color: rgba(79, 70, 229, .15);
}
.sidebar-user-button:focus-visible {
    outline: 2px solid var(--ph-accent, #4f46e5);
    outline-offset: 2px;
}
.sidebar-user-meta {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1;
    line-height: 1.2;
}
.sidebar-user-meta #photographer-name {
    font-size: .88rem;
    font-weight: 600;
    color: var(--ph-text, #111);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.sidebar-user-chevron {
    color: var(--ph-text-dim, #9ca3af);
    font-size: .75rem;
    line-height: 1;
    transition: transform .15s ease;
    flex-shrink: 0;
}
.sidebar-user-button[aria-expanded="true"] .sidebar-user-chevron { transform: rotate(180deg); color: var(--ph-accent, #4f46e5); }
.user-avatar {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--ph-accent, #4f46e5);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: .9rem;
    overflow: hidden;
    flex-shrink: 0;
    /* Faint ring so the circle reads against the sidebar background. */
    box-shadow: 0 0 0 2px rgba(79, 70, 229, .12);
}
.user-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.user-avatar-initials {
    line-height: 1;
    text-transform: uppercase;
}

/* Dropdown menu — absolute-positioned below the user button. Hidden via
   [hidden] until JS toggles it. Sits OUTSIDE the sidebar padding so the
   shadow has room to breathe. The chevron rotates to indicate the open
   state. */
.user-menu {
    position: absolute;
    top: calc(100% + .35rem);
    left: .6rem;
    right: .6rem;
    background: var(--ph-surface, #fff);
    border: 1px solid var(--ph-border, rgba(0,0,0,.08));
    border-radius: 10px;
    box-shadow: 0 10px 30px rgba(15, 23, 42, .15);
    padding: .3rem;
    z-index: 100;
    animation: user-menu-fade .12s ease-out;
}
.user-menu[hidden] { display: none; }
@keyframes user-menu-fade {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}
.user-menu-item {
    display: flex;
    align-items: center;
    gap: .65rem;
    width: 100%;
    padding: .55rem .75rem;
    background: transparent;
    border: none;
    border-radius: 6px;
    color: var(--ph-text, #111);
    font: inherit;
    font-size: .85rem;
    text-align: left;
    cursor: pointer;
    transition: background .12s ease;
}
.user-menu-item:hover { background: rgba(79, 70, 229, .08); }
.user-menu-item:focus-visible { outline: 2px solid var(--ph-accent, #4f46e5); outline-offset: -2px; }
.user-menu-item-danger { color: #dc2626; }
.user-menu-item-danger:hover { background: rgba(220, 38, 38, .08); }
.user-menu-icon {
    width: 18px;
    text-align: center;
    font-size: 1rem;
    line-height: 1;
    flex-shrink: 0;
}
.user-menu-divider {
    height: 1px;
    margin: .25rem .35rem;
    background: var(--ph-border, rgba(0,0,0,.08));
}

/* ── Settings modal ──
   Full-screen backdrop + centred card. Reuses the same backdrop fade
   animation as the order-modal so they feel like the same component
   family. */
.settings-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, .55);
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}
.settings-modal-backdrop[hidden] { display: none; }
.settings-modal {
    background: var(--ph-surface, #fff);
    border-radius: 14px;
    width: 100%;
    max-width: 480px;
    box-shadow: 0 20px 60px rgba(0, 0, 0, .25);
    overflow: hidden;
    animation: backdrop-fade-in .15s ease-out;
}
.settings-modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--ph-border, rgba(0,0,0,.08));
}
.settings-modal-header h2 {
    margin: 0;
    font-size: 1.1rem;
}
.settings-modal-close {
    background: transparent;
    border: none;
    color: var(--ph-text-dim, #6b7280);
    font-size: 1.6rem;
    line-height: 1;
    cursor: pointer;
    padding: 0 .25rem;
    border-radius: 4px;
}
.settings-modal-close:hover { color: var(--ph-text, #111); background: rgba(0,0,0,.04); }
.settings-modal-body { padding: 1.25rem; }
.settings-modal-footer {
    display: flex;
    justify-content: flex-end;
    gap: .5rem;
    padding: .85rem 1.25rem;
    border-top: 1px solid var(--ph-border, rgba(0,0,0,.08));
    background: rgba(0,0,0,.02);
}
.settings-avatar-row {
    display: flex;
    gap: 1.25rem;
    align-items: center;
    margin-bottom: 1.25rem;
    padding-bottom: 1.25rem;
    border-bottom: 1px solid var(--ph-border, rgba(0,0,0,.05));
}
.settings-avatar-preview {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    background: var(--ph-accent, #4f46e5);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 1.7rem;
    overflow: hidden;
    flex-shrink: 0;
    text-transform: uppercase;
}
.settings-avatar-preview img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.settings-avatar-actions {
    display: flex;
    flex-direction: column;
    gap: .35rem;
    flex: 1;
}
.settings-avatar-hint {
    margin: .15rem 0 0;
    font-size: .72rem;
    color: var(--ph-text-dim, #6b7280);
}
.settings-field { margin-bottom: 1rem; }
.settings-field label {
    display: block;
    font-size: .78rem;
    font-weight: 600;
    color: var(--ph-text-dim, #6b7280);
    text-transform: uppercase;
    letter-spacing: .04em;
    margin-bottom: .35rem;
}
.settings-field input {
    width: 100%;
    padding: .55rem .7rem;
    border: 1px solid var(--ph-border, rgba(0,0,0,.12));
    border-radius: 8px;
    font: inherit;
    font-size: .9rem;
    background: var(--ph-bg, #fff);
    color: var(--ph-text, #111);
    box-sizing: border-box;
}
.settings-field input:disabled {
    background: rgba(0,0,0,.04);
    color: var(--ph-text-dim, #6b7280);
    cursor: not-allowed;
}
.settings-field input:focus {
    outline: 2px solid var(--ph-accent, #4f46e5);
    outline-offset: -1px;
    border-color: transparent;
}
.settings-error {
    color: #dc2626;
    font-size: .85rem;
    margin: .5rem 0 0;
}

/* ── Achievements timeline (by month) ──
   Compact list of every earned tier, sorted chronologically and grouped
   under a "September 2026" / "October 2026" header. The "by month" view
   the photographer asked for — they want to see at a glance when each
   milestone landed, without having to click into each tier.

   Sits between the header and the ladders so it's the first thing the
   eye lands on after the level bar. Hidden when empty (no unlocks
   yet) so the panel doesn't start with a "you have nothing" block. */
.achievements-timeline {
    background: var(--ph-bg);
    border: 1px solid var(--ph-border);
    border-radius: var(--ph-radius-sm);
    padding: .55rem .75rem .75rem;
    margin-bottom: .75rem;
    display: flex;
    flex-direction: column;
    gap: .55rem;
}
.achievements-timeline[hidden] { display: none; }
.achievements-timeline-header {
    display: flex;
    align-items: center;
    gap: .35rem;
    font-size: .76rem;
    font-weight: 700;
    color: var(--ph-text-dim);
    text-transform: uppercase;
    letter-spacing: .05em;
}
.achievements-timeline-icon { font-size: .9rem; }
.achievements-timeline-month {
    display: grid;
    grid-template-columns: 95px 1fr;
    gap: .6rem;
    align-items: start;
}
.achievements-timeline-month-label {
    font-size: .78rem;
    font-weight: 700;
    color: var(--ph-text);
    padding-top: .15rem;
}
.achievements-timeline-items {
    display: flex;
    flex-direction: column;
    gap: .25rem;
}
.achievements-timeline-item {
    display: flex;
    align-items: center;
    gap: .55rem;
    font-size: .76rem;
    color: var(--ph-text);
    padding: .2rem 0;
    border-bottom: 1px dashed var(--ph-border, rgba(0,0,0,.05));
}
.achievements-timeline-item:last-child { border-bottom: none; }
.achievements-timeline-tier {
    font-weight: 600;
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.achievements-timeline-ladder {
    font-size: .68rem;
    color: var(--ph-text-dim);
    background: var(--ph-surface, #fff);
    border: 1px solid var(--ph-border);
    border-radius: 999px;
    padding: .08rem .5rem;
    flex-shrink: 0;
}
.achievements-timeline-date {
    font-size: .72rem;
    color: var(--ph-text-dim);
    font-variant-numeric: tabular-nums;
    flex-shrink: 0;
    min-width: 60px;
    text-align: right;
}

/* ── Upload tab ── */
.upload-zone {
    border: 2px dashed var(--ph-border);
    border-radius: 12px;
    padding: 2rem 1rem;
    text-align: center;
    background: var(--ph-surface);
    color: var(--ph-text-dim);
    cursor: pointer;
    transition: all .2s;
}
.upload-zone.is-hover,
.upload-zone:hover {
    border-color: var(--ph-accent, #6366f1);
    background: rgba(99, 102, 241, .08);
    color: var(--ph-text);
}
.upload-zone-icon { font-size: 2.5rem; margin-bottom: .5rem; }
.upload-album-card {
    display: flex; align-items: center; gap: .65rem;
    padding: .75rem 1rem; margin-bottom: .5rem;
    background: var(--ph-surface);
    border: 1px solid var(--ph-border);
    border-radius: 10px; cursor: pointer;
    transition: border-color .15s;
}
.upload-album-card:hover { border-color: var(--ph-accent, #6366f1); }
.upload-album-card.selected { border-color: var(--ph-accent, #6366f1); background: rgba(99, 102, 241, .08); }
.upload-album-card-name { font-weight: 600; flex: 1; min-width: 0; }
.upload-album-card-meta { font-size: .78rem; color: var(--ph-text-dim); }
.upload-queue-item {
    display: flex; align-items: center; gap: .65rem;
    padding: .55rem .75rem; background: var(--ph-surface);
    border: 1px solid var(--ph-border); border-radius: 8px;
    font-size: .88rem;
}
.upload-queue-item-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.upload-queue-progress {
    width: 100px; height: 6px; border-radius: 3px;
    background: rgba(99, 102, 241, .15); overflow: hidden;
}
.upload-queue-progress-fill {
    height: 100%; background: var(--ph-accent, #6366f1);
    transition: width .25s ease;
}
.upload-status-ok { color: #16a34a; }
.upload-status-fail { color: #dc2626; }
.upload-status-pending { color: var(--ph-text-dim); }
