/* ─────────────────────────────────────────────────────────────────────────
   upload-panel/panel.css
   Styles for the Share Your Memories upload panel.
   Link from public/upload.html <head> AFTER the existing styles.css link.
   Inherits all CSS custom properties and font families from styles.css.
   ───────────────────────────────────────────────────────────────────────── */

/* Site-wide focus indicator (mirrors styles.css base rule). Standardised on
   var(--color-focus): #333 cleared 3:1 everywhere it was used, but panel.css
   loads after styles.css so this rule was winning at equal specificity —
   keeping every focus ring on one token instead of two colours drifting
   apart. */
:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

:root {
  /* --color-error (#a3352c) is 3.95:1 on --color-champagne — under the 4.5:1
     text minimum, only reached once the form sits on the champagne band
     introduced below. This darker red keeps the "error" hue but clears
     4.5:1 (4.84:1 measured) for the validation copy that sits directly on
     the band rather than inside a white card. */
  --color-error-deep: #8c2e26;
}

/* ── Upload page header ───────────────────────────────────────────────────
   Gone: this page's header trio and its lede now live in the shared
   .subpage-masthead (styles.css), on the dark band with the nav — which is
   what removes the flat off-white opening the page used to have. The old
   .upload-header / .photos-subtitle rules are deleted rather than left in
   place: panel.css loads after styles.css, so `color: var(--color-primary)`
   on .upload-header .section-title would have won and painted the title
   near-black on the dark band. */
/* ── Panel background ──────────────────────────────────────────────────── */
.photos-panel {
  background-color: var(--color-light);
}

/* ── Champagne form band ───────────────────────────────────────────────────
   The header above stays on --color-light; this wraps the warning banner,
   form and success message in the same background-color + texture-noise
   pairing as the landing page's .details-panel (styles.css), so the page
   reads light header -> champagne form band -> dark footer instead of one
   flat, undifferentiated surface. It's an inset band (fills .panel-content's
   width) rather than a full 100vw bleed, deliberately — a viewport-width
   breakout is the more literal reading of "band" but risks the 320px
   horizontal-overflow regression this audit explicitly flags; this gets the
   same colour/texture/rhythm without that risk. */
.upload-band {
  background-color: var(--color-champagne);
  background-image: var(--texture-noise);
  /* Full-bleed: no radius, and the vertical padding carries the band rhythm
     while the horizontal padding is only a safety gutter — the form column
     itself is set by the .upload-form / .upload-field max-widths below. */
  padding: clamp(2.5rem, 6vw, 4rem) 1.5rem;
}

/* ── Upload init error ─────────────────────────────────────────────────── */
/* Both notices are prepended to the <section>, so they are full-bleed siblings
   of .upload-band, not children of it. They therefore stack directly on top of
   the champagne band as their own tinted strips (margin-bottom: 0 — a gap here
   would show as a stray sliver of off-white between two full-bleed bands), and
   their inner padding tracks the FIELD column (640px, not .upload-form's own
   720px) so the text lines up with the visible edges of the name input and the
   drop zone below rather than sitting 36px to their left. */
.upload-init-error {
  padding: 1rem 1.25rem;
  padding-inline: max(1.25rem, calc((100% - 640px) / 2));
  background: rgba(163, 53, 44, 0.08);
  border-left: 3px solid var(--color-error);
  color: var(--color-error);
  font-size: 0.9rem;
  margin-bottom: 0;
}

/* ── Resume banner (C7) ────────────────────────────────────────────────── */
.upload-resume-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.75rem 1.25rem;
  padding-inline: max(1.25rem, calc((100% - 640px) / 2));
  background: rgba(58, 90, 64, 0.08);
  border-left: 3px solid var(--color-sage);
  font-size: 0.85rem;
  color: var(--color-primary);
  margin-bottom: 0;
}

