@import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600&family=Inter:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&family=Playfair+Display:wght@400;500;600;700&display=swap');

:root {
  color-scheme: dark;
  --bg: #0d1a20;
  --surface: #13262e;
  --surface-raised: #18313a;
  --hairline: #2a4750;
  --ink: #edf6f5;
  --ink-dim: #9bb7bd;
  --ink-faint: #728f96;
  --brass: #4fa8bd;
  --brass-bright: #7ecfe0;
  /* Text/icon color for anything sitting ON a --brass background. It has
     to flip with the theme: in dark mode --brass is a light teal, so near
     black reads well on it; in light mode --brass is a much darker teal
     (and --brass-bright is darker still, for hover), where near-black on
     it fell to roughly 3.6:1 and about 2.5:1 on hover — legible only if
     you already knew what it said. Every brand-theme preset overrides
     --brass but not this, so they all pick up the correct pairing. */
  --on-brass: #08191d;
  --teal: #1c5a63;
  --danger: #c96a4f;
  /* Readable directly on --bg/--surface — separate from --danger/--teal
     (which are tuned for badges/borders/buttons) because message text
     needs its own contrast-checked value per theme, not just an accent
     color reused for a different job. */
  --error-text: #e8a893;
  --success-text: #9fd9d4;
  --teal-text: #4a969a;
  --topbar-bg: rgba(28, 26, 23, 0.7);

  --font-display: 'Fraunces', Georgia, serif;
  --font-body: 'Inter', -apple-system, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, monospace;
}

/* Same ocean/mountain palette, inverted for a light background — every
   color in the app is driven by these variables, so this one block is
   what light mode actually is. */
[data-theme="light"] {
  color-scheme: light;
  --bg: #f2f7f8;
  --surface: #ffffff;
  --surface-raised: #ffffff;
  --hairline: #d7e3e6;
  --ink: #12242a;
  --ink-dim: #4c6169;
  --ink-faint: #5d7177;
  --brass: #1f7a91;
  --brass-bright: #145e70;
  --on-brass: #ffffff;
  --teal: #2f7d74;
  --danger: #b6472e;
  --error-text: #a63f22;
  --success-text: #1d6b60;
  --teal-text: #2f7d74;
  --topbar-bg: rgba(255, 255, 255, 0.8);
}

/* Optional per-admin brand theme presets — set via data-brand-theme on
   <html> for clients whose admin has picked one. Only the accent colors
   change; text/surface stay the same as the base ocean theme in both
   dark and light mode (so contrast/legibility is never a risk), but the
   page background and topbar now pick up a subtle tint of each theme too
   — not just buttons and badges — so switching themes is visible across
   the whole page, not only on isolated accent elements. Values here need
   to stay in sync with branding.js's THEME_PRESETS. */
