/* PhotoStop — shared styles. Dark charcoal theme (Tailwind zinc). */
:root {
  --bg: #18181b;
  /* page background — zinc-900, charcoal not pure black so the eye still
     reads it as a surface */
  --panel: #1f1f23;
  /* cards, header, form panels — one notch lighter than bg so panels lift */
  --panel-2: #27272a;
  /* input backgrounds, drop zone, row-hover — lighter still for affordance */
  --text: #f4f4f5;
  /* primary text — zinc-100 */
  --muted: #a1a1aa;
  /* secondary / hint text — zinc-400, AA contrast on bg */
  --accent: #f6821f;
  /* Cloudflare orange — primary buttons (unchanged, brand) */
  --accent-2: #ff9233;
  /* lighter hover on dark */
  --link: #60a5fa;
  /* nav / inline links — blue-400, more legible than blue-600 on dark */
  --border: #3f3f46;
  /* card + input borders — zinc-700, visible without shouting */
  --good: #22c55e;
  /* success / saved states — green-500 */
  --bad: #ef4444;
  /* errors, delete — red-500 */
  --warn: #f59e0b;
  /* "fill manually" hint — amber-500 */
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.30), 0 1px 3px rgba(0, 0, 0, 0.40);
  /* Justified-tile row height. The gallery JS reads this so per-row width
     math (ratio × row-height) tracks the CSS-set tile height. The mobile
     override below shrinks it so 2 photos typically fit per row on phone. */
  --row-height: 240px;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  font: 14px/1.5 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
}

a {
  color: var(--link);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
}

/* Page header / nav.
   Two visual bands inside one element:
     1. A full-bleed hero (::before) that paints the landscape JPEG.
     2. A white nav strip below it carrying brand + nav + user chip.
   We keep the brand/nav/user-chip DOM exactly where it was so every
   page picks this up without an HTML change — `flex-wrap: wrap` plus a
   `flex: 0 0 100%` ::before forces the line break between the two. */
.app-header {
  padding: 0;
  border-bottom: 1px solid var(--border);
  background-color: var(--panel);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: 24px;
  row-gap: 0;
}

.app-header::before {
  content: '';
  display: block;
  flex: 0 0 100%;
  height: 220px;
  background-image: url('/header-bg.jpg');
  background-size: cover;
  background-position: center 70%;
  background-repeat: no-repeat;
}

/* Vertical breathing room + edge padding for the second row. */
.app-header > h1,
.app-header > .app-nav,
.app-header > #header-user {
  padding-top: 14px;
  padding-bottom: 14px;
}
.app-header > h1        { padding-left: 24px; }
.app-header > #header-user { padding-right: 24px; }

.app-header h1 {
  margin: 0;
  font-size: 20px;
  letter-spacing: -0.01em;
}

.app-header h1 a {
  color: var(--text);
  display: inline-flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
}

.app-header h1 a:hover {
  text-decoration: none;
}

.brand-icon {
  width: 32px;
  height: 32px;
  max-width: 32px;
  max-height: 32px;
  object-fit: contain;
  flex: 0 0 auto;
}

.app-nav {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
}

.app-nav a {
  color: var(--muted);
  font-weight: 500;
  padding: 6px 0;
  border-bottom: 2px solid transparent;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.app-nav a:hover {
  color: var(--text);
  text-decoration: none;
}

.app-nav a.active {
  color: var(--text);
  border-bottom-color: var(--accent);
}

/* `.app-nav a` (class+element, specificity 0,1,1) outranks the UA
   `[hidden]` rule (0,1,0), so without this the admin link is visible to
   everyone — non-admins included. Bumping the selector to a[hidden]
   (0,1,2) wins back. */
.app-nav a[hidden] {
  display: none;
}

.nav-icon {
  display: inline-block;
  flex: 0 0 auto;
}

.app-spacer {
  flex: 1;
}

.app-header .api-base {
  color: var(--muted);
  font-size: 12px;
}

/* Forms */
label {
  display: block;
  color: var(--muted);
  font-size: 12px;
  margin-bottom: 4px;
}

input[type="text"],
input[type="number"],
input[type="datetime-local"],
input[type="file"],
textarea,
select {
  background: var(--panel-2);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 8px 10px;
  border-radius: 6px;
  font: inherit;
  width: 100%;
}

textarea {
  min-height: 60px;
  resize: vertical;
}

input:focus,
textarea:focus,
select:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

button {
  background: var(--accent);
  border: 0;
  color: white;
  padding: 9px 18px;
  border-radius: 6px;
  cursor: pointer;
  font: inherit;
  font-weight: 500;
}

button:hover:not(:disabled) {
  background: var(--accent-2);
}

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

button.secondary {
  background: var(--panel);
  border: 1px solid var(--border);
  color: var(--text);
}

button.secondary:hover:not(:disabled) {
  background: var(--panel-2);
}

/* Form-helper buttons (AI suggest, pick-on-map, reference photo). Solid
   green fill with white text — same visual weight as primary (orange)
   and danger (red) buttons, just a different colour family so utility
   actions don't compete with Save / Delete. */
button.btn-tool {
  background: var(--good);
  border: 0;
  color: white;
}

button.btn-tool:hover:not(:disabled) {
  background: #166534;
}

button.danger {
  background: var(--bad);
}

button.danger:hover:not(:disabled) {
  background: #b91c1c;
}

/* Layout */
main {
  padding: 24px;
  max-width: 1400px;
  margin: 0 auto;
}

.toolbar {
  display: flex;
  gap: 12px;
  align-items: flex-end;
  margin-bottom: 20px;
  flex-wrap: wrap;
}

.toolbar>div {
  min-width: 100px;
}

#status,
.status {
  color: var(--muted);
  margin-bottom: 16px;
  font-size: 13px;
}

.status.error {
  color: var(--bad);
}

.status.ok {
  color: var(--good);
}

/* Tile grid */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 18px;
}

.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow);
  transition: transform 0.08s ease, border-color 0.08s ease, box-shadow 0.08s ease;
}

.card:hover {
  transform: translateY(-2px);
  border-color: var(--accent);
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.35), 0 10px 20px rgba(0, 0, 0, 0.45);
}

.card a.thumb {
  display: block;
  aspect-ratio: 4 / 3;
  background: var(--panel-2);
}

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

.card .meta {
  padding: 12px 14px;
}

.card .meta .name {
  font-weight: 600;
  margin-bottom: 2px;
}

.card .meta .where {
  color: var(--muted);
  font-size: 12px;
  display: flex;
  justify-content: space-between;
  gap: 8px;
}

/* ── First-visit hero copy on the gallery (/index.html only) ─────────
   Tagline + sub-paragraph + signature line. Sits between the header
   image and the toggle row. Tight padding so returning users don't
   feel scrolled past. */
.intro-hero {
  padding: 4px 0 28px;
  max-width: 720px;
}

/* Compass badge — sits inline at the start of the title, before "Scout".
   Soft blue background plus a blue-stroked compass = three shades of
   blue layered for depth without dominating the line. vertical-align
   nudges it up so the badge centerline matches the cap line of the
   title text. */