.upload-resume-dismiss {
  background: none;
  border: none;
  font-size: 1.2rem;
  cursor: pointer;
  color: var(--color-muted);
  min-width: 44px;
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ── Upload warning banner ─────────────────────────────────────────────── */
.upload-warn {
  background: var(--color-sage);
  color: white;
  font-size: 0.85rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  padding: 0.6rem 1.25rem;
  margin-bottom: 1.5rem;
  text-align: center;
}

/* ── Batch progress strip (C2) ─────────────────────────────────────────── */
.upload-progress-strip {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--color-light);
  border-bottom: 1px solid var(--color-champagne);
  padding: 0.5rem 0.75rem;
  font-size: 0.8rem;
  font-family: var(--font-body);
  font-variant-numeric: tabular-nums;
  color: var(--color-muted);
  margin-bottom: 0.5rem;
}

/* ── Form layout ───────────────────────────────────────────────────────── */
.upload-form {
  max-width: 580px;
  margin: 0 auto;
  text-align: left;
}

.upload-field {
  margin-bottom: 1.25rem;
}

.upload-optional {
  font-size: 0.8rem;
  font-weight: 400;
  /* Sits directly on the champagne band below — --color-muted is 3.11:1
     there and fails AA; --color-muted-deep is 5.76:1. */
  color: var(--color-muted-deep);
  margin-left: 0.4rem;
}

/* Required-field indicator on Your Name — asterisk plus a small uppercase
   wide-tracked note, echoing .pre-title's treatment at a smaller scale. The
   asterisk is aria-hidden; aria-required="true" on the input already
   conveys required-ness to screen readers. */
.upload-req-star {
  /* Was --color-secondary (the "gold" token) — 1.30:1 against the champagne
     band this now sits on. No accent/gold in the palette clears 4.5:1 there
     (see .map-link:hover's note in styles.css for the same limitation), so
     this uses the same --color-muted-deep (5.76:1) as the rest of the
     band's text. */
  color: var(--color-muted-deep);
  margin-left: 0.2rem;
}

.upload-req-note {
  display: inline-block;
  font-family: var(--font-body);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--color-muted-deep);
  margin-left: 0.6rem;
}

/* ── Drop zone ─────────────────────────────────────────────────────────── */
/* Sits directly on the champagne form band (.upload-band, below). The
   previous champagne-on-champagne border and rgba(212,196,168,0.12) fill
   were both nearly invisible there, so this is now the reverse: a light
   inset (var(--color-light)) with a --color-muted-deep border — 5.76:1
   against the band, well past the 3:1 non-text minimum for the page's
   primary interactive target. */
.upload-drop-zone {
  border: 2px solid var(--color-muted-deep);
  background-color: var(--color-light);
  border-radius: 4px;
  padding: 2.25rem 1.5rem;
  text-align: center;
  cursor: pointer;
  transition: border-color 0.2s, background-color 0.2s;
  margin-bottom: 0.75rem;
  outline: none;
}

.upload-drop-zone:hover,
.upload-drop-zone:focus-visible,
.upload-drop-zone.drag-over {
  /* var(--color-focus) (== --color-accent, #7a6449) is 3.27:1 against the
     champagne band — passes 3:1 non-text — and consistent with the
     standardised focus token used everywhere else in this file. */
  border-color: var(--color-focus);
  background-color: rgba(122, 100, 73, 0.08);
}

.drop-arrow {
  font-size: 2rem;
  color: var(--color-secondary);
  line-height: 1;
  margin-bottom: 0.75rem;
  transition: transform 0.2s;
}

@media (prefers-reduced-motion: reduce) {
  .drop-arrow { transition: none; }
}

.upload-drop-zone:hover .drop-arrow,
.upload-drop-zone.drag-over .drop-arrow {
  transform: translateY(-3px);
}

@media (prefers-reduced-motion: reduce) {
  .upload-drop-zone:hover .drop-arrow,
  .upload-drop-zone.drag-over .drop-arrow {
    transform: none;
  }
}

.drop-primary {
  font-size: 1rem;
  font-weight: 500;
  color: var(--color-primary);
  margin-bottom: 0.3rem;
}

.drop-secondary {
  font-size: 0.9rem;
  color: var(--color-muted);
  margin-bottom: 0.5rem;
}

.drop-note {
  font-size: 0.75rem;
  color: var(--color-muted);
  letter-spacing: 0.04em;
}