[data-brand-theme="sunset"] { --brass: #d98c4f; --brass-bright: #f0ab6e; --teal: #8a4a2e; --bg: #1a140f; --topbar-bg: rgba(38, 28, 20, 0.75); }
[data-theme="light"][data-brand-theme="sunset"] { --brass: #b8672a; --brass-bright: #8f4d1d; --teal: #a85d35; --bg: #f8f2ec; --topbar-bg: rgba(255, 250, 245, 0.82); }

[data-brand-theme="forest"] { --brass: #5fa876; --brass-bright: #82cf9c; --teal: #2e5c3f; --bg: #0d1a14; --topbar-bg: rgba(20, 34, 27, 0.75); }
[data-theme="light"][data-brand-theme="forest"] { --brass: #2f7a4a; --brass-bright: #1f5c37; --teal: #3d7a52; --bg: #eef7f0; --topbar-bg: rgba(247, 253, 249, 0.82); }

[data-brand-theme="berry"] { --brass: #a878c9; --brass-bright: #c9a0e8; --teal: #5c3a70; --bg: #150d1a; --topbar-bg: rgba(30, 20, 36, 0.75); }
[data-theme="light"][data-brand-theme="berry"] { --brass: #7a4a96; --brass-bright: #5c3670; --teal: #6b4080; --bg: #f5eff8; --topbar-bg: rgba(251, 247, 253, 0.82); }

[data-brand-theme="slate"] { --brass: #7a8ca0; --brass-bright: #a3b5c7; --teal: #4a5a6a; --bg: #12161a; --topbar-bg: rgba(26, 31, 36, 0.75); }
[data-theme="light"][data-brand-theme="slate"] { --brass: #4f5f70; --brass-bright: #3a4854; --teal: #556575; --bg: #eff1f3; --topbar-bg: rgba(249, 250, 251, 0.82); }

[data-brand-theme="rose"] { --brass: #c97f8a; --brass-bright: #e8a5ae; --teal: #7a3f47; --bg: #1a0f13; --topbar-bg: rgba(36, 22, 27, 0.75); }
[data-theme="light"][data-brand-theme="rose"] { --brass: #a8515e; --brass-bright: #8a3d48; --teal: #96505c; --bg: #f8eff1; --topbar-bg: rgba(253, 247, 249, 0.82); }

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  min-height: 100%;
}

body {
  background-image:
    radial-gradient(ellipse 80% 50% at 50% -10%, rgba(79, 168, 189, 0.08), transparent),
    radial-gradient(ellipse 60% 40% at 100% 100%, rgba(28, 90, 99, 0.14), transparent);
}

a { color: inherit; }

/* ---------- Touch feel ----------
   Two small things that make a web page read as a web page on a phone.
   The grey box that flashes over anything tappable is a browser default no
   native app has, and long-pressing a button label in an app shouldn't pop
   up a text-selection handle. Selection is only switched off on controls
   and chrome — never on file names, comments or other real content, which
   people do legitimately want to copy. */
a, button, .ghost, .primary, .topbar, .tile-download-btn, .tile-cart-btn,
.tile-favorite-btn, .download-fab, .cart-fab, .modal-close, label, summary {
  -webkit-tap-highlight-color: transparent;
}

button, .ghost, .primary, .topbar, .wordmark, .modal-close,
.landing-nav, label, summary, .viewer-name, .tile-price-badge {
  -webkit-user-select: none;
  user-select: none;
}

/* ---------- Peak mark (signature element — mountain doubling as a lens) ---------- */

.peak-mark {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
}

/* ---------- Alpine ridge backdrop (auth pages only) ---------- */

.mountain-backdrop {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  height: 220px;
  color: var(--brass);
  opacity: 0.16;
  pointer-events: none;
  z-index: 0;
}
.mountain-backdrop .ridge { fill: currentColor; }
.mountain-backdrop .ski-track { fill: none; stroke: currentColor; stroke-width: 2.5; stroke-linecap: round; opacity: 0.8; }
.mountain-backdrop .swell { fill: var(--teal); opacity: 0.55; }

/* ---------- Auth pages ---------- */

.auth-shell {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.auth-card {
  width: 100%;
  max-width: 380px;
  position: relative;
  z-index: 1;
}
.auth-card-wide { max-width: 480px; }

.wordmark {
  display: flex;
  align-items: center;
  gap: 10px;
  justify-content: center;
  margin-bottom: 34px;
  color: var(--ink);
  text-decoration: none;
}

.wordmark .peak-mark { color: var(--brass); width: 22px; height: 22px; }

a.wordmark:hover .peak-mark { color: var(--brass-bright); }
a.wordmark:hover .wordmark-text { color: var(--brass-bright); }
.wordmark-text {
  font-family: var(--font-display);
  font-size: 20px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.wordmark-sub {
  display: block;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  color: var(--ink-faint);
  text-transform: uppercase;
  margin-top: 3px;
}

.card {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 3px;
  padding: 36px 32px;
}

.card h1 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 22px;
  margin: 0 0 6px;
}

.card .lede {
  color: var(--ink-dim);
  font-size: 13.5px;
  margin: 0 0 26px;
  line-height: 1.5;
}

label {
  display: block;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 7px;
}

.field { margin-bottom: 18px; }

input[type="text"],
input[type="email"],
input[type="password"] {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--hairline);
  color: var(--ink);
  padding: 11px 13px;
  border-radius: 2px;
  font-size: 14.5px;
  font-family: var(--font-body);
  transition: border-color 0.15s ease;
}

input[type="text"]:focus,
input[type="email"]:focus,
input[type="password"]:focus {
  outline: none;
  border-color: var(--brass);
}

/* Browsers force their own background color on autofilled fields (can't be
   overridden with background-color directly) — this is the standard trick
   to make it match the theme instead of a jarring light box. */
input[type="text"]:-webkit-autofill,
input[type="email"]:-webkit-autofill,
input[type="password"]:-webkit-autofill,
input[type="text"]:-webkit-autofill:focus,
input[type="email"]:-webkit-autofill:focus,
input[type="password"]:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--ink);
  -webkit-box-shadow: 0 0 0 1000px var(--bg) inset;
  box-shadow: 0 0 0 1000px var(--bg) inset;
  transition: background-color 9999s ease-in-out 0s;
}

input:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.primary {
  width: 100%;
  background: var(--brass);
  color: var(--on-brass);
  border: none;
  padding: 12px;
  border-radius: 2px;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 14px;
  cursor: pointer;
  transition: background 0.15s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  text-decoration: none;
  box-sizing: border-box;
}

.primary:hover { background: var(--brass-bright); }

.btn-success-flash {
  background: #5fa876 !important;
  border-color: #5fa876 !important;
  color: #ffffff !important;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.dashboard-reminder {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  background: rgba(217, 140, 79, 0.1);
  border: 1px solid #d98c4f;
  border-radius: 4px;
  padding: 12px 16px;
  margin-bottom: 14px;
  font-size: 13px;
  color: var(--ink-dim);
}
.dashboard-reminder a { color: var(--brass-bright); font-weight: 600; }
.dashboard-reminder label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--ink-faint);
  cursor: pointer;
  white-space: nowrap;
}

.gift-card-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(79, 168, 189, 0.12);
  border: 1px solid var(--brass);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 11.5px;
  padding: 4px 8px;
  border-radius: 12px;
}
.gift-card-chip button {
  background: none;
  border: none;
  color: var(--ink-faint);
  cursor: pointer;
  padding: 0;
  font-size: 12px;
  line-height: 1;
}
.gift-card-chip button:hover { color: var(--danger); }

/* Per-row "⋯" actions menu — replaces a row of several buttons that
   would otherwise wrap awkwardly (especially with many rows, or in a
   narrower table). Same floating-dropdown pattern as the topbar menus,
   just one instance per row instead of one shared instance. */
.row-actions-wrap { position: relative; display: inline-block; }
.row-actions-btn {
  font-family: var(--font-mono);
  font-size: 12px;
  background: transparent;
  border: 1px solid var(--hairline);
  color: var(--ink-dim);
  border-radius: 3px;
  padding: 5px 10px;
  cursor: pointer;
}
.row-actions-btn:hover { border-color: var(--brass); color: var(--brass-bright); }
.row-actions-dropdown {
  display: none;
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  background: var(--surface-raised);
  border: 1px solid var(--hairline);
  border-radius: 4px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
  min-width: 170px;
  z-index: 40;
  padding: 6px;
}
.row-actions-dropdown.open { display: block; }
.row-actions-dropdown button {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  color: var(--ink);
  font-size: 13px;
  padding: 8px 10px;
  border-radius: 3px;
  cursor: pointer;
}
.row-actions-dropdown button:hover { background: var(--surface); }
.row-actions-dropdown button.danger { color: var(--danger); }
.row-actions-dropdown button:disabled { opacity: 0.4; cursor: not-allowed; }
.row-actions-dropdown button:disabled:hover { background: none; }

/* Search/filter above a table that could realistically hold a lot of
   rows (dozens of clients or admins) — narrows the list instead of
   requiring a long scroll to find one specific row. */
.table-filter-input {
  width: 100%;
  max-width: 320px;
  margin-bottom: 12px;
  background: var(--bg);
  border: 1px solid var(--hairline);
  color: var(--ink);
  border-radius: 3px;
  padding: 9px 12px;
  font-size: 13px;
}
.primary:disabled { opacity: 0.6; cursor: default; }

button.ghost, a.ghost {
  background: transparent;
  border: 1px solid var(--hairline);
  color: var(--ink-dim);
  padding: 9px 16px;
  border-radius: 2px;
  font-family: var(--font-body);
  font-size: 13px;
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
}
button.ghost:hover, a.ghost:hover { border-color: var(--brass); color: var(--ink); }

/* New client comment/favorite the admin hasn't seen yet — cleared by
   actually opening Comments & Favorites, not per-item, since favorites
   have no stable id to track "read" on individually. Plain inline dot
   rather than absolute-positioned so it just flows after the button/menu
   item's own text, regardless of that text's width. */
.notif-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--danger);
  margin-left: 6px;
  vertical-align: middle;
}

.theme-toggle-btn {
  background: transparent;
  border: 1px solid var(--hairline);
  color: var(--ink-dim);
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
}
.theme-toggle-btn:hover { border-color: var(--brass); color: var(--brass-bright); }
.theme-toggle-btn svg { width: 17px; height: 17px; }

.settings-menu-wrap { position: relative; }
.settings-menu-dropdown {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  background: var(--surface-raised);
  border: 1px solid var(--hairline);
  border-radius: 4px;
  min-width: 170px;
  padding: 6px;
  z-index: 20;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
}
.settings-menu-dropdown.open { display: block; }
.settings-menu-dropdown button {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  color: var(--ink-dim);
  font-family: var(--font-body);
  font-size: 13px;
  padding: 9px 10px;
  border-radius: 3px;
  cursor: pointer;
}
.settings-menu-dropdown button:hover { background: rgba(79, 168, 189, 0.1); color: var(--ink); }

.error-msg {
  background: rgba(201, 106, 79, 0.12);
  border: 1px solid rgba(201, 106, 79, 0.4);
  color: var(--error-text);
  font-size: 13px;
  padding: 10px 12px;
  border-radius: 2px;
  margin-bottom: 18px;
  display: none;
}
.error-msg.show { display: block; }

.success-msg {
  background: rgba(28, 90, 99, 0.25);
  border: 1px solid rgba(28, 90, 99, 0.6);
  color: var(--success-text);
  font-size: 13px;
  padding: 10px 12px;
  border-radius: 2px;
  margin-bottom: 18px;
  display: none;
}
.success-msg.show { display: block; }

.notice-msg {
  background: rgba(79, 168, 189, 0.08);
  border: 1px solid rgba(79, 168, 189, 0.35);
  color: var(--ink-dim);
  font-size: 12.5px;
  line-height: 1.5;
  padding: 12px 14px;
  border-radius: 2px;
  margin-bottom: 18px;
}
.notice-msg strong { color: var(--ink); }

.powered-by-footer {
  text-align: center;
  padding: 22px 24px 30px;
  font-size: 11px;
  color: var(--ink-faint);
}
.powered-by-footer a { color: var(--ink-faint); text-decoration: underline; }
.powered-by-footer a:hover { color: var(--brass-bright); }

/* Sticky footer: keeps "Powered by..." pinned to the bottom of the
   viewport on short pages without overlapping content on tall ones —
   scoped to .footer-page so it doesn't touch pages that don't use it. */
body.footer-page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
body.footer-page > .container,
body.footer-page > .auth-shell {
  flex: 1 0 auto;
}
body.footer-page > .auth-shell {
  min-height: 0; /* sizing now comes from the flex parent instead */
}
body.footer-page .powered-by-footer {
  margin-top: auto;
  flex-shrink: 0;
}

.branding-page-preview {
  background: var(--bg);
  border: 1px solid var(--hairline);
  border-radius: 4px;
  overflow: hidden;
}
.branding-preview-topbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--hairline);
}
.branding-preview-logo-wrap {
  width: 24px; height: 24px;
  display: flex; align-items: center; justify-content: center;
  color: var(--brass);
  flex-shrink: 0;
}
.branding-preview-logo-wrap img { max-width: 100%; max-height: 100%; }
#brandingPreviewName { font-size: 16px; color: var(--ink); }