.intro-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 9px;
  background: rgba(96, 165, 250, 0.14);
  vertical-align: 2px;
  margin: 0 8px 0 0;
}

.intro-title {
  margin: 0 0 8px;
  font-size: 30px;
  font-weight: 700;
  letter-spacing: -0.015em;
  color: var(--text);
  line-height: 1.15;
}

.intro-body {
  margin: 0;
  font-size: 16px;
  line-height: 1.55;
  color: var(--muted);
}

.intro-signature {
  margin: 10px 0 0;
  font-size: 14px;
  color: var(--muted);
  font-style: italic;
}

@media (max-width: 600px) {
  .intro-hero { padding: 0 0 20px; }
  .intro-badge { width: 28px; height: 28px; border-radius: 8px; vertical-align: 4px; margin: 0 6px 0 0; }
  .intro-badge svg { width: 16px; height: 16px; }
  .intro-title { font-size: 24px; }
  .intro-body { font-size: 15px; }
  .intro-signature { font-size: 13px; }
}

/* ── Gallery view-mode toggles (filter row above the grid) ──────────── */
/* Inactive pills mirror button.secondary so the row reads as part of
   the same button family as Edit/Delete; active pill picks up the
   brand link blue. Rectangular 6 px corners (not capsule) for the
   same reason. */
.gallery-toggles {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 24px;
}

.gallery-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--panel);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 6px;
  padding: 9px 18px;
  font-size: 14px;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.gallery-toggle .toggle-icon {
  flex-shrink: 0;
  color: var(--muted);
}

.gallery-toggle.active .toggle-icon {
  color: #fff;
}

.gallery-toggle:hover:not(:disabled):not(.active) {
  background: var(--panel-2);
}

.gallery-toggle.active,
.gallery-toggle.active:hover {
  background: var(--link);
  color: #fff;
  border-color: var(--link);
}

.gallery-toggle:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Two labels per pill — full one for desktop, short one for mobile.
   Hide the short label by default; the @media block below flips it. */
.gallery-toggle .label-short {
  display: none;
}

/* ── Gallery sections (one per state/country in grouped modes) ───────── */
.gallery-section {
  margin-bottom: 36px;
}

.gallery-section:last-child {
  margin-bottom: 0;
}

.gallery-section-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 0 0 12px 0;
}

.gallery-section-head h2 {
  font-size: 18px;
  font-weight: 600;
  margin: 0;
  color: var(--text);
  letter-spacing: -0.01em;
}

.gallery-section-head .count {
  color: var(--muted);
  font-size: 13px;
  font-weight: 500;
}

/* ── Justified tile gallery (Flickr-style) ──────────────────────────── */
.tile-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  /* a tiny last-row trick: the final flex item can over-grow if we don't
     rein it in. Acceptable — last row may end short. */
}

.tile {
  position: relative;
  height: var(--row-height);
  overflow: hidden;
  background: var(--panel-2);
  text-decoration: none;
  color: white;
  /* Default flex set inline per tile in JS once the aspect ratio is known.
     Both height and flex-basis hang off --row-height so the mobile media
     query can shrink the layout in one place. */
  flex: 1 1 var(--row-height);
}

.tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0;
  transition: opacity 0.25s ease, transform 0.4s ease;
}

.tile img.loaded {
  opacity: 1;
}

/* Skeleton placeholder — rendered on initial page load before /spots
   responds. Shares the .tile shape (height + panel-2 background) so
   when real tiles swap in, the visual continuity is tight. No
   animation — option (1) "minimum lift". */
.tile-skeleton {
  height: var(--row-height);
  background: var(--panel-2);
  flex: 1 1 var(--row-height);
}

.tile:hover img {
  transform: scale(1.04);
}

.tile:hover {
  text-decoration: none;
}

.tile-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 14px 16px 12px;
  /* darken the bottom for white-text legibility, fade out toward the top */
  background: linear-gradient(to top,
      rgba(0, 0, 0, 0.70) 0%,
      rgba(0, 0, 0, 0.35) 35%,
      rgba(0, 0, 0, 0.00) 70%);
  color: #ffffff;
  pointer-events: none;
  /* clicks pass through to the <a> */
  opacity: 0;
  transition: opacity 0.18s ease;
}

.tile:hover .tile-overlay {
  opacity: 1;
}

.tile-name {
  font-size: 15px;
  font-weight: 600;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
  letter-spacing: -0.005em;
  /* ellipsize long names on a single line */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Sits between .tile-name and .tile-meta when a photographer-supplied
   title is set on the spot — title takes the headline slot, and the
   city/state line demotes to this slightly-smaller subtitle. */
.tile-subtitle {
  font-size: 13px;
  font-weight: 500;
  margin-top: 2px;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
  opacity: 0.95;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tile-meta {
  font-size: 12px;
  margin-top: 2px;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
  opacity: 0.92;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Distance badge: top-right corner pill, only visible if geolocation
   was granted (toggled via the `hidden` attribute in JS). */
.tile-distance[hidden] {
  display: none;
}

.tile-distance {
  position: absolute;
  top: 10px;
  right: 10px;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.01em;
  backdrop-filter: blur(2px);
  pointer-events: none;
}

@media (max-width: 600px) {

  /* Mobile keeps the same two-band shape (hero on top, white nav strip
     below). The hero is shorter at narrow widths so the nav row sits
     above the fold. The white strip is what carries the text now, so
     the image-side gradient can be lighter than it used to be — text
     legibility doesn't depend on it. */
  .app-header {
    column-gap: 12px;
    row-gap: 0;
    justify-content: space-between;
  }

  .app-header::before {
    /* Image extends behind the iOS status bar; the nav strip below it
       is well clear of the notch, so we don't need a safe-area-inset
       padding hack on the wrapper anymore. */
    height: 140px;
    background-image: url('/header-bg.jpg');
  }

  .app-header > h1,
  .app-header > .app-nav,
  .app-header > #header-user {
    padding-top: 10px;
    padding-bottom: 10px;
  }
  .app-header > h1           { padding-left: 16px; }
  .app-header > #header-user { padding-right: 16px; }
  /* When the nav drops to its own row (width: 100%), it loses the flex
     column-gap and would otherwise sit flush against the left edge.
     Give it the same 16 px gutter as the brand for visual alignment. */
  .app-header > .app-nav {
    padding-left: 16px;
    padding-right: 16px;
  }

  .app-header h1 {
    font-size: 20px;
    order: 1;
  }

  #header-user {
    order: 2;
  }

  .brand-icon,
  .app-header .brand-icon {
    width: 28px;
    height: 28px;
    max-width: 28px;
    max-height: 28px;
  }

  .app-nav {
    gap: 14px;
    font-size: 14px;
    order: 3;
    width: 100%;
  }

  /* Hide the desktop spacer so the sign-in/user chip sits next to the nav
     instead of being pushed to its own row by flex-wrap. */
  .app-spacer {
    display: none;
  }

  /* Mobile turns the toggle row into an iOS-style segmented control —
     one rounded track with four equal-width text segments. Active
     segment lifts as a neutral pill (Apple's default segmented look)
     instead of the desktop's blue fill, because the blue read as
     button-y next to three pseudo-buttons. Icons are dropped: the
     labels are self-evident on mobile and the icons doubled the
     visual weight without earning it.

     Equal-width via `flex: 1` so 3-segment (Nearby hidden) and
     4-segment layouts both look intentional. */
  .gallery-toggles {
    display: flex;
    flex-wrap: nowrap;
    gap: 0;
    padding: 3px;
    margin-bottom: 20px;
    background: var(--panel-2);
    border: 1px solid var(--border);
    border-radius: 9px;
  }

  .gallery-toggle {
    flex: 1 1 0;
    min-width: 0;
    padding: 7px 4px;
    font-size: 13px;
    font-weight: 500;
    background: transparent;
    border: 0;
    border-radius: 6px;
    color: var(--muted);
    justify-content: center;
    transition: background 0.15s ease, color 0.15s ease;
  }

  .gallery-toggle:hover:not(:disabled):not(.active) {
    background: transparent;
    color: var(--text);
  }

  .gallery-toggle.active,
  .gallery-toggle.active:hover {
    background: var(--panel);
    color: var(--text);
    border-color: transparent;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
  }

  .gallery-toggle .toggle-icon {
    display: none;
  }

  .gallery-toggle .label-full {
    display: none;
  }

  .gallery-toggle .label-short {
    display: inline;
  }

  /* Mobile gallery: same justified-flex layout as desktop, just with a
     shorter target row height so ~2 photos typically fit per row at
     iPhone widths. The actual photo count per row is data-driven (rows
     of three skinny portraits, two landscapes, etc.) — same algorithm
     that powers the desktop view, just calibrated for the smaller
     viewport. Captions stay hidden on mobile; tap to see context. */
  :root { --row-height: 160px; }

  .tile-grid {
    /* Tighter gap than desktop — small phones lose too much real estate
       to seams at 6 px. */
    gap: 4px;
  }

  /* Captions don't fit on a 160 px-tall tile and read poorly when
     overlaid on busy photos at small sizes. Hide on mobile; the spot
     detail page surfaces title / place / photographer on tap. */
  .tile-overlay {
    display: none;
  }

  /* Distance pill survives — the only on-tile metadata that's useful at
     a glance. Smaller padding to suit the smaller tile. */
  .tile-distance {
    top: 6px;
    right: 6px;
    font-size: 11px;
    padding: 2px 7px;
  }
}

/* Definition list (used on detail page) */
dl.exif {
  margin: 0;
  display: grid;
  grid-template-columns: 130px 1fr;
  gap: 6px 16px;
  font-size: 13px;
}

dl.exif dt {
  color: var(--muted);
}

dl.exif dd {
  margin: 0;
}

/* Detail layout */
.detail {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr);
  gap: 24px;
  align-items: flex-start;
}