/* ── Folder-pick button (desktop only) ─────────────────────────────────── */
.upload-folder-btn {
  display: block;
  width: 100%;
  min-height: 44px;                       /* C9 touch target */
  padding: 0.55rem 1rem;
  font-family: var(--font-body);
  font-size: 0.8rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  /* Sits directly on the champagne band — --color-accent is 3.27:1 there,
     under the 4.5:1 text minimum (it was 4.99:1 on the page's old light
     background). --color-muted-deep matches the band's other text at
     5.76:1. Same reasoning for the border: --color-champagne-on-champagne
     was invisible; --color-muted-deep reads clearly. */
  color: var(--color-muted-deep);
  background: transparent;
  border: 1px solid var(--color-muted-deep);
  cursor: pointer;
  transition: border-color 0.2s, color 0.2s;
  /* C9: generous bottom margin — .upload-submit-btn is position:sticky and
     pins to the viewport bottom; on short forms (empty queue, no turnstile
     yet) its "stuck" position can land close to this button's natural flow
     position. This margin keeps a safe gap between them at any width. */
  margin-bottom: 2rem;
}

.upload-folder-btn:hover {
  border-color: var(--color-primary);
  color: var(--color-primary);
}

/* ── Rejected files list (C8) ──────────────────────────────────────────── */
.upload-rejected-details {
  margin: 0.5rem 0 1rem;
  font-size: 0.82rem;
  /* Directly on the champagne band — see --color-muted-deep note above. */
  color: var(--color-muted-deep);
}

.upload-rejected-details summary {
  cursor: pointer;
  font-weight: 500;
  /* --color-error is 3.95:1 on the champagne band this sits on, under the
     4.5:1 text minimum — see --color-error-deep at the top of this file. */
  color: var(--color-error-deep);
  min-height: 44px;
  display: flex;
  align-items: center;
}

.upload-rejected-list {
  list-style: disc;
  padding-left: 1.5rem;
  margin-top: 0.4rem;
  line-height: 1.7;
}