.branding-preview-gallery {
  display: flex;
  justify-content: center;
  padding: 24px 20px;
}
.branding-preview-card { text-align: center; width: 160px; }
.branding-preview-thumb {
  aspect-ratio: 4 / 5;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 2px;
  margin-bottom: 8px;
  position: relative;
}
.branding-preview-badge {
  position: absolute;
  top: 8px; left: 8px;
  background: rgba(20, 19, 17, 0.7);
  border: 1px solid var(--brass);
  color: var(--brass-bright);
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 3px 6px;
  border-radius: 2px;
}
.branding-preview-cart-btn {
  position: absolute;
  bottom: 8px; right: 8px;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--brass);
  border: none;
  color: var(--on-brass);
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  cursor: default;
}
.branding-preview-cart-btn svg { width: 13px; height: 13px; }
.branding-preview-card-title {
  font-family: var(--font-display);
  font-size: 13px;
  color: var(--ink);
  margin-bottom: 10px;
}
.branding-preview-cta {
  width: 100%;
  background: var(--brass);
  color: var(--on-brass);
  border: none;
  padding: 8px;
  border-radius: 2px;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 12px;
  cursor: default;
}

.theme-swatch-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--bg);
  border: 1px solid var(--hairline);
  color: var(--ink-dim);
  padding: 8px 14px;
  border-radius: 20px;
  font-size: 12.5px;
  cursor: pointer;
}
.theme-swatch-btn:hover { border-color: var(--brass); }
.theme-swatch-btn.selected { border-color: var(--brass); color: var(--ink); background: rgba(79, 168, 189, 0.1); }
.theme-swatch-dot {
  width: 14px; height: 14px;
  border-radius: 50%;
  display: inline-block;
  flex-shrink: 0;
}

.link-btn {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: var(--ink);
  font-weight: 600;
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: transparent;
  transition: text-decoration-color 0.15s ease, color 0.15s ease;
}
.link-btn:hover { color: var(--brass-bright); text-decoration-color: currentColor; }

.folder-purchase-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  background: rgba(79, 168, 189, 0.08);
  border: 1px solid var(--brass);
  border-radius: 4px;
  padding: 14px 18px;
  margin-bottom: 18px;
}
.folder-purchase-banner-text { font-size: 13.5px; color: var(--ink-dim); }
.folder-purchase-banner-text strong { color: var(--ink); }
.folder-purchase-banner-price { color: var(--brass-bright); font-family: var(--font-mono); }

.faq-item {
  border-bottom: 1px solid var(--hairline);
  padding: 14px 0;
}
.faq-item:last-child { border-bottom: none; }
.faq-item summary {
  cursor: pointer;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: '+';
  font-family: var(--font-body);
  font-size: 20px;
  color: var(--brass);
  flex-shrink: 0;
  transition: transform 0.15s ease;
}
.faq-item[open] summary::after { transform: rotate(45deg); }
.faq-item p {
  margin: 10px 0 0;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--ink-dim);
}

.foot-link {
  text-align: center;
  margin-top: 22px;
  font-size: 12.5px;
  color: var(--ink-faint);
}
.foot-link a { color: var(--ink-dim); text-decoration: none; border-bottom: 1px solid var(--hairline); }
.foot-link a:hover { color: var(--brass); border-color: var(--brass); }

/* ---------- App shell (gallery + admin) ---------- */

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20px 32px;
  border-bottom: 1px solid var(--hairline);
  background: var(--topbar-bg);
  backdrop-filter: blur(6px);
  position: sticky;
  top: 0;
  z-index: 10;
}

/* A backdrop-filter on a sticky bar has to re-sample and re-blur whatever
   is passing behind it on every single scroll frame, which is a real cost
   on phone GPUs and one of the bigger reasons scrolling a big gallery
   stutters instead of gliding. Touch devices get an opaque bar instead —
   var(--bg) rather than a hand-picked color, so every brand theme and
   light/dark combination stays correct automatically. */
@media (hover: none) {
  .topbar {
    backdrop-filter: none;
    background: var(--bg);
  }
}

.topbar .wordmark { margin: 0; justify-content: flex-start; }
.topbar .wordmark-text { font-size: 16px; }
.topbar .wordmark-sub { font-size: 9px; margin-top: 1px; }
@media (max-width: 560px) {
  .topbar .wordmark-sub { display: none; }
}

.topbar-right { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }
.topbar-right-buttons { display: flex; align-items: center; gap: 16px; }

/* Without this, the topbar's row of buttons (Client Sign-in, Help,
   Client Activity, Settings, Activity Log, Sign out, theme toggle) never
   wraps — on a phone-width screen that forces the *entire page* wider
   than the viewport to fit them all on one line, instead of just this
   one row wrapping onto a second line. */
@media (max-width: 700px) {
  .topbar { flex-wrap: wrap; gap: 10px 16px; padding: 16px; }
  .topbar-right { align-items: flex-start; width: 100%; }
  .topbar-right-buttons { flex-wrap: wrap; gap: 8px; }

  /* Tables (clients, admins, albums, activity log, coupons, gift cards —
     every one of them) have more columns than a phone-width screen has
     room for. table {width:100%} alone just squeezes/clips the extra
     columns off-screen with no way to reach them; letting the table's own
     containing panel/modal scroll horizontally keeps every column
     reachable without needing per-table markup changes. */
  .panel, .modal-body-plain { overflow-x: auto; }
}

.admin-url-line {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-faint);
  display: flex;
  align-items: center;
  gap: 6px;
}
.admin-url-line a { color: var(--ink-dim); text-decoration: none; }
.admin-url-line a:hover { color: var(--brass-bright); }

.url-copy-btn {
  background: transparent;
  border: 1px solid var(--hairline);
  color: var(--ink-faint);
  width: 20px;
  height: 20px;
  border-radius: 3px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
}
.url-copy-btn:hover { border-color: var(--brass); color: var(--brass-bright); }
.url-copy-btn svg { width: 11px; height: 11px; }

.viewer-name {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-dim);
  letter-spacing: 0.03em;
}

.container { max-width: 1200px; margin: 0 auto; padding: 40px 32px 80px; }

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 24px;
}

.section-head h1 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 26px;
  margin: 0;
}