.detail .photo {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  box-shadow: var(--shadow);
}

.detail .photo img {
  width: 100%;
  display: block;
  background: #0a0a0a;
}

.detail .info {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 18px;
  box-shadow: var(--shadow);
}

.detail .info h2 {
  margin: 0 0 4px;
  font-size: 18px;
}

.detail .info .where {
  color: var(--muted);
  font-size: 13px;
  margin-bottom: 16px;
}

@media (max-width: 800px) {
  .detail {
    grid-template-columns: 1fr;
  }
}

/* ── Detail v2 (locationscout-inspired layout) ────────────────────────── */
.detail-v2 {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(300px, 1fr);
  gap: 24px;
  align-items: flex-start;
}

@media (max-width: 900px) {
  .detail-v2 {
    grid-template-columns: 1fr;
  }
}

.detail-main {
  min-width: 0;
}

.title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 6px;
  flex-wrap: wrap;
}

.title-row h2 {
  margin: 0;
  font-size: 26px;
  letter-spacing: -0.01em;
  font-weight: 700;
}

/* Wrapper for spot detail's H2 + optional subtitle. Stays in the
   flex flow as a single column; flex-shrink: 1 lets long titles
   wrap before pushing header-actions off the row. */
.title-stack {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1 1 auto;
}

/* Smaller muted line under the H2 — only rendered when the spot has
   a photographer-supplied `title` and we demote the auto-derived
   "City, State" to a subtitle. */
.spot-subtitle {
  color: var(--muted);
  font-size: 14px;
  font-weight: 500;
}

.header-actions {
  display: flex;
  gap: 8px;
}

.header-actions button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  font-size: 13px;
}

.header-actions button svg {
  display: block;
}

.meta-row {
  display: flex;
  gap: 18px;
  flex-wrap: wrap;
  color: var(--muted);
  font-size: 13px;
  margin-bottom: 16px;
}

.meta-row span {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.meta-row a {
  color: var(--muted);
}

.meta-row a:hover {
  color: var(--link);
}

.photo-frame {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  box-shadow: var(--shadow);
  margin-bottom: 20px;
}

.photo-frame img {
  display: block;
  width: 100%;
  height: auto;
  background: #0a0a0a;
  cursor: zoom-in;
}

/* ── Lightbox overlay (click photo to view full) ────────────────── */
.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.92);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  cursor: zoom-out;
  padding: 32px;
  animation: lightbox-fade 120ms ease-out;
}

@keyframes lightbox-fade {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

.lightbox-img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  cursor: default;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}

.lightbox-close {
  position: absolute;
  top: 16px;
  right: 20px;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: 28px;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s;
}

.lightbox-close:hover {
  background: rgba(255, 255, 255, 0.25);
}

.text-section {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 18px 20px;
  box-shadow: var(--shadow);
  margin-bottom: 16px;
}

.text-section h3 {
  margin: 0 0 8px;
  font-size: 16px;
  font-weight: 600;
}

.text-body {
  white-space: pre-wrap;
  line-height: 1.55;
}

/* Empty state on the spot page when the id doesn't exist */
.empty-state {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 48px 32px;
  text-align: center;
  box-shadow: var(--shadow);
  max-width: 520px;
  margin: 32px auto;
}

.empty-state h2 {
  margin: 0 0 8px;
  font-size: 20px;
}

.empty-state p {
  margin: 6px 0;
  color: var(--muted);
}

.empty-link {
  display: inline-block;
  margin-top: 12px;
  font-weight: 500;
}

/* Sun-path map on the spot detail page */
/* "Sky" section header — H3 + the Sun/Moon/MilkyWay segmented toggle on
   one row. The toggle wraps below on narrow viewports so the H3 stays
   left-aligned with the rest of the page. Same flex pattern as
   .title-row above. */
.astro-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}

.astro-header h3 {
  margin: 0;
}

/* Segmented control — shared shape with the mobile gallery toggles
   (one rounded track holding equal-priority text segments, neutral
   active pill). Used here on every viewport since there are only
   three options and the labels stay short. */
.astro-toggle {
  display: inline-flex;
  flex-wrap: nowrap;
  gap: 0;
  padding: 3px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 9px;
}

.astro-toggle button {
  background: transparent;
  border: 0;
  color: var(--muted);
  padding: 6px 14px;
  font-size: 13px;
  font-weight: 500;
  border-radius: 6px;
  cursor: pointer;
  font: inherit;
  font-weight: 500;
  transition: background 0.15s ease, color 0.15s ease;
  white-space: nowrap;
}