/* ── Queue ─────────────────────────────────────────────────────────────── */
.upload-queue {
  margin-bottom: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.upload-item {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  padding: 0.75rem;
  background: white;
  border: 1px solid var(--color-champagne);
}

.upload-item[data-status="complete"] {
  border-color: rgba(58, 90, 64, 0.3);
}

.upload-item[data-status="failed"] {
  border-color: rgba(163, 53, 44, 0.3);
}

/* Thumbnail */
.upload-thumb {
  flex: 0 0 52px;
  width: 52px;
  height: 52px;
  background: var(--color-champagne);
  overflow: hidden;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

.upload-thumb-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.upload-thumb-placeholder {
  font-size: 1.4rem;
  line-height: 1;
  color: var(--color-accent);
}

/* Info column */
.upload-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.upload-filename {
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.upload-size {
  font-size: 0.75rem;
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}

/* Progress bar */
.upload-progress-wrap {
  height: 3px;
  background: var(--color-champagne);
  margin-top: 0.25rem;
  overflow: hidden;
}

.upload-progress-bar {
  height: 100%;
  background: var(--color-secondary);
  width: 0%;
  transition: width 0.15s linear;
}

@media (prefers-reduced-motion: reduce) {
  .upload-progress-bar { transition: none; }
}

/* Status label */
.upload-status-label {
  font-size: 0.75rem;
  letter-spacing: 0.04em;
}

.upload-status-label.is-done {
  color: var(--color-sage);
  font-weight: 600;
}

.upload-status-label.is-error {
  color: var(--color-error);
}

/* C5: per-file error detail */
.upload-error-detail {
  font-size: 0.72rem;
  color: var(--color-error);
  margin-top: 0.15rem;
}

/* C9 (mobile touch target): min-height 44px + real padding, not the 0-padding
   inline text this was. The grid layout below used to render this inside the
   absolutely-positioned .upload-status-label badge (top:0.3rem; left:0.3rem),
   crushing it to an untappable sliver — see .upload-status-label.is-error
   further down, which takes the errored label out of that badge and into
   normal flow beneath the filename instead, so this sizing actually applies
   on screen. */
.upload-retry-btn {
  font-family: var(--font-body);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-accent);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0.5rem 0.35rem;
  margin-left: 0.35rem;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  text-decoration: underline;
}

/* Was --color-secondary: 2.01:1 on this card's white ground, i.e. the hover
   state was less readable than the resting one. Resting is --color-accent
   (4.99:1), so hover darkens to --color-primary (17.4:1) — a real change of
   state rather than the no-op that swapping hover to accent would have been. */
.upload-retry-btn:hover {
  color: var(--color-primary);
}

/* Remove button — C9: 44×44 touch target */
.upload-remove-btn {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  background: none;
  border: none;
  cursor: pointer;
  font-size: 1.2rem;
  line-height: 1;
  color: var(--color-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color 0.15s;
}

.upload-remove-btn:hover {
  color: var(--color-error);
}

/* File error (oversized, etc.) — legacy, now using rejected list */
.upload-file-error {
  font-size: 0.82rem;
  color: var(--color-error);
  padding: 0.4rem 0;
}

/* ── Turnstile status label (C3) ───────────────────────────────────────── */
.upload-ts-label {
  font-size: 0.82rem;
  /* Directly on the champagne band — see --color-muted-deep note above. */
  color: var(--color-muted-deep);
  text-align: center;
  padding: 0.5rem 0;
}

/* A failed challenge aborts the entire batch while every queue row still says
   "queued" — this label is the only explanation the guest gets, so it must not
   read as the same quiet grey status text as "one moment…". */
.upload-ts-label.is-failed {
  color: var(--color-error-deep);
  font-weight: 600;
  font-size: 0.9rem;
  line-height: 1.45;
}

/* ── Submit button — C9: sticky, solid bg, sage, own sizing ────────────── */
.upload-submit-btn {
  width: 100%;
  min-height: 44px;
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 1rem;
  background: var(--color-sage);
  color: white;
  border: 2px solid var(--color-sage);
  cursor: pointer;
  transition: all 0.3s ease;
  /* C9: generous top margin, separate from .upload-folder-btn's margin-bottom
     below — keeps this sticky-pinned button clear of the folder button even
     on short forms where little/no other content is visible. */
  margin-top: 2rem;
}

/* Sticky ONLY once files are queued. The margins above are not enough on their
   own: with an empty queue the whole form is shorter than a phone viewport, so
   a bottom-pinned button floats over the "or pick a folder" button above it and
   clips it in half (measured at 390x844: button pinned 789-844, folder button
   765-809). Sticky exists for the 100-file case, where the queue is taller than
   the screen and the CTA must stay reachable — exactly the case where
   #upload-queue loses its [hidden] attribute. Browsers without :has() keep the
   static button, which is the safe fallback rather than the broken one. */
.upload-form:has(.upload-queue:not([hidden])) .upload-submit-btn {
  position: sticky;
  bottom: 0;
  z-index: 5;
}

/* Matches every other sage CTA on the site (.rsvp-btn:hover, .calendar-btn:hover
   in styles.css): keep the sage fill and swap the label to champagne with a
   glow, instead of swapping the fill to a brown accent. */
.upload-submit-btn:hover:not(:disabled) {
  background: var(--color-sage);
  color: var(--color-champagne);
  text-shadow: 0 0 10px rgba(212, 196, 168, 0.8), 0 0 20px rgba(212, 196, 168, 0.5);
}

/* opacity 0.5 dimmed the label along with the fill, leaving white on a washed
   sage at 3.26:1. A desaturated fill at full opacity still reads as disabled
   and keeps the label legible (white on var(--color-sage-disabled) = 5.15:1). */
.upload-submit-btn:disabled {
  background: var(--color-sage-disabled);
  border-color: var(--color-sage-disabled);
  cursor: not-allowed;
}

@media (prefers-reduced-motion: reduce) {
  .upload-submit-btn { transition: none; }
}

/* ── Success message ───────────────────────────────────────────────────── */
.upload-done-msg {
  margin-top: 2rem;
  padding: 1.75rem 1.5rem;
  background: rgba(58, 90, 64, 0.07);
  border-left: 3px solid var(--color-sage);
}

.upload-done-link {
  display: inline-block;
  margin-top: 0.9rem;
  font-size: 0.82rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  /* Sits on the champagne band (through the translucent green wash above) —
     --color-accent is 3.27:1 there, under the 4.5:1 text minimum. */
  color: var(--color-muted-deep);
  border-bottom: 1px solid currentColor;
  padding-bottom: 2px;
}

.upload-done-link:hover,
.upload-done-link:focus-visible {
  color: var(--color-primary);
}

.upload-done-text {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 300;
  color: var(--color-sage);
  text-wrap: balance;
}

/* ── Queue summary ──────────────────────────────────────────────────────── */
.upload-queue-summary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.5rem;
  font-size: 0.82rem;
  /* Directly on the champagne band — see --color-muted-deep note above. */
  color: var(--color-muted-deep);
}

/* C9: min-height 44px on clear-all */
.upload-clear-btn {
  background: none;
  border: none;
  min-height: 44px;
  padding: 0.2rem 0.5rem;
  font-family: inherit;
  font-size: 0.8rem;
  /* Directly on the champagne band — see --color-muted-deep note above. */
  color: var(--color-muted-deep);
  text-decoration: underline;
  cursor: pointer;
  display: flex;
  align-items: center;
}

.upload-clear-btn:hover { color: var(--color-error-deep); }

/* ── Per-file caption ──────────────────────────────────────────────────── */
.upload-item-caption {
  display: block;
  width: 100%;
  margin-top: 0.45rem;
  padding: 0.4rem 0.55rem;
  font-family: inherit;
  /* iOS Safari zooms the page when an input's font-size is under 16px. This
     input only renders on non-touch devices (see isTouch branch in
     uploader.js — touch devices get .upload-item-caption-trigger + the
     bottom sheet instead, whose textarea is already 1rem), but kept at 1rem
     here too so no input/textarea in this file is ever under 16px. */
  font-size: 1rem;
  color: var(--color-primary);
  background: white;
  border: 1px solid var(--color-champagne);
  border-radius: 4px;
}

.upload-item-caption::placeholder { color: var(--color-muted); }

.upload-item-caption:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  border-color: var(--color-focus);
}

.upload-item-caption:disabled {
  /* Sits on white; not part of the champagne-band contrast audit — see the
     hoisted --color-surface-disabled token in styles.css. */
  background: var(--color-surface-disabled);
  color: var(--color-muted);
}

/* Touch caption trigger (C4) */
.upload-item-caption-trigger {
  display: block;
  width: 100%;
  margin-top: 0.45rem;
  padding: 0.4rem 0.55rem;
  font-family: inherit;
  font-size: 0.82rem;
  color: var(--color-muted);
  background: white;
  border: 1px solid var(--color-champagne);
  border-radius: 4px;
  text-align: left;
  cursor: pointer;
  min-height: 44px;
}

.upload-item-caption-trigger:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

/* ── Required-field error ──────────────────────────────────────────────── */
.upload-field-error {
  margin: 0.4rem 0 0;
  font-size: 0.82rem;
  /* --color-error is 3.95:1 on the champagne band this sits on directly
     (below the input, not inside a white card) — see --color-error-deep at
     the top of this file. */
  color: var(--color-error-deep);
}

#upload-name.is-invalid,
#upload-section.is-invalid,
#upload-section-note.is-invalid {
  border-color: var(--color-error);
}