.section-head .count {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-faint);
}

.crumb {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--ink-faint);
  letter-spacing: 0.04em;
  margin-bottom: 10px;
}
.crumb a { color: var(--ink-dim); text-decoration: none; }
.crumb a:hover { color: var(--brass); }

/* Album grid */

.album-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 36px;
}

.album-card {
  cursor: pointer;
  text-decoration: none;
  color: var(--ink);
  display: block;
}

.album-thumb {
  aspect-ratio: 4 / 5;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 2px;
  overflow: hidden;
  position: relative;
  margin-bottom: 16px;
  transition: box-shadow 0.25s ease;
}
.album-card:hover .album-thumb { box-shadow: 0 18px 40px rgba(0, 0, 0, 0.25); }

.album-thumb img, .album-thumb video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}

.album-card:hover .album-thumb img,
.album-card:hover .album-thumb video { transform: scale(1.05); }

.album-thumb-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0) 55%, rgba(0, 0, 0, 0.45) 100%);
  pointer-events: none;
}

.album-overlay-text {
  position: absolute;
  left: 0; right: 0; bottom: 20px;
  text-align: center;
  color: #fdfbf7;
  padding: 0 16px;
  line-height: 1.25;
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
  letter-spacing: 0.01em;
  z-index: 1;
  word-break: break-word;
}

.album-download-btn {
  position: absolute;
  top: 8px; right: 9px;
  width: 30px; height: 30px;
  border-radius: 50%;
  background: rgba(20, 19, 17, 0.65);
  border: 1px solid rgba(242, 238, 230, 0.2);
  color: var(--ink);
  display: flex; align-items: center; justify-content: center;
  opacity: 0;
  transition: opacity 0.15s ease, background 0.15s ease;
  cursor: pointer;
  z-index: 1;
  padding: 0;
  font: inherit;
}
.album-card:hover .album-download-btn { opacity: 1; }
.album-download-btn:hover { background: rgba(79, 168, 189, 0.9); border-color: var(--brass); }
.album-download-btn svg { width: 15px; height: 15px; }

.album-paid-badge {
  position: absolute;
  top: 8px; left: 9px;
  background: rgba(20, 19, 17, 0.7);
  border: 1px solid var(--brass);
  color: var(--brass-bright);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 3px 7px;
  border-radius: 2px;
  z-index: 1;
}

/* Same shape/position as .album-paid-badge, just the --danger palette
   instead of --brass — matches the "Locked" badge color already used in
   the admin album table, for the same status shown two different places. */
.album-locked-badge {
  position: absolute;
  top: 8px; left: 9px;
  background: rgba(20, 19, 17, 0.7);
  border: 1px solid var(--danger);
  color: var(--error-text);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 3px 7px;
  border-radius: 2px;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 4px;
}
.album-locked-badge svg { width: 11px; height: 11px; }

.album-thumb .empty-icon {
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-faint);
}

.album-title {
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 500;
  letter-spacing: 0.01em;
  margin: 0 0 3px;
}

.album-meta {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* Photo grid */

.media-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 14px;
}

/* Mobile Safari's long-press "Save Image" sheet isn't a contextmenu event,
   so it needs its own rule — same deterrent as the JS listeners in
   gallery.js, just for touch instead of right-click. */
.media-grid img, #lightboxContent img {
  -webkit-touch-callout: none;
}

.media-tile {
  position: relative;
  aspect-ratio: 1 / 1;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 2px;
  overflow: hidden;
  cursor: pointer;
}

.media-tile img, .media-tile video {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}

/* Progressive thumbnail loading: the tiny placeholder shows immediately,
   the real thumbnail is stacked on top of it starting hidden, then made
   visible the instant it finishes loading — no transition, no blur, so
   there's no lingering filter/animation cost once the swap is done. */
.thumb-tiny, .thumb-full {
  position: absolute;
  inset: 0;
}
.thumb-full {
  opacity: 0;
}
.thumb-full.loaded {
  opacity: 1;
}

.media-tile .frame-no {
  position: absolute;
  top: 6px; left: 7px;
  font-family: var(--font-mono);
  font-size: 10px;
  color: rgba(242, 238, 230, 0.85);
  background: rgba(20, 19, 17, 0.55);
  padding: 2px 6px;
  border-radius: 2px;
  letter-spacing: 0.03em;
}

.media-tile .play-badge {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.media-tile .play-badge svg {
  width: 34px; height: 34px;
  color: rgba(242, 238, 230, 0.9);
  filter: drop-shadow(0 1px 4px rgba(0,0,0,0.5));
}

/* Audio/document icon tiles — used in the grid, album covers, and lightbox */

.file-tile-icon {
  width: 100%; height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background: linear-gradient(160deg, var(--surface) 0%, var(--surface-raised) 100%);
  color: var(--brass);
}
.file-tile-icon svg { width: 30%; height: 30%; max-width: 44px; max-height: 44px; }
.file-tile-ext {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.05em;
  color: var(--ink-faint);
}
.file-tile-path {
  font-family: var(--font-mono);
  font-size: 9.5px;
  color: var(--ink-faint);
  opacity: 0.7;
  max-width: 90%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 0 6px;
}

.audio-preview {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  padding: 40px;
}
.audio-preview svg { width: 64px; height: 64px; color: var(--brass); }
.audio-preview audio { width: 340px; max-width: 80vw; }

.doc-frame {
  width: 82vw;
  height: 86vh;
  max-width: 1000px;
  border: none;
  border-radius: 2px;
  background: #fff;
}

.ghost-link {
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.03em;
  color: var(--ink-dim);
  text-decoration: none;
  border: 1px solid var(--hairline);
  background: transparent;
  cursor: pointer;
  padding: 6px 12px;
  border-radius: 12px;
  white-space: nowrap;
}
.ghost-link:hover { color: var(--brass-bright); border-color: var(--brass); }
.ghost-link.active { color: var(--brass-bright); border-color: var(--brass); background: rgba(79, 168, 189, 0.1); }

.no-preview-message {
  text-align: center;
  color: var(--ink-dim);
  max-width: 320px;
}
.no-preview-message img { opacity: 0.7; margin-bottom: 16px; }
.no-preview-message p { margin: 4px 0; font-size: 13.5px; line-height: 1.5; }
.no-preview-message p:first-of-type { color: var(--ink); font-weight: 500; }

/* Lightbox */

.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(10, 9, 8, 0.94);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 100;
  padding: 40px;
}
.lightbox.open { display: flex; }

.lightbox img, .lightbox video {
  max-width: 100%;
  max-height: 100%;
  border-radius: 2px;
}

.lightbox-close, .lightbox-nav {
  position: absolute;
  background: rgba(28, 26, 23, 0.8);
  border: 1px solid var(--hairline);
  color: var(--ink);
  width: 42px; height: 42px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  font-size: 18px;
}
.lightbox-close { top: 24px; right: 24px; }
.lightbox-nav.prev { left: 24px; top: 50%; transform: translateY(-50%); }
.lightbox-nav.next { right: 24px; top: 50%; transform: translateY(-50%); }
.lightbox-close:hover, .lightbox-nav:hover { border-color: var(--brass); color: var(--brass-bright); }

.lightbox-caption {
  position: absolute;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--ink-dim);
  letter-spacing: 0.04em;
}

.empty-state {
  text-align: center;
  padding: 80px 20px;
  color: var(--ink-faint);
}
.empty-state .peak-mark { width: 32px; height: 32px; color: var(--ink-faint); margin-bottom: 14px; }
.empty-state p { font-family: var(--font-mono); font-size: 13px; letter-spacing: 0.02em; }