.astro-toggle button:hover:not(.active) {
  color: var(--text);
}

.astro-toggle button.active {
  background: var(--panel);
  color: var(--text);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
}

/* Date/time picker row sits below the mode toggle. Calendar-icon +
   datetime input live inside one rounded chip so they read as a single
   control. The native datetime-local picker handles iOS's wheel UI for
   free; we only shape the visual frame.

   Important: a global rule above sets `width: 100%` on every
   datetime-local input (specificity 0,1,1). Our wrapper-scoped selector
   bumps to 0,2,0 to win and clip the input to its content width. */
.astro-when-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
  flex-wrap: wrap;
}

.astro-when-field {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: text;
}

.astro-when-field:focus-within {
  border-color: var(--accent);
}

.astro-when-icon {
  color: var(--muted);
  flex-shrink: 0;
}

.astro-when-row .astro-when-input {
  /* width auto + flex 0 makes the input collapse to its native content
     width (the rendered "MM/DD/YYYY, HH:MM AM/PM" string + dropdown). */
  width: auto;
  flex: 0 0 auto;
  background: transparent;
  border: 0;
  color: var(--text);
  padding: 0;
  font-size: 13px;
  font-family: inherit;
  /* color-scheme: dark tells WebKit to render the native picker chrome
     (calendar grid, AM/PM wheel) in dark colors so it doesn't flash a
     bright white panel inside the dark page. */
  color-scheme: dark;
}

.astro-when-row .astro-when-input:focus {
  outline: none;
}

.astro-now-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  padding: 0 14px;
  font-size: 13px;
  font-weight: 500;
  border-radius: 6px;
  cursor: pointer;
  /* Stretch to the chip's height so the two controls visually align —
     padding-driven height matched the chip on Chrome but not Safari
     (native datetime-local renders taller). align-self handles both. */
  align-self: stretch;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.astro-now-btn:hover {
  color: var(--text);
  border-color: var(--text);
}

.sun-map-section .sun-map-wrap {
  position: relative;
  height: 400px;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--border);
}

.sun-map-section .sun-map {
  width: 100%;
  height: 100%;
}

.sun-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
  margin-top: 10px;
  font-size: 12px;
  color: var(--muted);
}

.sun-legend .dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  margin-right: 6px;
  vertical-align: -1px;
}

/* Map / Satellite toggle (used in picker modal + sun-path map) */
.map-canvas,
.sun-map {
  position: relative;
}

.map-type-toggle {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 5;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
  padding: 3px;
  display: flex;
  gap: 2px;
}

.map-type-toggle button {
  background: transparent;
  color: var(--text);
  border: 0;
  padding: 5px 12px;
  font-size: 12px;
  font-weight: 500;
  border-radius: 6px;
  cursor: pointer;
}

.map-type-toggle button.active {
  background: var(--accent);
  color: #fff;
}

.map-type-toggle button:not(.active):hover {
  background: var(--panel-2);
}

.sun-map-error {
  padding: 20px;
  color: var(--bad);
  font-size: 13px;
}

.detail-side {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.info-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 6px 0;
  box-shadow: var(--shadow);
}

.info-card h4 {
  margin: 8px 16px 4px;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.info-card h4 svg {
  display: block;
}

/* Collapsible "All EXIF" card. The <summary> already gives us the
   click target; remove its default disclosure triangle look in favour
   of a small chevron via ::marker manipulation. */
.all-exif-details>summary {
  cursor: pointer;
  list-style: none;
  padding: 0 16px;
}

.all-exif-details>summary::-webkit-details-marker {
  display: none;
}

.all-exif-details>summary h4 {
  margin: 8px 0 4px;
}

.all-exif-details>summary::after {
  content: '▾';
  float: right;
  display: inline-block;
  /* lets transform: rotate behave predictably */
  margin-top: 4px;
  color: var(--muted);
  font-size: 22px;
  line-height: 1;
  transition: transform 0.15s ease;
}

.all-exif-details[open]>summary::after {
  transform: rotate(180deg);
}

.all-exif-details>.exif {
  /* Smaller text since the long list can be a wall otherwise */
  font-size: 12px;
  /* Wrap long values (lens descriptions, software strings) instead of overflowing */
  word-break: break-word;
}

.info-card .exif {
  padding: 4px 16px 12px;
  grid-template-columns: 110px 1fr;
}

/* Photographer info-card on the spot detail page. Sits at the top of
   the right-side aside above Sunset/Sunrise. The card has no h4 header
   — the icon + name IS the header. Social chips stack below like a
   tiny linktree. */
.photog-card .photog-name {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px 8px;
  font-size: 16px;
  font-weight: 600;
  color: var(--text);
  text-decoration: none;
}

.photog-card .photog-icon {
  color: var(--muted);
  display: inline-flex;
  align-items: center;
}

.photog-card a.photog-name:hover {
  color: var(--link);
  text-decoration: underline;
}

.photog-card a.photog-name:hover .photog-icon {
  color: var(--link);
}

.photog-socials {
  list-style: none;
  margin: 0;
  padding: 0 16px 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.photog-socials a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--muted);
  padding: 4px 0;
  text-decoration: none;
  word-break: break-all; /* long handles wrap rather than overflow */
}

.photog-socials a:hover {
  color: var(--link);
}

.photog-socials svg {
  flex: 0 0 auto;
  color: var(--muted);
}

.photog-socials a:hover svg {
  color: var(--link);
}

.info-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
}

.info-row:last-child {
  border-bottom: 0;
}

.info-icon {
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  margin-top: 1px;
}

.info-body {
  flex: 1;
  min-width: 0;
}

.info-label {
  font-size: 12px;
  color: var(--muted);
  margin-bottom: 1px;
}

.info-value {
  font-size: 14px;
}

.muted {
  color: var(--muted);
}

/* Two-column row (e.g. Sunset / Sunrise side-by-side) */
.info-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border-bottom: 1px solid var(--border);
}

.info-pair:last-child {
  border-bottom: 0;
}

.info-pair .cell {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 16px;
  min-width: 0;
}

.info-pair .cell+.cell {
  border-left: 1px solid var(--border);
}

@media (max-width: 480px) {
  .info-pair {
    grid-template-columns: 1fr;
  }

  .info-pair .cell+.cell {
    border-left: 0;
    border-top: 1px solid var(--border);
  }
}

/* ── Map picker modal (Leaflet + OSM) ─────────────────────────────────── */
.map-overlay {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: 24px;
}

.map-modal {
  background: var(--panel);
  border-radius: 12px;
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.4);
  width: 100%;
  max-width: 900px;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.map-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
}

.map-close {
  background: transparent;
  color: var(--muted);
  border: 0;
  font-size: 22px;
  cursor: pointer;
  padding: 0 6px;
  line-height: 1;
}

.map-close:hover {
  color: var(--text);
}

.map-search {
  position: relative;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  background: var(--panel-2);
}

.map-search-input {
  width: 100%;
  padding: 8px 12px;
  background: var(--panel);
}

.map-search-results {
  position: absolute;
  top: calc(100% - 1px);
  left: 14px;
  right: 14px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-top: 0;
  border-radius: 0 0 6px 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.50);
  max-height: 240px;
  overflow-y: auto;
  z-index: 5;
}