/* ── Moment picker ───────────────────────────────────────────────────────────
   The <select> already inherits width, padding, 1rem type, the 4px radius and
   the champagne border from ".form-group select" in styles.css, which loads
   before this file. Two things it does not inherit:

   1. A 44px touch target. 0.75rem padding around 1rem text lands at ~40px.
   2. Nothing here drops below the 16px iOS zoom floor — 1rem is exactly 16px,
      so the inherited size is already correct and must not be reduced. */
.upload-select {
  min-height: 44px;
  /* Safari on iOS renders a rounded native control and ignores the border-radius
     above unless the appearance is reset. Keeping the native arrow (no custom
     background-image) means no chevron to mis-position in RTL or at any zoom. */
  -webkit-appearance: none;
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--color-muted-deep) 50%),
                    linear-gradient(135deg, var(--color-muted-deep) 50%, transparent 50%);
  background-position: calc(100% - 1.15rem) 1.15rem, calc(100% - 0.8rem) 1.15rem;
  background-size: 0.35rem 0.35rem, 0.35rem 0.35rem;
  background-repeat: no-repeat;
  padding-right: 2.5rem;
}

/* Explains that one upload = one moment, so a guest with photos spanning the
   weekend expects to make several trips instead of discovering it afterwards —
   sectionId is write-once, so a wrong choice cannot be corrected in the browser.
   Sits directly on the champagne band: --color-muted is 3.11:1 there and fails
   AA, --color-muted-deep is 5.76:1. */