/* Selection & downloads */

.download-fab {
  position: fixed;
  bottom: calc(28px + env(safe-area-inset-bottom));
  right: 28px;
  background: var(--brass);
  color: var(--on-brass);
  border: none;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  z-index: 150;
}
.download-fab svg { width: 22px; height: 22px; }
.download-fab #downloadCount {
  position: absolute;
  top: -4px; right: -4px;
  background: var(--teal);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 11px;
  min-width: 20px;
  height: 20px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 5px;
  border: 2px solid var(--bg);
}

/* Sits above the download-fab (bottom:28px) so both can be visible at
   once without overlapping — select-mode zip download and the paid-items
   cart are independent features that can both be active simultaneously. */
.cart-fab {
  position: fixed;
  bottom: calc(96px + env(safe-area-inset-bottom));
  right: 28px;
  background: var(--teal);
  color: var(--ink);
  border: 1px solid var(--brass);
  width: 56px;
  height: 56px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  z-index: 150;
}
.cart-fab svg { width: 22px; height: 22px; }
.cart-fab #cartCount {
  position: absolute;
  top: -4px; right: -4px;
  background: var(--brass);
  color: var(--on-brass);
  font-family: var(--font-mono);
  font-size: 11px;
  min-width: 20px;
  height: 20px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 5px;
  border: 2px solid var(--bg);
}

.cart-item-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--hairline);
  font-size: 13px;
  border-radius: 3px;
  transition: background 0.12s ease;
}
.cart-item-row:hover { background: var(--surface); }
.cart-item-row:last-child { border-bottom: none; }
.cart-item-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cart-item-album { color: var(--ink-faint); font-family: var(--font-mono); font-size: 10.5px; }
.cart-item-remove {
  background: transparent;
  border: none;
  color: var(--ink-faint);
  cursor: pointer;
  font-size: 16px;
  padding: 2px 6px;
  flex-shrink: 0;
}
.cart-item-remove:hover { color: var(--danger); }

/* Modal is a fixed-height column: header, a scrollable item list that
   takes whatever space is left, then a footer (coupon/gift card/total/
   checkout) that's always fully visible and reachable without having to
   scroll past a long list of items first. */
.cart-modal {
  display: flex;
  flex-direction: column;
  max-height: 82vh;
  padding: 0;
}
.cart-modal .modal-head { padding: 20px 24px 16px; flex-shrink: 0; }
.cart-items-scroll {
  overflow-y: auto;
  flex: 1;
  min-height: 60px;
  padding: 4px 20px;
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
  background: var(--bg);
}
.cart-footer {
  flex-shrink: 0;
  padding: 18px 24px 22px;
}
.cart-powered-by {
  text-align: center;
  margin-top: 14px;
  font-size: 10.5px;
  color: var(--ink-faint);
}
.cart-powered-by a { color: var(--ink-faint); }
.cart-powered-by a:hover { color: var(--brass-bright); }

.cart-breakdown {
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--ink-dim);
  margin: 16px 0;
  padding-top: 12px;
  border-top: 1px solid var(--hairline);
}
.cart-breakdown-row { display: flex; justify-content: space-between; padding: 3px 0; }
.cart-breakdown-row.total { color: var(--ink); font-weight: 600; font-size: 13.5px; padding-top: 8px; margin-top: 4px; border-top: 1px solid var(--hairline); }
.cart-breakdown-row.free { color: var(--brass-bright); }