.map-search-item {
  padding: 8px 12px;
  cursor: pointer;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}

.map-search-item:last-child {
  border-bottom: 0;
}

.map-search-item:hover {
  background: var(--panel-2);
}

.map-search-empty {
  padding: 10px 12px;
  font-size: 12px;
  color: var(--muted);
}

.map-area {
  flex: 1;
  position: relative;
  min-height: 400px;
  display: flex;
}

.map-canvas {
  flex: 1;
  min-height: 400px;
  /* MapKit takes full ownership of this element — no other children. */
  position: relative;
}

.map-hint {
  padding: 8px 18px;
  font-size: 12px;
  color: var(--muted);
  background: var(--panel-2);
  border-top: 1px solid var(--border);
}

.map-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 18px;
  border-top: 1px solid var(--border);
  flex-wrap: wrap;
}

.map-coords {
  display: flex;
  gap: 16px;
  font-size: 13px;
}

.map-coords code {
  background: var(--panel-2);
  padding: 2px 6px;
  border-radius: 4px;
  border: 1px solid var(--border);
}

.map-actions {
  display: flex;
  gap: 8px;
}

/* Edit layout on the spot page — photo stays visible above the form. */
.edit-layout {
  max-width: 900px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.edit-layout .title-row {
  margin-bottom: 0;
}

.edit-photo {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  box-shadow: var(--shadow);
  /* cap photo height in edit mode so the form stays in view */
  max-height: 50vh;
  display: flex;
  justify-content: center;
}

.edit-photo img {
  display: block;
  max-width: 100%;
  max-height: 50vh;
  object-fit: contain;
  background: #0a0a0a;
}

.photo-caption {
  text-align: center;
  color: var(--muted);
  font-size: 13px;
  margin-top: -8px;
}

/* Replace-photo controls live between the photo and the edit form. The
   button row swaps to the confirm bar (with a GPS checkbox + Replace /
   Cancel) once a file is picked. */
.replace-photo-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 4px;
}

.replace-photo-confirm {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 4px;
  padding: 12px 14px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
}

.replace-gps-label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text);
  margin: 0;
  cursor: pointer;
}

.replace-gps-label input[type="checkbox"] {
  /* override the global `width: 100%` rule that would otherwise stretch
     the checkbox across the row */
  width: auto;
  margin: 0;
}

.replace-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  flex-wrap: wrap;
}

/* Edit form on the spot page */
.edit-form {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 20px;
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.edit-form label {
  margin-top: 10px;
}

.edit-form .two-col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}

.edit-form .actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  margin-top: 16px;
}

/* Upload page */
.upload-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(320px, 480px);
  gap: 24px;
  align-items: flex-start;
}

@media (max-width: 800px) {
  .upload-layout {
    grid-template-columns: 1fr;
  }
}

.preview {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px;
  box-shadow: var(--shadow);
}

.preview .drop {
  aspect-ratio: 4 / 3;
  background: var(--panel-2);
  border: 1px dashed var(--border);
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  margin-bottom: 0;
  overflow: hidden;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}

.preview .drop:hover {
  border-color: var(--accent);
}

/* Set when the user is mid-drag over the zone. JS toggles this class on
   dragenter/dragleave so the highlight survives nested children that
   would otherwise fire stray dragleave events. */
.preview .drop.is-dragover {
  border-color: var(--accent);
  background: rgba(246, 130, 31, 0.10);
}

.preview .drop img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

.fields {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 18px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px 14px;
  box-shadow: var(--shadow);
}

.fields .full {
  grid-column: 1 / -1;
}

.field-hint {
  font-size: 11px;
  color: var(--muted);
  margin-top: 2px;
}

.field-hint.auto {
  color: var(--good);
}

.field-hint.manual {
  color: var(--warn);
}

.actions {
  grid-column: 1 / -1;
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  margin-top: 8px;
}

/* ── Header user widget ─────────────────────────────────────────── */

.header-signin {
  font-weight: 500;
  color: var(--accent);
  padding: 6px 14px;
  border-radius: 999px;
  border: 1px solid var(--accent);
}

.header-signin:hover {
  background: var(--accent);
  color: #fff;
  text-decoration: none;
}

.user-menu {
  position: relative;
}

.user-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px 4px 4px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
  color: var(--text);
  font: inherit;
}

.user-chip:hover {
  border-color: var(--accent);
}

.user-avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  object-fit: cover;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.user-avatar-fallback {
  background: var(--accent);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.5px;
}

.user-name {
  font-size: 13px;
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.user-menu-pop {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--shadow);
  padding: 8px;
  min-width: 200px;
  z-index: 10;
}

.user-menu-email {
  font-size: 12px;
  color: var(--muted);
  padding: 6px 10px 8px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 6px;
  word-break: break-all;
}

.user-menu-pop button {
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  padding: 8px 10px;
  border-radius: 6px;
  cursor: pointer;
  font: inherit;
  color: var(--text);
}

.user-menu-pop button:hover {
  background: var(--panel-2);
  color: var(--bad);
}

/* ── Login / sign-up page ───────────────────────────────────────── */
.auth-main {
  display: flex;
  justify-content: center;
  padding: 60px 20px;
}

.auth-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow);
  padding: 36px 32px;
  width: 100%;
  max-width: 400px;
}

.auth-card h2 {
  margin: 0 0 4px;
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.auth-sub {
  color: var(--muted);
  margin: 0 0 24px;
  font-size: 14px;
}

.sso {
  width: 100%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 12px 16px;
  border-radius: 10px;
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--text);
  transition: border-color 0.15s, background 0.15s;
}

.sso:hover {
  border-color: #52525b;
  background: var(--panel-2);
}

.sso svg {
  flex: 0 0 auto;
}

.auth-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--muted);
  font-size: 12px;
  margin: 18px 0;
}

.auth-divider::before,
.auth-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border);
}

.auth-divider span {
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.auth-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.auth-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.auth-field label {
  font-size: 12px;
  color: var(--muted);
  font-weight: 500;
  letter-spacing: 0.02em;
}

.auth-field input {
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel-2);
  font: inherit;
  color: var(--text);
}

.auth-field input:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--panel);
  box-shadow: 0 0 0 3px rgba(246, 130, 31, 0.15);
}

.auth-error {
  font-size: 13px;
  color: var(--bad);
  background: rgba(239, 68, 68, 0.10);
  border: 1px solid rgba(239, 68, 68, 0.35);
  border-radius: 8px;
  padding: 8px 12px;
}

.auth-submit {
  margin-top: 6px;
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: 999px;
  padding: 12px 18px;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s;
}

.auth-submit:hover {
  background: var(--accent-2);
}

.auth-submit:disabled {
  opacity: 0.6;
  cursor: progress;
}

.auth-toggle {
  text-align: center;
  margin: 18px 0 0;
  font-size: 13px;
  color: var(--muted);
}

.auth-toggle a {
  color: var(--link);
  font-weight: 500;
}

/* :not([hidden]) so the `hidden` attribute actually wins — otherwise
   display:flex overrides it and the verify box shows on the sign-in form. */
.auth-verify:not([hidden]) {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 8px;
}