.upload-field-guide {
  margin: 0 0 0.6rem;
  font-family: var(--font-body);
  font-size: 0.82rem;
  line-height: 1.55;
  color: var(--color-muted-deep);
}

/* ── Caption bottom sheet (C4) ─────────────────────────────────────────── */
.caption-sheet-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  z-index: 99;
}

.caption-sheet {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 100;
  background: var(--color-light);
  padding: 1.5rem 1.25rem;
  box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.14);
  border-top: 1px solid var(--color-champagne);
  max-height: 80dvh;
  overflow-y: auto;
}

.caption-sheet-title {
  font-family: var(--font-display);
  font-size: 1.2rem;
  font-weight: 300;
  color: var(--color-primary);
  margin-bottom: 0.75rem;
}

.caption-sheet-thumb {
  width: 100%;
  max-height: 180px;
  object-fit: contain;
  margin-bottom: 1rem;
  background: var(--color-champagne);
}

.caption-sheet-textarea {
  display: block;
  width: 100%;
  /* 1rem prevents iOS automatic zoom on focus (C4) */
  font-size: 1rem;
  font-family: inherit;
  color: var(--color-primary);
  background: white;
  border: 1px solid var(--color-champagne);
  border-radius: 4px;
  padding: 0.6rem 0.75rem;
  resize: none;
  min-height: 80px;
  box-sizing: border-box;
}

.caption-sheet-textarea:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  border-color: var(--color-focus);
}

.caption-sheet-buttons {
  display: flex;
  gap: 0.75rem;
  margin-top: 1rem;
}

.caption-sheet-btn {
  flex: 1;
  font-family: var(--font-body);
  font-size: 0.9rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  min-height: 44px;
  padding: 0.6rem 1rem;
  border: none;
  cursor: pointer;
  text-transform: uppercase;
}

.caption-sheet-done {
  background: var(--color-sage);
  color: white;
}

.caption-sheet-next {
  background: transparent;
  color: var(--color-accent);
  border: 1px solid var(--color-champagne);
}

/* ══════════════════════════════════════════════════════════════════════════
   BULK LAYOUT — overrides the single-column list above.
   ══════════════════════════════════════════════════════════════════════════ */

.upload-queue {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  gap: 0.6rem;
  align-items: start;
  max-height: 62vh;
  overflow-y: auto;
  padding: 0.15rem;
  overflow-anchor: none;
}

.upload-item {
  display: block;
  position: relative;
  padding: 0;
  overflow: hidden;
  border-radius: 4px;
}

.upload-thumb {
  flex: none;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
}

.upload-thumb-placeholder { font-size: 1.75rem; }

.upload-info {
  display: block;
  padding: 0.35rem 0.4rem 0.4rem;
}

.upload-filename {
  display: block;
  font-size: 0.68rem;
  font-weight: 500;
  line-height: 1.25;
}

.upload-size { display: none; }

.upload-progress-wrap {
  position: absolute;
  left: 0;
  right: 0;
  margin: 0;
  height: 4px;
}

.upload-item .upload-progress-wrap {
  position: absolute;
  top: auto;
  bottom: 0;
}

/* Plain done/retrying labels have no interactive control, so they stay a
   compact top-left badge overlaying the thumbnail. */