.tile-price-badge {
  position: absolute;
  top: 6px; left: 7px;
  max-width: calc(100% - 14px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  background: rgba(20, 19, 17, 0.7);
  border: 1px solid var(--brass);
  color: var(--brass-bright);
  font-family: var(--font-mono);
  font-size: 10px;
  padding: 2px 6px;
  border-radius: 2px;
  z-index: 1;
}

.tile-cart-btn {
  position: absolute;
  top: 6px; right: 7px;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: rgba(20, 19, 17, 0.6);
  border: 1px solid rgba(242, 238, 230, 0.2);
  color: var(--ink);
  display: flex; align-items: center; justify-content: center;
  opacity: 1;
  transition: opacity 0.15s ease, background 0.15s ease;
  cursor: pointer;
  padding: 0;
}
.tile-cart-btn:hover { background: rgba(79, 168, 189, 0.85); border-color: var(--brass); }
.tile-cart-btn.in-cart { background: var(--brass); border-color: var(--brass); color: var(--on-brass); opacity: 1; }
.tile-cart-btn svg { width: 14px; height: 14px; }

.tile-favorite-btn {
  position: absolute;
  bottom: 7px; right: 7px;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: rgba(20, 19, 17, 0.55);
  border: 1px solid rgba(242, 238, 230, 0.2);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  padding: 0;
  z-index: 1;
  transition: background 0.15s ease, color 0.15s ease;
}
.tile-favorite-btn:hover { border-color: var(--danger); }
.tile-favorite-btn.active { color: var(--danger); background: rgba(20, 19, 17, 0.7); }
.tile-favorite-btn svg { width: 14px; height: 14px; }

/* Now a real button rather than a read-only badge: comments used to be
   reachable only after opening a photo full-screen, so from the grid you
   could see that a photo had comments but not read or add any. Sized to
   stay tappable on a phone even when it's showing just the icon. */
.tile-comment-badge {
  position: absolute;
  bottom: 7px; left: 7px;
  background: rgba(20, 19, 17, 0.6);
  color: #fff;
  font-family: var(--font-mono);
  font-size: 10.5px;
  padding: 3px 7px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  gap: 4px;
  z-index: 1;
  border: none;
  cursor: pointer;
  min-height: 22px;
  line-height: 1;
}
.tile-comment-badge:hover { background: rgba(20, 19, 17, 0.85); }
/* Nothing to show a count for yet — kept subtle until hover/tap so a grid
   of uncommented photos isn't covered in identical icons, but still
   present so the first comment can actually be left from here. */
.tile-comment-badge.is-empty { opacity: 0.55; }
.media-tile:hover .tile-comment-badge.is-empty { opacity: 1; }

.tile-comment-badge svg { width: 11px; height: 11px; }

.media-tile .select-check {
  position: absolute;
  bottom: 6px;
  right: 7px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1.5px solid rgba(242, 238, 230, 0.6);
  background: rgba(20, 19, 17, 0.5);
  display: none;
  align-items: center;
  justify-content: center;
}
.media-tile.select-mode .select-check { display: flex; }
.media-tile.selected .select-check { background: var(--brass); border-color: var(--brass); }
.media-tile.selected .select-check svg { display: block; }
.media-tile .select-check svg { display: none; width: 13px; height: 13px; color: var(--on-brass); }
.media-tile.selected { outline: 2px solid var(--brass); outline-offset: -2px; }

.tile-download-btn {
  position: absolute;
  top: 6px; right: 7px;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: rgba(20, 19, 17, 0.6);
  border: 1px solid rgba(242, 238, 230, 0.2);
  color: var(--ink);
  display: flex; align-items: center; justify-content: center;
  opacity: 0;
  transition: opacity 0.15s ease, background 0.15s ease;
  cursor: pointer;
  padding: 0;
}
.media-tile:hover .tile-download-btn,
.media-tile.selected .tile-download-btn { opacity: 1; }
.tile-download-btn:hover { background: rgba(79, 168, 189, 0.85); border-color: var(--brass); }
.tile-download-btn svg { width: 14px; height: 14px; }

/* Same hover-reveal circular button as .tile-download-btn, opposite
   corner — used in the admin's Folder Contents grid for per-file delete. */
.tile-delete-btn {
  position: absolute;
  top: 6px; left: 7px;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: rgba(20, 19, 17, 0.6);
  border: 1px solid rgba(242, 238, 230, 0.2);
  color: var(--ink);
  display: flex; align-items: center; justify-content: center;
  opacity: 0;
  transition: opacity 0.15s ease, background 0.15s ease;
  cursor: pointer;
  padding: 0;
  font-size: 13px;
}
.media-tile:hover .tile-delete-btn { opacity: 1; }
.tile-delete-btn:hover { background: rgba(192, 102, 106, 0.85); border-color: #c0666a; }

.lightbox-download-btn {
  position: absolute;
  bottom: 24px;
  right: 24px;
  background: rgba(28, 26, 23, 0.8);
  border: 1px solid var(--hairline);
  color: var(--ink);
  width: 42px; height: 42px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  padding: 0;
}
.lightbox-download-btn svg { width: 18px; height: 18px; }
.lightbox-download-btn:hover { border-color: var(--brass); color: var(--brass-bright); background: var(--brass); }

.lightbox-favorite-btn, .lightbox-comments-btn {
  position: absolute;
  bottom: 24px;
  background: rgba(28, 26, 23, 0.8);
  border: 1px solid var(--hairline);
  color: var(--ink);
  width: 42px; height: 42px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  padding: 0;
}
.lightbox-favorite-btn { right: 76px; }
.lightbox-comments-btn { right: 128px; }
.lightbox-favorite-btn svg, .lightbox-comments-btn svg { width: 18px; height: 18px; }
.lightbox-favorite-btn:hover { border-color: var(--danger); color: var(--danger); }
.lightbox-favorite-btn.active { color: var(--danger); border-color: var(--danger); }
.lightbox-comments-btn:hover { border-color: var(--brass); color: var(--brass-bright); }
.lightbox-comments-btn.has-comments { border-color: var(--brass); color: var(--brass-bright); }

.lightbox-comments-panel {
  position: absolute;
  bottom: 80px;
  right: 24px;
  width: 300px;
  max-height: 300px;
  background: rgba(19, 38, 46, 0.95);
  border: 1px solid var(--hairline);
  border-radius: 4px;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.lightbox-comments-list {
  overflow-y: auto;
  max-height: 200px;
  font-size: 12.5px;
  color: var(--ink-dim);
}
.lightbox-comments-list .comment-row { padding: 6px 0; border-bottom: 1px solid var(--hairline); }
.lightbox-comments-list .comment-row:last-child { border-bottom: none; }
.lightbox-comments-list .comment-when { font-family: var(--font-mono); font-size: 10px; color: var(--ink-faint); display: block; margin-top: 2px; }
.lightbox-comments-panel input {
  background: var(--bg);
  border: 1px solid var(--hairline);
  color: var(--ink);
  padding: 8px 10px;
  border-radius: 2px;
  font-size: 12.5px;
}

/* Live-update toast */

.toast {
  position: fixed;
  bottom: calc(28px + env(safe-area-inset-bottom));
  left: 50%;
  transform: translateX(-50%) translateY(12px);
  background: var(--surface-raised);
  border: 1px solid var(--brass);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 12.5px;
  letter-spacing: 0.02em;
  padding: 11px 20px;
  border-radius: 20px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease, transform 0.25s ease;
  z-index: 300;
}
.toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* ---------- Admin ---------- */

.admin-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 28px;
}
@media (max-width: 860px) { .admin-grid { grid-template-columns: 1fr; } }

.panel {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 3px;
  padding: 26px 26px 28px;
}

.panel h2 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 18px;
  margin: 0 0 18px;
}

table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
th {
  text-align: left;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  padding: 0 8px 10px;
  border-bottom: 1px solid var(--hairline);
}
td { padding: 10px 8px; border-bottom: 1px solid var(--hairline); vertical-align: middle; }
tr:last-child td { border-bottom: none; }

/* Keeps a cell to one line regardless of how long its content is —
   without this, a long activity-log detail, a gift card's "used" note,
   or a comment message wraps onto multiple lines in just that one row,
   making the whole table look jagged/offset rather than evenly spaced.
   Full text is still available via the same element's title="" tooltip
   where callers already set one. */
.cell-clip {
  max-width: 260px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Same look as .path-hint (mono, faint, small) but without its
   display:block/margin-top — those break vertical-align:middle on a
   <td>, which is what misaligned the gift card table's row dividers
   once a date filled the cell. */
.cell-date-issued {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--ink-faint);
  white-space: nowrap;
}

/* Keeps a row of action buttons (pill-btn) on one line instead of
   wrapping onto a second line when a table gains another column and the
   actions cell ends up with less room — a wrapped actions cell is what
   actually breaks row-height consistency and makes the divider lines
   look offset, not just long text content. */
.cell-actions {
  white-space: nowrap;
}

.pill-btn {
  font-family: var(--font-mono);
  font-size: 11px;
  background: transparent;
  border: 1px solid var(--hairline);
  color: var(--ink-dim);
  padding: 5px 10px;
  border-radius: 12px;
  cursor: pointer;
  margin-right: 6px;
  text-decoration: none;
  display: inline-block;
}
.pill-btn:hover { border-color: var(--brass); color: var(--brass-bright); }
.pill-btn.danger:hover { border-color: var(--danger); color: var(--danger); }
.pill-btn.selected { border-color: var(--brass); color: var(--brass-bright); background: rgba(79, 168, 189, 0.12); }

.file-drop {
  border: 1.5px dashed var(--hairline);
  border-radius: 3px;
  padding: 30px 18px;
  text-align: center;
  color: var(--ink-faint);
  font-size: 13px;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
  margin-bottom: 18px;
}
.file-drop:hover, .file-drop.drag { border-color: var(--brass); color: var(--ink-dim); }
.file-drop input { display: none; }

.file-list {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--ink-dim);
  max-height: 120px;
  overflow-y: auto;
  margin-bottom: 18px;
}
.file-list div { padding: 3px 0; border-bottom: 1px solid var(--hairline); }

select {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--hairline);
  color: var(--ink);
  padding: 11px 13px;
  border-radius: 2px;
  font-size: 14px;
  font-family: var(--font-body);
}
select option {
  background: var(--surface);
  color: var(--ink);
}

.progress-track {
  width: 100%;
  height: 6px;
  background: var(--bg);
  border-radius: 3px;
  overflow: hidden;
  margin-top: 14px;
  display: none;
}
.progress-track.show { display: block; }
.progress-fill { height: 100%; background: var(--brass); width: 0%; transition: width 0.2s ease; }

.progress-label {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--ink-faint);
  margin-top: 6px;
  display: flex;
  justify-content: space-between;
}

/* The time-remaining estimate sits on its own centered line directly
   under the %/speed row, rather than sharing that row as a third
   space-between item — with the % and speed text at different widths,
   a middle flex item there never actually lands at the row's true
   visual center. */
.progress-eta {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--ink-faint);
  margin-top: 4px;
  text-align: center;
}

.upload-done-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background: rgba(95, 168, 118, 0.12);
  border: 1px solid #5fa876;
  color: #5fa876;
  border-radius: 4px;
  padding: 16px;
  margin-top: 10px;
  font-size: 15px;
  font-weight: 600;
}
.upload-done-banner svg { width: 22px; height: 22px; }