.auth-verify h3 {
  margin: 0;
}

.auth-verify input {
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel-2);
  font: 18px ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.4em;
  text-align: center;
}

.auth-verify input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(246, 130, 31, 0.15);
}

/* ── Tile author link (clickable photographer name on gallery tiles) ──
   Rendered as <span> not <a> because HTML forbids nested anchors;
   index.html attaches a delegated click handler. */
.tile-author {
  color: inherit;
  cursor: pointer;
  border-bottom: 1px dotted rgba(255, 255, 255, 0.5);
}

.tile-author:hover {
  color: #fff;
  border-bottom-color: #fff;
}

/* ── User menu "View profile" link ──────────────────────────────── */
.user-menu-link {
  display: block;
  padding: 8px 10px;
  border-radius: 6px;
  font-size: 13px;
  color: var(--text);
  text-decoration: none;
}

.user-menu-link:hover {
  background: var(--panel-2);
  text-decoration: none;
}

/* ── Profile page ───────────────────────────────────────────────── */
.profile-header {
  display: flex;
  gap: 28px;
  align-items: flex-start;
  padding: 32px 24px 24px;
  max-width: 1100px;
  margin: 0 auto;
}

.profile-avatar {
  width: 120px;
  height: 120px;
  border-radius: 50%;
  object-fit: cover;
  flex: 0 0 auto;
  border: 3px solid var(--panel);
  box-shadow: var(--shadow);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.profile-avatar-fallback {
  background: var(--accent);
  color: #fff;
  font-size: 44px;
  font-weight: 600;
}

.profile-meta {
  flex: 1;
  min-width: 0;
}

.profile-name {
  margin: 0 0 4px;
  font-size: 28px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.profile-handle {
  color: var(--muted);
  font-size: 14px;
  margin-bottom: 12px;
}

.profile-bio {
  margin: 8px 0 12px;
  font-size: 15px;
  line-height: 1.5;
  color: var(--text);
  max-width: 640px;
  white-space: pre-wrap;
}

.profile-websites {
  margin-bottom: 12px;
  font-size: 13px;
}

.profile-websites a {
  color: var(--link);
}

.profile-stats {
  display: flex;
  gap: 8px;
  font-size: 14px;
  color: var(--text);
  margin-bottom: 12px;
}

.profile-stats strong {
  font-weight: 600;
}

.profile-actions {
  margin-top: 4px;
}

.profile-photos {
  padding: 0 24px 32px;
  max-width: 1400px;
  margin: 0 auto;
}

.profile-photos h3 {
  margin: 16px 0 12px;
  font-size: 18px;
  font-weight: 600;
  color: var(--text);
}

.empty-state-mini {
  padding: 32px;
  text-align: center;
  color: var(--muted);
  font-size: 14px;
  border: 1px dashed var(--border);
  border-radius: 12px;
}

/* ── Profile edit form ──────────────────────────────────────────── */
.profile-edit {
  max-width: 600px;
  margin: 32px auto;
  padding: 32px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow);
}

.profile-edit h2 {
  margin: 0 0 20px;
  font-size: 22px;
}

.profile-form {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.profile-form label {
  font-size: 12px;
  color: var(--muted);
  font-weight: 500;
  margin-top: 12px;
}

.profile-form input,
.profile-form textarea {
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel-2);
  font: inherit;
  color: var(--text);
  /* Fill the parent cell. Without this, inputs render at their default
     intrinsic width (~200px) and a 3-col grid on a 600px form pushes
     the third input past the panel's right edge. */
  width: 100%;
}

/* Grid cells default to min-width: auto (content-driven). The input's
   ~200px intrinsic min keeps the 1fr columns from actually being 1fr.
   Allow the cells to shrink. */
.profile-form .two-col > div,
.profile-form .three-col > div {
  min-width: 0;
}

.profile-form textarea {
  resize: vertical;
  min-height: 80px;
}

.profile-form input:focus,
.profile-form textarea:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--panel);
  box-shadow: 0 0 0 3px rgba(246, 130, 31, 0.15);
}

.profile-form .two-col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

@media (max-width: 600px) {
  .profile-header {
    flex-direction: column;
    align-items: center;
    text-align: center;
  }

  .profile-bio {
    text-align: left;
  }
}

/* ── /map.html — full-bleed map with all spots ─────────────────── */
/* The map page makes <main> a flex region that fills the rest of the
   viewport below the header. Scoped via :has() so the global "main has
   24px padding + 1400px max-width" rule used elsewhere is overridden
   only on this page (no body-class plumbing needed). */
body:has(.map-page-main) {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
  margin: 0;
}

.map-page-main {
  padding: 0;
  max-width: none;
  margin: 0;
  flex: 1;
  position: relative;
  min-height: 0;
  /* lets the flex child shrink below content height */
}

.map-page {
  position: absolute;
  inset: 0;
  background: var(--panel-2);
}

/* Loading / empty / error banner that floats over the map area while
   spots are being fetched and the map initialises. */
.map-status {
  position: absolute;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 8px 18px;
  color: var(--muted);
  font-size: 13px;
  box-shadow: var(--shadow);
}

.map-status.error {
  color: var(--bad);
}

.map-status.empty {
  color: var(--text);
}

.map-status[hidden] {
  display: none;
}

/* Brand-icon pin used as the custom annotation on /map. The image is
   already a map-pin shape; the drop-shadow lifts it off the map tiles
   and the hover transform gives a tiny tactile feel. */
.map-brand-pin {
  display: block;
  filter: drop-shadow(0 2px 4px rgba(15, 23, 42, 0.35));
  cursor: pointer;
  transform-origin: 50% 100%;
  transition: transform 0.12s ease, filter 0.12s ease;
}

.map-brand-pin:hover {
  transform: scale(1.12);
  filter: drop-shadow(0 4px 6px rgba(15, 23, 42, 0.4));
}

/* Custom MapKit callout body. MapKit renders the bubble + arrow chrome;
   we provide just the inner content. The whole thing is wrapped in an
   anchor so the entire card click-targets the spot detail page. */
.map-callout {
  width: 240px;
}

.map-callout-link {
  display: block;
  color: inherit;
  text-decoration: none;
}

.map-callout-link:hover {
  text-decoration: none;
}

.map-callout-thumb {
  display: block;
  width: 100%;
  height: 140px;
  object-fit: cover;
  background: var(--panel-2);
}

.map-callout-body {
  padding: 10px 12px 12px;
}

.map-callout-title {
  font-weight: 600;
  font-size: 14px;
  color: var(--text);
  line-height: 1.25;
}

.map-callout-author {
  margin-top: 2px;
  color: var(--muted);
  font-size: 12px;
}

.map-callout-more {
  margin-top: 8px;
  color: var(--link);
  font-size: 13px;
  font-weight: 500;
}

.map-callout-link:hover .map-callout-more {
  text-decoration: underline;
}

/* ── /admin.html — admin user table ────────────────────────────── */
.admin-section {
  max-width: 900px;
  margin: 0 auto;
}

.admin-section h2 {
  margin: 0 0 4px;
  font-size: 22px;
  letter-spacing: -0.01em;
}

.admin-sub {
  color: var(--muted);
  margin: 0 0 20px;
  font-size: 14px;
}