.upload-status-label:not(:empty):not(.is-error) {
  position: absolute;
  top: 0.3rem;
  left: 0.3rem;
  z-index: 2;
  padding: 0.1rem 0.35rem;
  /* 0.68rem is the site-wide minimum type size; this badge sat at 0.62rem
     (9.9px), where 'Done' and the error states stop being distinguishable. */
  font-size: 0.68rem;
  line-height: 1.5;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.92);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}

/* C9 (mobile touch target): a failed upload's status label carries
   .upload-retry-btn (appended by uploader.js). The badge treatment above
   would crush that button's 44px target to a few pixels, so this state is
   pulled out of the absolute overlay into normal flow instead — a real row
   beneath the filename inside .upload-info, achieved in CSS only (no JS
   change): .upload-error-detail already renders in normal flow right after
   it, so this now sits in the same place. */
.upload-status-label.is-error {
  position: static;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  margin-top: 0.2rem;
}

.upload-actions {
  position: absolute;
  top: 0.25rem;
  right: 0.25rem;
  z-index: 2;
}

/* C9: 44×44 remove button in grid layout */
.upload-remove-btn {
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: rgba(26, 26, 26, 0.62);
  color: #fff;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}

.upload-remove-btn:hover,
.upload-remove-btn:focus-visible {
  background: rgba(163, 53, 44, 0.92);
}

.upload-item-caption {
  margin-top: 0.3rem;
  padding: 0.28rem 0.35rem;
  /* Stays 1rem (16px) even in the tight grid tile — see the base rule's
     comment on why every input in this file holds the 16px iOS floor. */
}

.upload-item-caption-trigger {
  margin-top: 0.3rem;
  padding: 0.28rem 0.35rem;
  font-size: 0.7rem;
}

/* Queue summary line: no rule here. It used to carry a byte-identical copy of
   the .upload-queue-summary block from the QUEUE SUMMARY section above. Equal
   specificity meant this later copy won unconditionally, so editing the earlier
   one — the one a reader finds first — had no visible effect at all. */

@media (max-width: 480px) {
  .upload-queue {
    grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
    gap: 0.45rem;
    max-height: 56vh;
  }
  /* Both of these were below the 0.68rem floor (9.9px and 10.6px) — the two
     smallest strings on the page, on the smallest screens. */
  .upload-filename { font-size: 0.68rem; }
  /* .upload-item-caption is a real <input> — stays at 1rem (16px) here too,
     so it's never shrunk below the size that keeps iOS Safari from zooming
     on focus. Only the trigger button (not an input) shrinks. */
  .upload-item-caption-trigger { font-size: 0.68rem; }
}

/* ── Responsive ────────────────────────────────────────────────────────── */
@media (max-width: 768px) {
  .upload-band {
    padding: 2.25rem 1.25rem;
  }

  .upload-drop-zone {
    padding: 1.75rem 1rem;
  }

  .drop-arrow {
    font-size: 1.6rem;
  }

  .upload-done-text {
    font-size: 1.2rem;
  }
}

/* Turnstile container */
.upload-turnstile {
  display: flex;
  justify-content: center;
}
.upload-turnstile:empty {
  display: none;
}
.upload-turnstile > * {
  margin: 0.5rem 0 0.25rem;
}

/* ── Wide layout for the standalone /upload page ──────────────────────── */
/* Same reasoning as gallery.css: styles.css caps .subpage-nav at 900px, but
   this page's content column is 1180px, so the nav links sat indented from the
   champagne band below them. panel.css loads after styles.css. */
body.subpage .subpage-nav {
  max-width: 1180px;
}

/* .panel-content is gone from this page — the champagne band is now a direct
   child of the section so it can be full-bleed, and the band's own padding
   carries the rhythm that the removed wrapper's padding used to. */

body.subpage .upload-form {
  max-width: 720px;
  margin-left: auto;
  margin-right: auto;
}

body.subpage .upload-field,
body.subpage .upload-drop-zone,
body.subpage .upload-warn,
body.subpage .upload-done-msg {
  max-width: 640px;
  margin-left: auto;
  margin-right: auto;
}

body.subpage .upload-submit-btn {
  max-width: 420px;
  margin-left: auto;
  margin-right: auto;
  display: block;
}