/* Wraps the topbar and the upload bar so the pair sticks to the top of
   the viewport together (in that order) as one unit — the topbar itself
   also being position:sticky is harmless/redundant here since it's
   already pinned via this ancestor. */
.app-header-sticky {
  position: sticky;
  top: 0;
  z-index: 250;
}

/* Persistent upload bar — sits directly below the topbar (inside
   .app-header-sticky above), so an upload stays visible after its own
   modal is closed/minimized. Hidden by default; JS toggles the .show
   class. */
.global-upload-bar {
  display: none;
  background: var(--surface-raised);
  border-bottom: 1px solid var(--hairline);
  padding: 8px 20px;
}
.global-upload-bar.show { display: block; }
.global-upload-bar.error { border-bottom-color: #c0666a; }
.global-upload-bar-track {
  width: 100%;
  height: 5px;
  background: var(--bg);
  border-radius: 3px;
  overflow: hidden;
}
.global-upload-bar-fill { height: 100%; background: var(--brass); width: 0%; transition: width 0.2s ease; }
.global-upload-bar.error .global-upload-bar-fill { background: #c0666a; }
.global-upload-bar-row {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 6px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-dim);
}
.global-upload-bar-status { color: var(--ink); font-weight: 600; }
.global-upload-bar.error .global-upload-bar-status { color: #c0666a; }
.global-upload-bar-detail { color: var(--ink-faint); }
.global-upload-bar-spacer { flex: 1; }
.global-upload-bar-row button { padding: 6px 14px; font-size: 12px; }
.global-upload-bar-dismiss {
  background: transparent;
  border: none;
  color: var(--ink-faint);
  font-size: 14px;
  cursor: pointer;
  padding: 4px 8px;
}
.global-upload-bar-dismiss:hover { color: var(--ink); }

@keyframes rowHighlightPulse {
  0% { background: rgba(79, 168, 189, 0.28); }
  100% { background: transparent; }
}
.row-highlight { animation: rowHighlightPulse 2s ease-out; }

/* Source badges (linked vs uploaded) */

.badge {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  padding: 3px 9px;
  border-radius: 10px;
  border: 1px solid var(--hairline);
}
.badge.linked { color: var(--success-text); border-color: rgba(28, 90, 99, 0.6); background: rgba(28, 90, 99, 0.15); }
.badge.uploaded { color: var(--brass-bright); border-color: rgba(79, 168, 189, 0.4); background: rgba(79, 168, 189, 0.1); }
.badge.danger { color: var(--error-text); border-color: rgba(182, 71, 46, 0.5); background: rgba(182, 71, 46, 0.12); }
.badge.donation { color: var(--teal-text); border-color: var(--teal-text); background: rgba(47, 125, 116, 0.12); }

.path-hint {
  display: block;
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--ink-faint);
  margin-top: 3px;
  max-width: 340px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Modals */

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(10, 9, 8, 0.7);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 200;
  padding: 16px;
}
.modal-overlay.open { display: flex; }

.modal {
  width: 100%;
  max-width: 640px;
  /* Was 82vh — content-heavy modals (Coupons & Gift Cards, Branding, etc.)
     were scrolling internally well before there was any real need to,
     especially on shorter laptop screens. Trimming the overlay's own
     padding above and giving the modal itself more of the viewport
     reclaims that space without changing anything about the content. */
  max-height: 92vh;
  background: var(--surface-raised);
  border: 1px solid var(--hairline);
  border-radius: 4px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.modal-narrow { max-width: 460px; }
.modal-wide { max-width: 860px; }

.activity-table { table-layout: fixed; }
.activity-table th:nth-child(1), .activity-table td:nth-child(1) { width: 100px; }
.activity-table th:nth-child(2), .activity-table td:nth-child(2) { width: 130px; }
.activity-table th:nth-child(3), .activity-table td:nth-child(3) { width: 150px; }
.activity-table th:nth-child(4), .activity-table td:nth-child(4) { width: auto; }
.activity-table th:nth-child(5), .activity-table td:nth-child(5) { width: 60px; text-align: right; }
.activity-table td { word-break: break-word; overflow-wrap: anywhere; }
.activity-table td:nth-child(2), .activity-table td:nth-child(3) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 20px;
  border-bottom: 1px solid var(--hairline);
  flex-shrink: 0;
}
.modal-head h3 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 17px;
  margin: 0;
}
.modal-close {
  background: transparent;
  border: none;
  color: var(--ink-faint);
  font-size: 16px;
  cursor: pointer;
  padding: 4px 8px;
}
.modal-close:hover { color: var(--ink); }

#browserPath { padding: 12px 20px 0; margin: 0; word-break: break-all; }

.modal-body {
  display: grid;
  grid-template-columns: 140px 1fr;
  gap: 0;
  overflow: hidden;
  flex: 1;
  min-height: 260px;
  padding: 14px 0;
}
.modal-body-plain { display: block; padding: 20px; overflow-y: auto; }

.quick-links {
  padding: 0 12px;
  border-right: 1px solid var(--hairline);
  overflow-y: auto;
}
.quick-links button {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  color: var(--ink-dim);
  font-family: var(--font-mono);
  font-size: 11.5px;
  padding: 7px 8px;
  border-radius: 2px;
  cursor: pointer;
}
.quick-links button:hover { color: var(--brass-bright); background: rgba(79, 168, 189, 0.08); }

.folder-list { overflow-y: auto; padding: 0 8px; }
.folder-row {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 13.5px;
  padding: 9px 10px;
  border-radius: 2px;
  cursor: pointer;
}
.folder-row:hover { background: rgba(79, 168, 189, 0.08); }

.summary-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px;
}
.summary-tile {
  background: var(--bg);
  border: 1px solid var(--hairline);
  border-radius: 3px;
  padding: 14px 16px;
}
.summary-tile-label {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 6px;
}
.summary-tile-value {
  font-family: var(--font-display);
  font-size: 22px;
  color: var(--ink);
}
.summary-tile-value.danger { color: var(--danger); }
.summary-tile-sub {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-faint);
  margin-top: 2px;
}

.file-list {
  max-height: 180px;
  overflow-y: auto;
  margin-top: 10px;
}
.queued-file-row {
  padding: 6px 0;
  border-bottom: 1px solid var(--hairline);
  font-size: 12.5px;
  color: var(--ink-dim);
}
.queued-file-row:last-child { border-bottom: none; }
.queued-file-row-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.queued-file-row-top span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.queued-file-remove {
  background: transparent;
  border: none;
  color: var(--ink-faint);
  cursor: pointer;
  font-size: 14px;
  padding: 2px 6px;
  flex-shrink: 0;
}
.queued-file-remove:hover { color: var(--danger); }

/* Thin per-file progress bar shown under each queued file during upload —
   fills as that file's bytes go out, then snaps to fully green once its
   batch's request actually completes, so it's obvious which files have
   genuinely finished vs. are still in flight. */