.table-wrap {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
  box-shadow: var(--shadow);
}

.admin-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}

.admin-table thead th {
  text-align: left;
  background: var(--panel-2);
  color: var(--muted);
  font-weight: 600;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
}

.admin-table tbody td {
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  color: var(--text);
}

.admin-table tbody tr:last-child td {
  border-bottom: none;
}

.admin-table tbody tr:hover {
  background: var(--panel-2);
}

.admin-table .muted {
  color: var(--muted);
}

.admin-hint {
  margin-top: 16px;
  color: var(--muted);
  font-size: 13px;
}

.admin-hint code {
  background: var(--panel-2);
  padding: 2px 6px;
  border-radius: 4px;
  border: 1px solid var(--border);
}

@media (max-width: 600px) {
  .admin-table {
    font-size: 13px;
  }

  .admin-table thead th,
  .admin-table tbody td {
    padding: 10px 12px;
  }
}

/* Admin tools — tiles linking to individual admin features. */
.admin-section+.admin-section {
  margin-top: 32px;
}

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

.admin-tool {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 16px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  text-decoration: none;
  color: inherit;
  box-shadow: var(--shadow);
  transition: transform 0.08s ease, border-color 0.15s ease;
}

.admin-tool:hover {
  text-decoration: none;
  border-color: var(--accent);
  transform: translateY(-1px);
}

.admin-tool-icon {
  width: 40px;
  height: 40px;
  border-radius: 10px;
  background: rgba(246, 130, 31, 0.12);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
}

.admin-tool-title {
  font-weight: 600;
  color: var(--text);
}

.admin-tool-sub {
  color: var(--muted);
  font-size: 13px;
  margin-top: 2px;
}

/* ── /admin-bulk-upload.html — multi-file upload queue ─────────── */
.bulk-section {
  max-width: 900px;
  margin: 0 auto;
}

.bulk-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}

.bulk-heading h2 {
  margin: 0;
  font-size: 22px;
  letter-spacing: -0.01em;
}

.bulk-back {
  color: var(--muted);
  font-size: 13px;
}

.bulk-sub {
  color: var(--muted);
  margin: 8px 0 20px;
  font-size: 14px;
  line-height: 1.55;
}

.bulk-pick {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 24px;
  background: var(--panel-2);
  border: 2px dashed var(--border);
  border-radius: 12px;
  margin-bottom: 16px;
}

.bulk-pick-btn {
  background: var(--accent);
  color: white;
  padding: 10px 18px;
  border-radius: 8px;
  font-weight: 600;
  cursor: pointer;
  user-select: none;
}

.bulk-pick-btn:hover {
  background: var(--accent-2);
}

.bulk-pick-hint {
  color: var(--muted);
  font-size: 13px;
}

.bulk-queue {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
  box-shadow: var(--shadow);
}

.bulk-row {
  display: grid;
  grid-template-columns: 56px 1fr auto;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
}

.bulk-row:last-child {
  border-bottom: none;
}

.bulk-thumb {
  width: 56px;
  height: 56px;
  object-fit: cover;
  border-radius: 6px;
  background: var(--panel-2);
}

.bulk-row-name {
  font-weight: 500;
  color: var(--text);
  font-size: 14px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bulk-row-meta {
  color: var(--muted);
  font-size: 12px;
  margin-top: 2px;
}

.bulk-meta-loading {
  font-style: italic;
  color: var(--muted);
}

.bulk-meta-warn {
  color: var(--warn);
}

.bulk-badge {
  display: inline-block;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--panel-2);
  color: var(--muted);
  border: 1px solid var(--border);
  white-space: nowrap;
}

.bulk-badge-ok {
  color: var(--good);
  border-color: rgba(21, 128, 61, 0.3);
  background: rgba(21, 128, 61, 0.06);
}

.bulk-badge-warn {
  color: var(--warn);
  border-color: rgba(217, 119, 6, 0.3);
  background: rgba(217, 119, 6, 0.06);
}

.bulk-badge-busy {
  color: var(--link);
  border-color: rgba(37, 99, 235, 0.3);
  background: rgba(37, 99, 235, 0.06);
}

.bulk-badge-done {
  color: var(--good);
  border-color: rgba(21, 128, 61, 0.3);
  background: rgba(21, 128, 61, 0.08);
  text-decoration: none;
}

.bulk-badge-done:hover {
  text-decoration: underline;
}

.bulk-badge-err {
  color: var(--bad);
  border-color: rgba(220, 38, 38, 0.3);
  background: rgba(220, 38, 38, 0.06);
}

.bulk-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 16px;
  flex-wrap: wrap;
}

.bulk-actions button {
  background: var(--accent);
  color: white;
  border: none;
  padding: 10px 18px;
  border-radius: 8px;
  font-weight: 600;
  cursor: pointer;
}

.bulk-actions button:hover:not(:disabled) {
  background: var(--accent-2);
}

.bulk-actions button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.bulk-actions button.secondary {
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--border);
}

.bulk-actions button.secondary:hover:not(:disabled) {
  background: var(--panel-2);
}

.bulk-summary {
  color: var(--muted);
  font-size: 13px;
  margin-left: auto;
}

.bulk-summary-bad {
  color: var(--bad);
}

.bulk-summary-warn {
  color: var(--warn);
}

@media (max-width: 600px) {
  .bulk-row {
    grid-template-columns: 48px 1fr;
    gap: 10px;
    padding: 10px 12px;
  }

  .bulk-thumb {
    width: 48px;
    height: 48px;
  }

  .bulk-row-status {
    grid-column: 1 / -1;
    padding-left: 58px;
  }

  .bulk-summary {
    margin-left: 0;
    width: 100%;
  }
}

/* ── Profile enrichment (migration 0014) ─────────────────────────────
   Cover banner sits flush with the top of <main>; the profile-header
   below it is pulled up so the avatar half-overlaps the cover edge,
   like Twitter/Instagram. Cover is full-bleed inside main's max-width
   container so it reads as a "this person's space." */
.profile-cover {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 1;
  max-height: 320px;
  background: var(--panel-2);
  overflow: hidden;
  border-radius: 14px;
  margin-bottom: -56px; /* lets the avatar pull up over the bottom edge */
}

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

/* No-cover gradient — soft brand-blue → panel fade so empty profiles
   still look intentional rather than missing-image. */
.profile-cover-blank {
  background:
    linear-gradient(135deg, rgba(96, 165, 250, 0.20) 0%, var(--panel-2) 70%);
}

.profile-header-with-cover {
  padding-top: 0; /* avatar hangs over the cover */
  padding-left: 24px;
  padding-right: 24px;
  position: relative;
}

.profile-header-with-cover .profile-avatar {
  margin-top: 0;
  width: 112px;
  height: 112px;
}

.profile-header-with-cover .profile-meta {
  padding-top: 64px; /* push name below the avatar's overhang */
}

.profile-tagline {
  font-size: 16px;
  color: var(--text);
  margin: 4px 0 8px;
  font-weight: 500;
}

.profile-countries {
  font-size: 13px;
  margin-top: -6px;
  margin-bottom: 12px;
}

/* Social icons row — sits between websites and stats. Subtle border
   on each so they read as buttons, not decoration. */
.profile-socials {
  display: inline-flex;
  gap: 8px;
  margin: 4px 0 12px;
}

.social-icon {
  width: 34px;
  height: 34px;
  border-radius: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  color: var(--muted);
  background: var(--panel-2);
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}

.social-icon:hover {
  color: var(--link);
  border-color: var(--link);
  background: var(--panel);
  text-decoration: none;
}

/* Generic profile sub-card (used by the Gear list, easy to reuse). */
.profile-card {
  max-width: 1100px;
  margin: 8px auto 24px;
  padding: 20px 24px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
}

.profile-card h3 {
  margin: 0 0 12px;
  font-size: 16px;
  font-weight: 600;
  color: var(--text);
}

.profile-gear ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.profile-gear li {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 6px 12px;
  font-size: 13px;
  color: var(--text);
}

.profile-photos-hint {
  margin: 0 0 12px;
  font-size: 13px;
}

/* Three-col grid on the edit form for Instagram/YouTube/X. Stacks on mobile. */
.profile-form .three-col {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 12px;
}

@media (max-width: 600px) {
  .profile-form .three-col {
    grid-template-columns: 1fr;
  }
  .profile-cover {
    margin-bottom: -36px;
    border-radius: 10px;
    aspect-ratio: 3 / 1;
  }
  .profile-header-with-cover .profile-avatar {
    width: 88px;
    height: 88px;
  }
  .profile-header-with-cover .profile-meta {
    padding-top: 48px;
  }
}

/* Tile wrap — used on user.html so each photo gets a "Set as cover"
   button overlaid in the corner without breaking the gallery layout.
   The wrap becomes the flex item; .tile inside fills it. */
.tile-wrap {
  position: relative;
  height: var(--row-height);
  flex: 1 1 var(--row-height);
}

.tile-wrap > .tile {
  width: 100%;
  height: 100%;
  display: block;
  flex: none; /* the wrap owns flex sizing, not the inner tile */
}

/* Set-as-cover button — top-right corner of the tile. Hidden until
   hover except on the current cover (so the photographer always sees
   which one they picked). On touch devices we keep it visible because
   there's no hover signal. */
.set-cover-btn {
  position: absolute;
  top: 8px;
  right: 8px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 5px 9px;
  font-size: 11px;
  font-weight: 600;
  color: #fff;
  background: rgba(0, 0, 0, 0.62);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 999px;
  cursor: pointer;
  z-index: 2;
  opacity: 0;
  transition: opacity 0.15s ease, background 0.15s, border-color 0.15s;
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}

.tile-wrap:hover .set-cover-btn,
.set-cover-btn.is-cover,
.set-cover-btn:focus-visible {
  opacity: 1;
}

.set-cover-btn:hover {
  background: rgba(0, 0, 0, 0.78);
  border-color: rgba(255, 255, 255, 0.32);
}

/* "This is your cover" state — solid yellow star on a tinted pill so
   it's the most visible thing on the tile. */
.set-cover-btn.is-cover {
  background: rgba(246, 195, 31, 0.92);
  color: #1a1a1a;
  border-color: rgba(0, 0, 0, 0.18);
}

.set-cover-btn:disabled {
  opacity: 0.55;
  cursor: progress;
}

@media (hover: none) {
  /* Always-visible on touch — hover targeting is unreliable. */
  .tile-wrap .set-cover-btn { opacity: 1; }
}

/* ── Photographer directory (/photographers.html) ──────────────────── */
.photographer-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 16px;
  max-width: 1400px;
  margin: 24px auto 0;
}

.photographer-card {
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
  text-decoration: none;
  color: var(--text);
  transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}

.photographer-card:hover {
  transform: translateY(-2px);
  border-color: var(--link);
  text-decoration: none;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
}

.photographer-cover {
  width: 100%;
  aspect-ratio: 16 / 9;
  background: var(--panel-2);
  position: relative;
  overflow: hidden;
}

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

.photographer-cover-blank {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 36px;
  font-weight: 700;
  color: var(--muted);
  background: linear-gradient(135deg, rgba(96, 165, 250, 0.16) 0%, var(--panel-2) 70%);
}

.photographer-card-body {
  padding: 14px 16px 16px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.photographer-name {
  font-size: 16px;
  font-weight: 600;
  color: var(--text);
}

.photographer-handle {
  font-size: 13px;
  color: var(--muted);
}

.photographer-tagline {
  font-size: 13px;
  color: var(--text);
  margin-top: 4px;
  /* Clamp to two lines so cards stay aligned. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.photographer-stats {
  margin-top: 8px;
  font-size: 12px;
  color: var(--muted);
}

.photographer-stats strong {
  color: var(--text);
}

@media (max-width: 600px) {
  .photographer-grid {
    grid-template-columns: 1fr;
    gap: 12px;
  }
}

/* ── Personal photos strip on /user (read mode) ─────────────────────
   Sits between the Gear card and the photo grid. Hidden via [hidden]
   when the photographer hasn't uploaded any. Horizontal scroll on
   narrow viewports so we don't reflow on every breakpoint. */
.profile-personal h3 {
  margin: 0 0 12px;
  font-size: 16px;
  font-weight: 600;
  color: var(--text);
}

.personal-strip {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding-bottom: 4px;
}

.personal-tile {
  flex: 0 0 auto;
  width: 180px;
  height: 180px;
  border-radius: 10px;
  overflow: hidden;
  background: var(--panel-2);
  scroll-snap-align: start;
  display: block;
}

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

@media (max-width: 600px) {
  .personal-tile { width: 140px; height: 140px; }
}

/* ── Edit form: avatar uploader + personal photos manager ───────────
   Sits inside .profile-edit. Avatar block is row-flex (preview left,
   actions right); personal photos is a small grid with a delete badge
   on each tile. */
.avatar-edit {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 0 20px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 20px;
}

.avatar-edit-preview {
  flex: 0 0 auto;
  width: 96px;
  height: 96px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--panel-2);
  border: 1px solid var(--border);
}

.avatar-edit-preview img,
.avatar-edit-fallback {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: flex;
  align-items: center;
  justify-content: center;
}

.avatar-edit-fallback {
  background: var(--accent);
  color: #fff;
  font-size: 32px;
  font-weight: 600;
}

.avatar-edit-actions {
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: flex-start;
}

.avatar-edit-actions .secondary {
  /* Tighter than the form Save button — these are inline maintenance actions. */
  padding: 6px 12px;
  font-size: 13px;
}

.personal-edit {
  margin-top: 24px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

.personal-edit h3 {
  margin: 0 0 4px;
  font-size: 16px;
  font-weight: 600;
}

.personal-edit-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 8px;
  margin: 12px 0;
}

.personal-edit-tile {
  position: relative;
  aspect-ratio: 1;
  border-radius: 8px;
  overflow: hidden;
  background: var(--panel-2);
}

.personal-edit-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.personal-edit-del {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.62);
  color: #fff;
  border: none;
  font-size: 18px;
  line-height: 24px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}

.personal-edit-del:hover {
  background: rgba(220, 38, 38, 0.92);
}

.personal-edit-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 4px;
}