.queued-file-progress {
  height: 3px;
  background: var(--hairline);
  border-radius: 2px;
  margin-top: 5px;
  overflow: hidden;
}
.queued-file-progress-fill {
  height: 100%;
  width: 0%;
  background: var(--brass);
  transition: width 0.15s ease, background 0.2s ease;
}
.queued-file-progress-fill.done { width: 100%; background: #4caf6d; }

.file-list-head {
  display: flex;
  justify-content: flex-end;
  margin-top: 10px;
}
.file-list-clear-btn {
  background: transparent;
  border: none;
  color: var(--ink-faint);
  cursor: pointer;
  font-size: 12px;
  text-decoration: underline;
  padding: 2px 0;
}
.file-list-clear-btn:hover { color: var(--danger); }

.pricing-file-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--hairline);
  font-size: 13px;
}
.pricing-file-row:last-child { border-bottom: none; }
.pricing-file-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-dim);
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.pricing-file-name:hover { color: var(--brass-bright); text-decoration: underline; }

/* This preview is opened from inside the Files & Pricing modal (a
   .modal-overlay, z-index 200) — without this it would render behind
   that modal instead of on top of it, since .lightbox's own z-index
   (100) is lower. */
#adminPreviewLightbox { z-index: 260; }
.pricing-file-price {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}
.pricing-file-price span { color: var(--ink-faint); font-family: var(--font-mono); font-size: 12px; }
.pricing-file-price input {
  width: 80px;
  padding: 6px 8px;
  font-size: 13px;
}
.folder-row svg { width: 15px; height: 15px; color: var(--brass); flex-shrink: 0; }
.folder-row.up { color: var(--ink-faint); }
.folder-row.file { color: var(--ink-faint); cursor: default; }
.folder-row.file:hover { background: transparent; }
.folder-row.file svg { color: var(--ink-faint); }

.modal-foot {
  display: flex;
  align-items: flex-end;
  gap: 14px;
  padding: 16px 20px;
  border-top: 1px solid var(--hairline);
  flex-shrink: 0;
}

/* ---------- Help tooltips ----------
   A small "?" marker next to a setting, explaining what it does and when
   you'd want it. Pure CSS on hover, plus :focus so it also works by
   keyboard and by tap on touch screens (the marker carries tabindex="0" —
   without that, a phone user could never see any of these).

   Text lives in a data-tip attribute rather than title="": the native
   tooltip is slow to appear, can't be styled to match the app, and on
   touch devices never shows at all. */
.help-tip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  margin-left: 6px;
  border: 1px solid var(--hairline);
  border-radius: 50%;
  color: var(--ink-faint);
  background: var(--surface);
  font-family: var(--font-body);
  font-size: 10px;
  font-weight: 600;
  line-height: 1;
  cursor: help;
  position: relative;
  vertical-align: middle;
  text-transform: none;
  letter-spacing: 0;
  flex-shrink: 0;
}

.help-tip:hover,
.help-tip:focus-visible {
  color: var(--brass-bright);
  border-color: var(--brass);
  outline: none;
}

/* The bubble itself. Hidden rather than display:none so it can transition,
   and pointer-events:none so it never swallows a click meant for whatever
   is underneath it. */
.help-tip::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(3px);
  width: max-content;
  max-width: 260px;
  padding: 9px 11px;
  border: 1px solid var(--hairline);
  border-radius: 3px;
  background: var(--surface-raised);
  color: var(--ink-dim);
  font-size: 12px;
  font-weight: 400;
  line-height: 1.5;
  text-align: left;
  white-space: normal;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.13s ease, transform 0.13s ease;
  pointer-events: none;
  z-index: 60;
}

.help-tip:hover::after,
.help-tip:focus::after {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
}

/* Near the left or right edge of a narrow modal a centered bubble would
   overflow the viewport, so these opt out of centering. */
.help-tip.tip-left::after {
  left: auto;
  right: -4px;
  transform: translateX(0) translateY(3px);
}
.help-tip.tip-left:hover::after,
.help-tip.tip-left:focus::after {
  transform: translateX(0) translateY(0);
}

/* Left-anchored: bubble's left edge sits at the marker. Used when a
   centered or right-anchored bubble would spill off the left. */
.help-tip.tip-right::after {
  left: -4px;
  right: auto;
  transform: translateX(0) translateY(3px);
}
.help-tip.tip-right:hover::after,
.help-tip.tip-right:focus::after {
  transform: translateX(0) translateY(0);
}

/* On small screens a 260px bubble anchored to a marker can still spill.
   Pinning it to the marker's left edge and capping the width keeps it on
   screen whatever the anchor position. */
@media (max-width: 600px) {
  .help-tip::after,
  .help-tip.tip-left::after {
    left: auto;
    right: -4px;
    transform: translateX(0) translateY(3px);
    max-width: min(240px, calc(100vw - 60px));
  }
  .help-tip:hover::after,
  .help-tip:focus::after,
  .help-tip.tip-left:hover::after,
  .help-tip.tip-left:focus::after {
    transform: translateX(0) translateY(0);
  }
}

/* The default 1px hairline border-bottom on td is easy to miss on a
   tightly-packed, single-line table like this one — there isn't enough
   surrounding whitespace for the eye to register it, unlike wider rows
   elsewhere. Same underlying issue as an old gift-card table bug: rows
   need a border strong enough to actually separate them. */
#adminDetailClientsBody tr td {
  border-bottom: 1px solid var(--ink-faint);
}

/* ---------- Touch targets on media tiles ----------
   Deliberately at the very end of this file. These rules have the same
   specificity as the base .tile-*-btn definitions above, so source order
   is the only thing deciding the winner — placed any earlier, the later
   base rules silently override them and the buttons stay small.

   On a touch screen those controls were 22-26px, well under what a
   fingertip hits reliably: the actions existed but were fiddly enough to
   feel broken. There's no hover on these devices either, so nothing fades
   in — whatever a tile can do is visible and tappable. */
@media (hover: none) {
  .tile-download-btn, .tile-cart-btn, .tile-favorite-btn {
    width: 36px;
    height: 36px;
  }
  .tile-download-btn svg, .tile-cart-btn svg, .tile-favorite-btn svg {
    width: 17px;
    height: 17px;
  }
  .tile-comment-badge {
    min-height: 30px;
    padding: 6px 10px;
    font-size: 11.5px;
  }
  .tile-comment-badge svg { width: 14px; height: 14px; }
  .tile-comment-badge.is-empty { opacity: 0.85; }

  /* These are hidden at opacity 0 and revealed by :hover on the tile or
     album card. A touch screen never produces hover, so on a phone they
     were invisible and unreachable — the download button on a photo, the
     download-folder / add-to-cart button on an album, and the admin's
     per-file delete simply weren't there. Shown permanently instead;
     there's no pointer to reveal them with.

     .thumb-full is deliberately NOT in this list: its opacity 0 is the
     blur-up placeholder fading into the loaded image, not a hidden
     control. Forcing it visible would break that transition. */
  .tile-download-btn,
  .album-download-btn,
  .tile-delete-btn {
    opacity: 1;
  }
}

/* ---------- Individual-download progress ----------
   Sits above the floating action buttons and clear of the phone's gesture
   bar. Only ever shown while files are being handed off one at a time; a
   zip is a single download the browser or phone reports on itself. */
.download-progress-banner {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(28px + env(safe-area-inset-bottom));
  z-index: 1600;
  width: min(420px, calc(100% - 32px));
  background: var(--surface);
  border: 1px solid var(--brass);
  border-radius: 4px;
  padding: 12px 14px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
.download-progress-text {
  font-size: 13.5px;
  color: var(--ink);
  margin-bottom: 8px;
}
.download-progress-track {
  height: 3px;
  background: var(--hairline);
  border-radius: 2px;
  overflow: hidden;
}
.download-progress-fill {
  height: 100%;
  width: 0;
  background: var(--brass);
  transition: width 0.2s ease;
}
