@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,700;1,400&family=Tangerine:wght@700&display=swap');

:root {
  --accent: #8a2432;
  --ink: #0a2a6b;
  --paper: #ffffff;
  --shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: 'Playfair Display', Georgia, serif;
  color: var(--ink);
  background: #fff;
}

/* .stage (the full-bleed photo slideshow on index.php) keeps its own black
   backdrop deliberately — it's a photo-viewing lightbox, not page content,
   and its caption/counter/controls already use explicit white text on a
   dark overlay that doesn't depend on the page background at all. */

a { color: var(--accent); }

/* ---------- Full-bleed slideshow (index.php) ---------- */

.stage {
  position: fixed;
  inset: 0;
  overflow: hidden;
  background: #000;
}

.stage-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 1.8s ease;
  will-change: transform, opacity;
}

/* The currently-shown layer — see assets/app.js's renderSlide(), which
   toggles this between the two <img id="stage-image-a/-b"> on every
   photo change. Above the fading-out layer so the dissolve looks like
   one photo settling in over the other, not the reverse. */
.stage-image.active { opacity: 1; z-index: 1; }

/* A slow, subtle zoom for as long as a photo is shown (10s, matching
   assets/app.js's slideshow interval) — "about 5%" per the request, so
   barely perceptible moment-to-moment but still gives the slideshow some
   life. transform-origin (set per photo in JS, picked from a small fixed
   set of corners/center) is what makes it read as a *pan* and not just a
   zoom — scaling toward an off-center point shifts the visible framing
   as it grows. Re-triggered fresh for every photo by toggling this class
   off and back on with a forced reflow in between (renderSlide()) —
   CSS animations don't restart on their own just because the element's
   other properties (src) changed. */
.stage-image.ken-burns { animation: stage-ken-burns 10s linear forwards; }

@keyframes stage-ken-burns {
  from { transform: scale(1); }
  to { transform: scale(1.05); }
}

/* Continuous zoom is exactly the kind of motion prefers-reduced-motion
   exists for — keep the plain crossfade (a one-time opacity change,
   not sustained motion) but skip the pan/zoom for anyone who's asked
   for less of it. */
@media (prefers-reduced-motion: reduce) {
  .stage-image.ken-burns { animation: none; }
}

.stage-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #eee;
  font-size: 1.5rem;
  text-align: center;
  padding: 2rem;
}

.brand-box {
  position: fixed;
  top: 20px;
  left: 20px;
  /* Leave room for .top-right-stack (search + admin utility box), which is
     independently fixed at top:20px;right:20px — without this cap, the
     wrapped nav dropdowns grow wide enough to run underneath it and become
     unclickable at common viewport widths. */
  max-width: calc(100vw - 340px);
  z-index: 10;
  background: var(--paper);
  box-shadow: var(--shadow);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
}

.brand-title {
  font-family: 'Tangerine', 'Brush Script MT', cursive;
  font-size: 2.6rem;
  line-height: 1;
  padding: 0.25rem 1rem;
  white-space: nowrap;
}

/* nav.php's brand-title is a link wrapping the logo image (the other
   .brand-title elements, in each page's .site-header, stay plain text) —
   this scopes the image-link layout to just that one. */
.brand-box .brand-title {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

.brand-logo {
  display: block;
  height: 2.4rem;
  width: auto;
}

.brand-nav {
  display: flex;
  flex-wrap: wrap;
  border-left: 1px solid #ddd;
}

.brand-nav-link {
  appearance: none;
  border: none;
  border-right: 1px solid #ddd;
  background: transparent;
  font-family: 'Playfair Display', Georgia, serif;
  font-variant: small-caps;
  letter-spacing: 0.05em;
  font-size: 0.95rem;
  padding: 0.9rem 1rem;
  cursor: pointer;
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

.brand-nav-link:hover { background: #faf6f6; }

/* The last item's own trailing border-right is removed — reaching one
   level into a flyout-trigger wrapper div too (Gallery/Texas/Vermont/
   Parks/Travel/Links are each a div wrapping their own .brand-nav-link,
   not a bare link directly under .brand-nav), since only the inner link
   actually carries the border. */
.brand-nav > *:last-child,
.brand-nav > *:last-child > .brand-nav-link { border-right: none; }

/* "Gallery" cascading flyout menu (Region -> Country -> State -> County ->
   Location) and the "Vermont" menu (All Vermont Photos / Covered Bridges /
   Waterfalls -> landmark list) both share this flyout mechanism: each level's <ul> nested
   inside its parent <li> (or the top-level trigger div) and positioned to
   the right of it, opened by hover (desktop) or a click (touch/keyboard
   fallback, toggled via the .open class in site-nav.js). */
.gallery-menu, .flyout-trigger { position: relative; }

.flyout-menu {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 20;
  display: none;
  list-style: none;
  margin: 0;
  padding: 0;
  min-width: 220px;
  background: var(--paper);
  box-shadow: var(--shadow);
}

.gallery-menu:hover > .flyout-menu,
.gallery-menu.open > .flyout-menu,
.flyout-trigger:hover > .flyout-menu,
.flyout-trigger.open > .flyout-menu {
  display: block;
}

.flyout-menu li { position: relative; }

.flyout-menu li > a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 0.6rem 1rem;
  font-family: 'Playfair Display', Georgia, serif;
  font-variant: small-caps;
  letter-spacing: 0.05em;
  font-size: 0.9rem;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}

.flyout-menu li.has-children > a::after {
  content: '\00bb';
  color: #999;
}

.flyout-menu li:hover > a,
.flyout-menu li.open > a {
  background: #dbeafe;
}

.flyout-menu li > .flyout-menu {
  top: -1px;
  left: 100%;
}

.flyout-menu li:hover > .flyout-menu,
.flyout-menu li.open > .flyout-menu {
  display: block;
}

.top-right-stack {
  position: fixed;
  top: 20px;
  right: 20px;
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 12px;
}

.search-box {
  background: var(--paper);
  box-shadow: var(--shadow);
  padding: 0.75rem;
}

.search-box form {
  display: flex;
  gap: 0.5rem;
}

.search-box input[type="search"] {
  font-family: Georgia, serif;
  font-size: 0.9rem;
  padding: 0.4rem 0.6rem;
  border: 1px solid #ccc;
  border-radius: 4px;
  width: 200px;
}

.search-box button {
  font-family: 'Playfair Display', Georgia, serif;
  font-variant: small-caps;
  letter-spacing: 0.05em;
  background: var(--ink);
  color: #fff;
  border: none;
  border-radius: 4px;
  padding: 0.4rem 0.9rem;
  cursor: pointer;
}

/* Admin dashboard's four panes (admin/admin.php) — plain 4-item grid
   auto-placement (left-to-right, then top-to-bottom) puts them at
   row1col1/row1col2/row2col1/row2col2 in source order, no explicit
   grid-area needed. Default align-items: stretch matches each row's two
   panes to the taller one's height (the Upload/Edit Photos/Banned IPs
   panes are much shorter than Manage's 12-item one) — reads as a clean
   symmetric 2x2 grid rather than four boxes that happen to differ wildly
   in height. */
.admin-panes {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
  margin-bottom: 1.5rem;
}

.utility-box {
  background: var(--paper);
  box-shadow: var(--shadow);
  padding: 1.5rem 1.75rem 1.5rem 3rem;
  margin: 0;
  text-align: left;
  font-size: 1.2rem; /* was 1.53rem — sized down further once the dashboard split into four panes */
  list-style: disc outside;
}

.utility-box li { margin-bottom: 0.5rem; }
.utility-box li:last-child { margin-bottom: 0; }

.utility-box a {
  font-variant: small-caps;
  letter-spacing: 0.05em;
  text-decoration: none;
  font-weight: 600;
}

.utility-note {
  margin: 0.35rem 0 0;
  color: #777;
  font-size: 0.75rem;
}

.nav-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 10;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.2);
  border: none;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  font-size: 1.1rem;
  color: #333;
  text-decoration: none;
  cursor: pointer;
  box-shadow: var(--shadow);
  transition: background 0.15s ease;
}

.nav-arrow--prev { left: 20px; }
.nav-arrow--next { right: 20px; }
.nav-arrow:hover:not(:disabled):not(.nav-arrow--disabled) { background: rgba(255, 255, 255, 0.85); }

/* :disabled for index.php's <button> arrows; .nav-arrow--disabled for
   view_photo.php's server-rendered <span> arrows (an <a> without an href
   can't be truly disabled, so a non-link element stands in at the ends of
   a location's photo sequence). */
.nav-arrow:disabled,
.nav-arrow--disabled { opacity: 0.3; cursor: default; }

.counter {
  position: absolute;
  left: 20px;
  bottom: 16px;
  z-index: 10;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
  font-size: 0.95rem;
}

.caption-bar {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  z-index: 10;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
  font-size: 1.4rem;
  font-weight: 700;
  text-align: center;
  max-width: 80vw;
}


.flash {
  position: fixed;
  top: 20px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  background: #e6ffed;
  border: 1px solid #34c759;
  padding: 0.5rem 1rem;
  border-radius: 4px;
}

.flash--error {
  position: static;
  transform: none;
  display: inline-block;
  background: #ffebe9;
  border-color: #d1242f;
  color: #82071e;
}

.login-form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  max-width: 320px;
}

/* The 6-digit code step's live countdown (admin/login.php) — filled in
   and updated entirely by its own inline script; empty (and takes no
   layout space beyond its own line) until then. */
.mfa-countdown {
  margin: 0 0 0.75rem;
  font-family: 'Playfair Display', Georgia, serif;
  font-weight: 600;
  color: var(--accent);
}

.login-form label {
  font-variant: small-caps;
  letter-spacing: 0.05em;
}

.login-form input {
  font-family: Georgia, serif;
  font-size: 1rem;
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.login-form button {
  align-self: flex-start;
  font-family: 'Playfair Display', Georgia, serif;
  font-variant: small-caps;
  letter-spacing: 0.05em;
  background: var(--ink);
  color: #fff;
  border: none;
  border-radius: 4px;
  padding: 0.6rem 1.2rem;
  cursor: pointer;
}

/* ---------- Regular pages (upload.php) ---------- */

.page {
  max-width: 900px;
  margin: 2rem auto;
  padding: 0 1.5rem;
}

.page--wide { max-width: 1200px; }

/* Pages that also include nav.php's fixed brand-box/top-right-stack need
   extra top clearance so page content doesn't start underneath them. */
.page--with-nav { margin-top: 130px; }

.site-header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 1rem 1.5rem;
  margin-bottom: 1.5rem;
  border-bottom: 2px solid var(--ink);
  padding-bottom: 0.75rem;
}

/* "Back to gallery" and "Logout" (admin.php's own header) / just
   "Logout" (admin/_header.php, every other admin page) — no underline,
   a touch larger than plain body text, and vertically centered against
   the logo image beside them (align-self overrides .site-header's own
   align-items: baseline just for these, without changing how the "Admin"
   dropdown trigger or anything else in the row lines up). */
.site-header-link {
  text-decoration: none;
  font-size: 1.05rem;
  align-self: center;
}

/* Pushed to the far right of .site-header (still above its border-bottom
   "line") via margin-left: auto — the standard flexbox way to pin one
   item to the end while everything else before it stays left-packed
   with its own gap, rather than switching the whole row to
   justify-content: space-between and affecting every other item's
   spacing too. Used on every admin page (admin/_header.php) and
   admin.php's own separate header alike. */
.site-header-logout {
  margin-left: auto;
  font-family: 'Playfair Display', Georgia, serif;
  font-variant: small-caps;
  letter-spacing: 0.05em;
}

.site-header .brand-title {
  font-family: 'Tangerine', 'Brush Script MT', cursive;
  font-size: 2.6rem;
  padding: 0;
}

/* Every admin/*.php page's "Admin ▾" dropdown — every other admin nav
   link lives inside it. Opens on hover (desktop) or via the .open class
   toggled by assets/admin-nav.js (click, for touch/keyboard). Positioned
   directly against .admin-nav-menu (the trigger's own wrapper, set
   position:relative right below) rather than further out against
   .site-header — sitting flush against the trigger with zero vertical
   gap is what lets a mouse actually glide from "Admin" straight down into
   the dropdown on hover alone; a gap there breaks the hover chain (the
   cursor crosses dead space belonging to neither element, the menu closes
   before the mouse arrives, and clicking becomes the only way in). */
.admin-nav-menu { position: relative; align-self: center; }

.admin-nav-trigger {
  appearance: none;
  border: none;
  background: transparent;
  font-family: inherit;
  font-variant: small-caps;
  letter-spacing: 0.05em;
  font-size: 1rem;
  color: var(--ink);
  cursor: pointer;
  padding: 0;
}

.admin-nav-trigger::after { content: ' \25be'; } /* ▾ */

.admin-nav-dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 20;
  display: none;
  list-style: none;
  margin: 0;
  padding: 0;
  min-width: 220px;
  background: var(--paper);
  box-shadow: var(--shadow);
}

.admin-nav-menu:hover .admin-nav-dropdown,
.admin-nav-menu.open .admin-nav-dropdown {
  display: block;
}

.admin-nav-dropdown li > a {
  display: block;
  padding: 0.6rem 1rem;
  font-family: 'Playfair Display', Georgia, serif;
  font-variant: small-caps;
  letter-spacing: 0.05em;
  font-size: 0.9rem;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}

.admin-nav-dropdown li > a:hover { background: #dbeafe; }

/* On admin/*.php pages only, the site-header's brand-title is a link
   wrapping the logo image instead of plain cursive text (park.php,
   landmark.php, links.php, and view_photo.php keep the plain-text version
   above — they aren't part of the admin section). */
.admin-page .site-header .brand-title {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

.site-header a { font-variant: small-caps; letter-spacing: 0.05em; }

#upload-form fieldset { margin-bottom: 1.5rem; border: 1px solid #ddd; padding: 1rem; }
#upload-form legend { font-variant: small-caps; letter-spacing: 0.05em; padding: 0 0.5rem; }
#upload-form fieldset > * { margin-right: 0.5rem; }

/* The geography place picker (Region -> Country -> ... -> Location, as
   many fieldsets as the current branch actually has): a wrapping row
   instead of full-width stacked fieldsets, so they take a lot less
   vertical space — tight gap, no flex-grow (so they sit close together
   rather than stretching to fill the row), and less padding than a
   standalone #upload-form fieldset normally gets. */
.place-fields {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.place-fields fieldset { flex: 0 1 190px; margin-bottom: 0; padding: 0.5rem 0.75rem; }

/* Lights up light green the moment a level's options are ready to pick
   (right after its parent above is chosen or typed) — a visual "go here
   next" cue as the admin drills down, cleared again once that level
   itself is used. */
.place-fields fieldset.just-unlocked {
  background: #e6f7e6;
  border-color: #8fd19e;
  transition: background-color 0.2s ease, border-color 0.2s ease;
}

.place-field-actions { margin-top: 0.4rem; display: flex; gap: 0.4rem; flex-wrap: wrap; }
.place-field-actions button { font-size: 0.75rem; padding: 0.25rem 0.5rem; }
.place-label-input { margin-top: 0.3rem; }

/* A field that's "not applicable right now" (e.g. the level-label input
   before any new name is typed, or a level's own select once a new name
   IS typed) — visually/interactively locked like a disabled field, but
   deliberately not the disabled attribute itself, which would drop it
   from the submitted form data entirely and desync
   place_id[]/new_place_name[]/new_place_level_label[] across fieldsets
   (see assets/upload.js's lockField()). */
.place-field-locked { opacity: 0.5; pointer-events: none; }
#upload-form label { display: block; margin-top: 0.5rem; font-family: Georgia, serif; }
#upload-form select, #upload-form input[type="text"] { font-family: Georgia, serif; padding: 0.35rem; }

#upload-form button[type="submit"] {
  font-family: 'Playfair Display', Georgia, serif;
  font-variant: small-caps;
  letter-spacing: 0.05em;
  background: var(--ink);
  color: #fff;
  border: none;
  padding: 0.6rem 1.5rem;
  cursor: pointer;
}

/* Bulk Process's submit button starts (and stays, while any photo has no
   Category picked — see assets/bulk-upload.js) disabled, since a category
   is required per photo there. */
#upload-form button[type="submit"]:disabled { opacity: 0.5; cursor: default; }

/* ---------- Drag-and-drop file inputs ---------- */

.visually-hidden-file {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.drop-zone {
  border: 2px dashed #bbb;
  border-radius: 6px;
  padding: 1.5rem;
  text-align: center;
  cursor: pointer;
  color: #666;
  font-family: Georgia, serif;
  margin: 0.5rem 0 1rem;
}

.drop-zone:hover,
.drop-zone--active {
  border-color: var(--accent);
  color: var(--ink);
  background: #faf6f6;
}

.drop-zone--small { padding: 0.75rem; font-size: 0.85rem; }

.drop-zone-text { margin: 0; }

.drop-zone-preview {
  display: block;
  max-width: 200px;
  max-height: 200px;
  margin: 0.75rem auto 0;
}

/* ---------- Photo editor (edit_photo.php) ---------- */

.editor-layout {
  display: flex;
  gap: 1.5rem;
  flex-wrap: wrap;
  align-items: flex-start;
  margin: 1rem 0;
}

.editor-canvas-wrap {
  flex: 1 1 420px;
  max-width: 100%;
}

#editor-canvas {
  width: 100%;
  height: auto;
  display: block;
  border: 1px solid #ddd;
  background: #eee;
}

.editor-controls {
  flex: 1 1 260px;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.editor-controls input[type="range"] { width: 100%; margin: 0; }
.editor-controls label { margin-top: 0.75rem; }
.editor-controls button { margin-top: 0.5rem; align-self: flex-start; }

.signature-thumb {
  display: block;
  max-width: 100px;
  max-height: 100px;
  margin: 0.5rem auto 0;
  cursor: grab;
}

.editor-hint {
  font-size: 0.8rem;
  color: #777;
  font-family: Georgia, serif;
  margin: 0.25rem 0;
}

.editor-status {
  font-size: 0.85rem;
  color: var(--accent);
  min-height: 1.2em;
  margin: 0.25rem 0 0;
}

.signature-library {
  border: 1px solid #ddd;
  border-radius: 6px;
  padding: 0.75rem;
  margin: 0.5rem 0;
}

.signature-library-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-family: Georgia, serif;
  font-size: 0.9rem;
  margin-bottom: 0.5rem;
}

.signature-palette {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  font-family: Georgia, serif;
  font-size: 0.85rem;
  color: #777;
}

.signature-palette .signature-thumb { margin: 0; }

/* ---------- Full photo editor (photo_editor.php) ---------- */

/* The flex item that used to be .full-editor-canvas-wrap itself (still
   flex: 1 1 420px via the shared .editor-canvas-wrap rule above) — now one
   level further out, wrapping the canvas *and* the boxes stacked below it.
   flex-basis (and grow weight) bumped 15% over that 420px so the image
   renders larger than the plain editor's. */
.editor-canvas-column {
  flex: 1.15 1 483px;
  max-width: 100%;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* Photo Details, Categories, and Park/Landmark/Flower/Butterfly/
   Travel Country stack in this order, top to bottom (not side by side —
   flex-direction:column, each stretching to the column's full width via
   the default align-items:stretch). */
.under-image-controls {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.under-image-controls .editor-control-group { margin-bottom: 0; }

/* Field labels (the "for=" ones — excludes the wrapping .category-checkbox/
   .editor-checkbox-label labels, which keep their own inline layout) and
   their selects/text inputs, matching the .park-landmark-fields look used
   elsewhere but without doubling up on that class's own border/padding —
   these three boxes already get both from .editor-control-group. */
.under-image-controls .editor-control-group label[for] {
  display: block;
  font-family: Georgia, serif;
  margin-top: 0.5rem;
}

.under-image-controls .editor-control-group select,
.under-image-controls .editor-control-group input[type="text"],
.under-image-controls .editor-control-group input[type="number"],
.under-image-controls .editor-control-group input[type="datetime-local"] {
  font-family: Georgia, serif;
  padding: 0.35rem;
  max-width: 220px;
}

.full-editor-canvas-wrap {
  position: relative;
  overflow: hidden; /* clips the crop overlay's huge dimming outline below */
  /* Overrides .editor-canvas-wrap's flex: 1 1 420px above — that rule was
     written for .editor-layout (a row), where 420px sets a WIDTH basis.
     Here the flex parent is .editor-canvas-column, a COLUMN, so the same
     rule set a fixed 420px HEIGHT basis instead. Any photo whose actual
     display height (at this column's width) is under 420px — most wide/
     landscape photos — still got a 420px-tall wrap, leaving blank space
     below the image that overflow:hidden just hid. The crop overlay
     positions itself as a percentage of this wrap, not of the image
     itself, so that blank margin threw off every crop coordinate. Sizing
     to content instead makes the wrap always match the canvas exactly. */
  flex: 0 0 auto;
}

#full-editor-canvas {
  width: 100%;
  height: auto;
  display: block;
  border: 1px solid #ddd;
  background: #eee;
  cursor: grab;
}

#full-editor-canvas:active { cursor: grabbing; }

/* The crop rectangle itself: a dashed box whose oversized outline (clipped
   by the wrap's overflow:hidden above) dims everything outside it, without
   needing four separate mask divs. */
.crop-overlay {
  position: absolute;
  border: 2px dashed #fff;
  outline: 9999px solid rgba(0, 0, 0, 0.5);
  cursor: move;
  box-sizing: border-box;
}

.crop-handle {
  position: absolute;
  width: 14px;
  height: 14px;
  background: #fff;
  border: 2px solid var(--accent);
  border-radius: 50%;
  transform: translate(-50%, -50%);
}

.crop-handle--nw { top: 0; left: 0; cursor: nwse-resize; }
.crop-handle--ne { top: 0; left: 100%; cursor: nesw-resize; }
.crop-handle--sw { top: 100%; left: 0; cursor: nesw-resize; }
.crop-handle--se { top: 100%; left: 100%; cursor: nwse-resize; }

.editor-control-group {
  border: 1px solid #ddd;
  border-radius: 6px;
  padding: 0.6rem 0.85rem 0.85rem;
  margin-bottom: 0.85rem;
}

.editor-control-group strong {
  display: block;
  font-variant: small-caps;
  letter-spacing: 0.05em;
  margin-bottom: 0.35rem;
}

/* Adjustments/Effects are <details>/<summary> instead of a plain div —
   collapsible, since between them they hold 14 sliders, to save vertical
   room on the page. Same look as the plain <strong> group titles above,
   just clickable, with the browser's native disclosure triangle kept
   (it's the small ▶/▼ marker at the start of the line). */
.editor-control-group summary {
  font-variant: small-caps;
  letter-spacing: 0.05em;
  cursor: pointer;
}

.editor-control-group[open] summary { margin-bottom: 0.35rem; }

/* Live per-edit histogram (photo_editor.php only — see updateHistogram()
   in assets/photo-full-editor.js). Same white background + overlaid
   translucent Red/Green/Blue bars as the saved histogram_path JPEG
   (make_histogram_jpeg() in image_helper.php), just recomputed from the
   live canvas instead of a static file. width:100% scales the fixed
   256x100 backing canvas down to fit the controls column without
   blurring (it's flat bars, not a photo, so the browser's default
   image-rendering already looks crisp scaled down). */
.live-histogram-canvas {
  display: block;
  width: 100%;
  height: 100px;
  border: 1px solid #ddd;
  border-radius: 4px;
  background: #fff;
}

.editor-button-row {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin: 0.35rem 0;
}

.editor-button-row button { margin: 0; }

.editor-checkbox-label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-family: Georgia, serif;
  margin-top: 0.5rem;
}

.full-editor-controls input[type="number"] {
  font-family: Georgia, serif;
  padding: 0.35rem;
  max-width: 120px;
  display: block;
}

/* ---------- Edit Photos grid (edit_photos.php) ---------- */

/* minmax(140px, 160px) — a fixed ceiling, not the old minmax(160px, 1fr) — so a row
   with too few cards to fill doesn't stretch a track (and the thumbnail_path image
   inside it, generated capped at 200x200 by image_helper.php) past its native size. */
.edit-photo-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 160px)); }

.edit-photo-card {
  display: block;
  border: 1px solid #ddd;
  border-radius: 6px;
  overflow: hidden;
  background: #fff;
}

/* object-fit: contain (not cover) + no forced aspect-ratio — the previous
   cover+1:1 combination cropped whatever didn't fit a square, hiding part
   of the photo. A fixed-height flex box here just keeps every cell the
   same height in the grid regardless of a photo's own aspect ratio;
   letting a shorter/wider thumbnail sit on a light background rather than
   stretching or cropping it. */
.edit-photo-card a {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 160px;
  background: #f3f3f3;
}

.edit-photo-card img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  cursor: pointer;
}

.edit-photo-card:hover { border-color: var(--accent); }

.edit-photo-filename,
.edit-photo-caption {
  margin: 0;
  padding: 0.3rem 0.5rem;
  font-size: 0.75rem;
  color: #666;
  overflow-wrap: break-word;
}

/* ---------- Signature library management (manage_signatures.php) ---------- */

.flash--inline {
  position: static;
  transform: none;
  display: inline-block;
  margin-bottom: 1rem;
}

.signature-list {
  list-style: none;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

.signature-list-item {
  border: 1px solid #ddd;
  border-radius: 6px;
  padding: 0.75rem;
  text-align: center;
  width: 140px;
  font-family: Georgia, serif;
  font-size: 0.85rem;
}

.signature-list-thumb {
  display: block;
  max-width: 100px;
  max-height: 100px;
  margin: 0 auto 0.5rem;
}

.signature-list-item span { display: block; margin-bottom: 0.5rem; word-break: break-word; }

.delete-signature-btn {
  font-family: 'Playfair Display', Georgia, serif;
  font-size: 0.75rem;
  padding: 0.3rem 0.6rem;
  border-radius: 4px;
  border: 1px solid var(--accent);
  background: none;
  color: var(--accent);
  cursor: pointer;
}

.delete-signature-btn:hover { background: var(--accent); color: #fff; }

/* ---------- Bulk Process Photos (admin/bulk_upload.php) ---------- */

.pending-photos-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, 190px);
  gap: 1rem;
  margin-bottom: 1.5rem;
}

.pending-photo-card {
  margin: 0;
  border: 1px solid #ddd;
  border-radius: 6px;
  overflow: hidden;
  background: #fff;
  padding-bottom: 0.6rem;
}

.pending-photo-card > a { display: block; }

.pending-photo-card img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: contain;
  display: block;
  background: #eee;
}

.pending-photo-card figcaption {
  padding: 0.3rem 0.6rem 0;
  font-family: Georgia, serif;
  font-size: 0.7rem;
  color: #666;
  overflow-wrap: break-word;
}

.pending-photo-card label,
.pending-photo-card .pending-photo-category-label {
  display: block;
  padding: 0 0.6rem;
  margin-top: 0.4rem;
  font-family: Georgia, serif;
  font-size: 0.75rem;
}

.pending-photo-card .category-multiselect-row { padding: 0 0.6rem; margin-top: 0.15rem; }
.pending-photo-card .category-multiselect { width: 100%; }

.pending-photo-card input[type="text"],
.pending-photo-card select,
.pending-photo-card textarea {
  display: block;
  width: calc(100% - 1.2rem);
  margin: 0.15rem 0.6rem 0;
  font-family: Georgia, serif;
  font-size: 0.8rem;
  padding: 0.25rem;
}

.pending-photo-card textarea {
  resize: vertical;
}

.pending-photo-featured {
  display: flex !important;
  align-items: center;
  gap: 0.35rem;
}

.delete-pending-btn {
  display: block;
  margin: 0.5rem 0.6rem 0;
  font-family: 'Playfair Display', Georgia, serif;
  font-size: 0.7rem;
  padding: 0.25rem 0.5rem;
  border-radius: 4px;
  border: 1px solid var(--accent);
  background: none;
  color: var(--accent);
  cursor: pointer;
}

.delete-pending-btn:hover { background: var(--accent); color: #fff; }
.delete-pending-btn:disabled { opacity: 0.5; cursor: default; }

/* ---------- Photo/category management (manage_photos.php) ---------- */

.photo-manage-list {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.photo-manage-card {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  border: 1px solid #ddd;
  border-radius: 6px;
  padding: 1rem;
}

/* max-width/max-height (not a fixed square + object-fit:cover, which
   this used to be) so the whole thumbnail always shows uncropped, at its
   real aspect ratio — a landscape photo renders wider than tall, a
   portrait photo taller than wide, immediately visible at a glance.
   thumbnail_path files are never cropped to begin with (make_resized_webp()
   in image_helper.php only ever scales down to fit a bounding box,
   preserving the original ratio), so this was purely a display-side
   crop; 160px matches that function's own 200x200-max thumbnail bound
   closely enough that nothing here ever scales up past its native size. */
.photo-manage-thumb-wrap {
  flex-shrink: 0;
  max-width: 160px;
}

.photo-manage-thumb-wrap > a { display: block; }

.photo-manage-thumb {
  max-width: 160px;
  max-height: 160px;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 4px;
  display: block;
  cursor: pointer;
}

/* The floating hover-preview image (assets/hover-preview.js) — one
   shared element, created once and repositioned/re-sourced for whichever
   .hover-preview-trigger is currently being hovered (Manage Photos'
   thumbnail, or any public grid card via assets/photo-grid.js). 720px
   matches the ASSUMED_WIDTH/ASSUMED_HEIGHT that file's own positioning
   math uses. */
.hover-preview-image {
  position: fixed;
  z-index: 200;
  max-width: 720px;
  max-height: 720px;
  width: auto;
  height: auto;
  border: 3px solid var(--paper);
  border-radius: 4px;
  box-shadow: var(--shadow);
  pointer-events: none;
}

.photo-manage-original-filename {
  margin: 0.3rem 0 0;
  font-family: Georgia, serif;
  font-size: 0.7rem;
  color: #888;
  overflow-wrap: break-word;
  text-align: center;
}

.photo-manage-map-wrap { margin-top: 0.4rem; width: 160px; }

.photo-manage-map {
  width: 100%;
  height: 110px;
  border: 1px solid #ddd;
  border-radius: 4px;
  display: block;
}

.photo-manage-map-empty {
  margin: 0;
  height: 110px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed #ddd;
  border-radius: 4px;
  font-family: Georgia, serif;
  font-size: 0.7rem;
  color: #999;
  text-align: center;
}

.photo-manage-fields {
  flex: 1 1 auto;
  min-width: 0;
}

.photo-manage-location {
  margin: 0 0 0.5rem;
  font-family: Georgia, serif;
  font-size: 0.85rem;
  color: #666;
}

/* Manage Landmarks / Manage Parks: the identifying fields (Name/Category,
   Name/Location) on the left, up to 3 sample thumbnails (photo-manage-thumb,
   sized down since there are several side by side here rather than the
   one-per-card thumbnail Manage Photos shows) on the right, so an admin
   can recognize which one is which at a glance. Wraps to stack on narrow
   screens rather than squeezing both columns. */
.manage-card-top-row {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  flex-wrap: wrap;
}

.manage-card-top-fields { flex: 1 1 220px; min-width: 0; }

.manage-card-photos { display: flex; gap: 0.5rem; flex-shrink: 0; }

.manage-card-photos .photo-manage-thumb { max-width: 90px; max-height: 90px; }

/* Manage Parks only (of the two .manage-card-top-row users) — a small
   live GPS preview sitting between the Name/Location fields and the
   sample photos, roughly spanning their combined height. */
.manage-card-map-wrap { flex: 0 0 180px; align-self: stretch; }

.manage-card-map {
  width: 100%;
  height: 100%;
  min-height: 140px;
  border: 1px solid #ddd;
  border-radius: 4px;
  display: block;
}

.manage-card-map-empty {
  height: 100%;
  min-height: 140px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed #ddd;
  border-radius: 4px;
  font-family: Georgia, serif;
  font-size: 0.7rem;
  color: #999;
  text-align: center;
  margin: 0;
}

/* Manage Parks' History formatting toolbar (assets/manage-parks.js wraps
   the textarea's current selection in whatever tag was clicked). */
.history-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin: 0.3rem 0;
}

.history-toolbar button {
  font-family: 'Playfair Display', Georgia, serif;
  font-size: 0.75rem;
  padding: 0.25rem 0.55rem;
  border-radius: 4px;
  border: 1px solid var(--accent);
  background: none;
  color: var(--accent);
  cursor: pointer;
}

.history-toolbar button:hover { background: var(--accent); color: #fff; }

.history-editor-hint {
  margin: 0.3rem 0 0;
  font-family: Georgia, serif;
  font-size: 0.7rem;
  color: #888;
}

/* Upload/Bulk Process: explains why the submit button starts disabled
   (assets/upload.js / assets/bulk-upload.js) until a place resolves at
   least 3 levels deep. */
.place-selection-hint {
  margin: 0.3rem 0 1rem;
  font-family: Georgia, serif;
  font-size: 0.8rem;
  color: #777;
}

.photo-manage-fields label { display: block; font-family: Georgia, serif; margin-top: 0.5rem; }
.photo-manage-fields textarea {
  width: 100%;
  font-family: Georgia, serif;
  padding: 0.5rem;
  resize: vertical;
}

/* Wide enough to show ~100 characters at once (the "ch" unit is the width
   of the font's own "0" glyph, the standard way to size a text input to a
   character count rather than a pixel guess) instead of the browser's
   default ~20-character width — a caption is often a full sentence, not a
   short label. Capped at 100% so it never overflows a narrow card. */
.photo-manage-caption-input {
  width: 100ch;
  max-width: 100%;
}

.category-checkboxes {
  border: 1px solid #ddd;
  padding: 0.5rem 0.75rem;
  margin: 0.75rem 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
}

.category-checkboxes legend,
.exif-edit-fields legend,
.park-landmark-fields legend { font-variant: small-caps; letter-spacing: 0.05em; padding: 0 0.5rem; }

.exif-edit-fields {
  border: 1px solid #ddd;
  padding: 0.25rem 0.75rem 0.75rem;
  margin: 0.75rem 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
}

/* Latitude, Longitude, and Taken At side by side (each its own label +
   input stacked, the three of them in a row) rather than one full-width
   row per field. */
.exif-field { display: flex; flex-direction: column; }

.exif-edit-fields input[type="text"],
.exif-edit-fields input[type="datetime-local"] {
  font-family: Georgia, serif;
  padding: 0.35rem;
  max-width: 220px;
}

.park-landmark-fields {
  border: 1px solid #ddd;
  border-radius: 6px;
  padding: 0.25rem 0.75rem 0.75rem;
  margin: 0.75rem 0;
}

/* manage_photos.php uses <details class="park-landmark-fields"> (was a
   plain <fieldset>) so this whole Park/Landmark/Flower/Butterfly/Travel
   group collapses by default — same collapsible "cascade" box already
   used for this exact group on Upload/the Photo Editor
   (.editor-control-group), just reusing this class's own border/padding
   instead of stacking both classes (which would double them up). */
.park-landmark-fields summary {
  font-variant: small-caps;
  letter-spacing: 0.05em;
  cursor: pointer;
  padding: 0.35rem 0;
}

.park-landmark-fields[open] summary { margin-bottom: 0.35rem; }

.park-landmark-fields label { display: block; font-family: Georgia, serif; margin-top: 0.5rem; }

.park-landmark-fields select,
.park-landmark-fields input[type="text"] {
  font-family: Georgia, serif;
  padding: 0.35rem;
  max-width: 220px;
}

/* Groups a Landmark's four controls (existing-landmark select/new-name
   text, then existing-category select/new-category text — see
   render_park_landmark_fields()) into one visibly bordered box, so they
   read as configuring one thing rather than blending into the next field
   in the Park/Landmark/Flower/Butterfly/Travel Country group. Self-
   contained (not scoped under .park-landmark-fields/.editor-control-group)
   since it's used identically across all four pages that call
   render_park_landmark_fields(), several of which don't share those
   classes' own label/input styling. Shared as-is (via this one class name)
   by the Park field group too — see lib/render_helpers.php's
   render_park_landmark_fields(), which gives that wrapper both classes
   (.landmark-field-group.park-field-group) purely so .park-field-group is
   available as a hook if Park-specific styling is ever needed on its own. */
.landmark-field-group {
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 0.25rem 0.75rem 0.75rem;
  margin: 0.5rem 0;
}

.landmark-field-group label {
  display: block;
  font-family: Georgia, serif;
  margin-top: 0.5rem;
}

.landmark-field-group select,
.landmark-field-group input[type="text"] {
  font-family: Georgia, serif;
  padding: 0.35rem;
  max-width: 220px;
}

.category-checkbox {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-family: Georgia, serif;
  font-size: 0.9rem;
}

.category-checkbox--new input[type="text"] { padding: 0.25rem; font-family: Georgia, serif; }

/* Manage Photos' own category picker (render_category_multiselect()) — a
   multi-select listbox (ctrl/cmd-click, or shift-click, for more than
   one) next to "Add new category", instead of one checkbox per category,
   to save vertical room across many photo cards on screen at once. */
.category-multiselect-row {
  display: flex;
  gap: 1rem;
  align-items: flex-start;
  flex-wrap: wrap;
}

.category-multiselect {
  font-family: Georgia, serif;
  font-size: 0.85rem;
  min-width: 160px;
}

.category-multiselect-new label {
  display: block;
  font-family: Georgia, serif;
  font-size: 0.85rem;
  margin-bottom: 0.25rem;
}

.category-multiselect-new input[type="text"] { padding: 0.35rem; font-family: Georgia, serif; }

.photo-manage-card .delete-photo-btn {
  font-family: 'Playfair Display', Georgia, serif;
  font-size: 0.75rem;
  padding: 0.3rem 0.6rem;
  border-radius: 4px;
  border: 1px solid var(--accent);
  background: none;
  color: var(--accent);
  cursor: pointer;
  margin-left: 0.5rem;
}

.photo-manage-card .delete-photo-btn:hover { background: var(--accent); color: #fff; }

.photo-manage-card button[type="submit"] {
  font-family: 'Playfair Display', Georgia, serif;
  font-variant: small-caps;
  letter-spacing: 0.05em;
  background: var(--ink);
  color: #fff;
  border: none;
  padding: 0.5rem 1.25rem;
  cursor: pointer;
  margin-top: 0.5rem;
}

.photo-manage-card button[type="submit"]:disabled { opacity: 0.5; cursor: default; }

.manage-pagination {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 1.5rem;
  margin-top: 1.5rem;
  font-family: Georgia, serif;
}

.save-all-bar {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 1rem;
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid #ddd;
}

.save-all-bar button {
  font-family: 'Playfair Display', Georgia, serif;
  font-variant: small-caps;
  letter-spacing: 0.05em;
  font-size: 1.05rem;
  background: var(--ink);
  color: #fff;
  border: none;
  padding: 0.7rem 1.75rem;
  border-radius: 4px;
  cursor: pointer;
}

.save-all-bar button:disabled { opacity: 0.6; cursor: default; }
.save-all-bar button:hover:not(:disabled) { background: var(--accent); }

/* Destructive, so it stands apart from Save/Reset at a glance instead of
   matching their --ink look exactly — --accent is already this site's
   "delete" color everywhere else (e.g. .photo-manage-card .delete-photo-btn's
   hover state), just used here as the resting state instead. */
#full-delete-btn { background: var(--accent); }
#full-delete-btn:hover:not(:disabled) { background: var(--ink); }

/* Smaller than .save-all-bar's other user (Manage Photos' one full-width
   "Save All Changes" button) — three short-labeled buttons side by side
   here, so shrinking just these three saves real space without touching
   that unrelated page. */
#full-save-btn,
#full-reset-btn,
#full-delete-btn {
  font-size: 0.85rem;
  padding: 0.45rem 1.1rem;
}

#save-all-status { font-family: Georgia, serif; color: #666; }

/* ---------- Location photo grid (index.php) ---------- */

.grid-wrap {
  background: #fff;
  min-height: 100vh;
  padding: 130px 20px 40px;
}

.grid-location-heading {
  text-align: center;
  font-family: 'Playfair Display', Georgia, serif;
  font-size: 2.4rem;
  color: var(--ink);
  margin: 0 auto 1.5rem;
  max-width: 1600px;
}

.photo-grid-results {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1.5rem;
  max-width: 1600px;
  margin: 0 auto;
}

.photo-grid-empty {
  text-align: center;
  color: #777;
  font-family: Georgia, serif;
  grid-column: 1 / -1;
}

/* Park/Landmark/Flower/Butterfly/Travel Country detail pages
   (assets/category-photo-grid.js) only — never more than 4 photos per
   row, unlike index.php's own grid above (.photo-grid-results' plain
   auto-fill, uncapped). Below the breakpoint, auto-fill still shrinks to
   3/2/1 columns as the viewport narrows; above it, always exactly 4
   regardless of how much wider the page gets. */
@media (min-width: 950px) {
  #category-photo-grid { grid-template-columns: repeat(4, 1fr); }
}

.grid-pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  max-width: 1600px;
  margin: 2.5rem auto 0;
  font-family: 'Playfair Display', Georgia, serif;
}

.grid-pagination button {
  padding: 0.5rem 1.2rem;
  border-radius: 4px;
  border: 1px solid var(--accent);
  background: none;
  color: var(--accent);
  font-family: inherit;
  font-size: 0.95rem;
  cursor: pointer;
}

.grid-pagination button:hover:not(:disabled) { background: var(--accent); color: #fff; }
.grid-pagination button:disabled { opacity: 0.35; cursor: default; }

.grid-page-indicator {
  color: var(--ink);
  font-size: 0.95rem;
}

.grid-card {
  margin: 0;
  border: 1px solid #ddd;
  border-radius: 6px;
  overflow: hidden;
  background: #fff;
  display: flex;
  flex-direction: column;
}

.grid-card-link { display: block; position: relative; }

.grid-card img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: contain;
  display: block;
  background: #eee;
}

/* "For sale" price badge (assets/photo-grid.js) — only rendered at all
   when the photo has a price set (Manage Favorites), same photos.price
   that puts a PayPal Buy box on this photo's own detail page. */
.grid-card-price-badge {
  position: absolute;
  top: 0.4rem;
  right: 0.4rem;
  background: var(--accent);
  color: #fff;
  font-family: 'Playfair Display', Georgia, serif;
  font-weight: 600;
  font-size: 0.75rem;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  box-shadow: var(--shadow);
}

.grid-card figcaption {
  padding: 0.75rem;
  font-family: Georgia, serif;
  font-size: 0.85rem;
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.grid-card-description { margin: 0; color: var(--ink); }

.grid-card-categories { margin: 0; display: flex; flex-wrap: wrap; gap: 0.3rem; }

.category-badge {
  display: inline-block;
  background: #f1e6e8;
  color: var(--accent);
  border-radius: 999px;
  padding: 0.1rem 0.6rem;
  font-size: 0.75rem;
  font-variant: small-caps;
  letter-spacing: 0.04em;
}

.grid-card-date { margin: 0; color: #888; font-size: 0.8rem; }


/* ---------- Photo detail page (view_photo.php) ---------- */

.photo-detail {
  display: flex;
  gap: 2rem;
  flex-wrap: wrap;
  align-items: flex-start;
}

.photo-detail-image-wrap { position: relative; flex: 2 1 500px; }

.photo-detail-large-link { display: block; }

.photo-detail-large {
  width: 100%;
  height: auto;
  display: block;
  border: 1px solid #ddd;
  cursor: zoom-in;
}

.photo-detail-info { flex: 1 1 300px; font-family: Georgia, serif; }

.photo-detail-location { color: #666; font-size: 0.9rem; margin: 0 0 0.5rem; }
.photo-detail-description { margin: 0 0 0.75rem; }

/* Taken date, camera/lens/exposure EXIF, and GPS — directly under the
   photo (photo-detail-image-wrap), not in the side info panel. */
.photo-detail-exif {
  font-family: Georgia, serif;
  font-size: 0.85rem;
  color: #555;
  margin-top: 0.75rem;
}

.photo-detail-exif p { margin: 0.2rem 0; }
.photo-detail-date { color: #555; }
.photo-detail-camera, .photo-detail-lens, .photo-detail-exposure { color: #555; }
.photo-detail-gps { font-size: 0.85rem; }

.photo-detail-map {
  width: 100%;
  height: 300px;
  border: 1px solid #ddd;
  margin-top: 0.5rem;
}

.photo-detail-histogram {
  width: 100%;
  max-width: 512px;
  border: 1px solid #ddd;
  display: block;
}

/* The PayPal Buy box (view_photo.php, assets/paypal-checkout.js) — only
   rendered at all when photos.price is set for this photo (see
   Manage Favorites). */
.photo-buy-box {
  margin: 0.75rem 0;
  padding: 0.75rem 1rem;
  border: 1px solid var(--accent);
  border-radius: 4px;
  max-width: 320px;
  /* Establishes a local stacking context so anything PayPal's SDK
     renders inside #paypal-buttons (it uses an intentionally very high
     z-index so its own UI stays above the host page) is contained here
     rather than competing with the nav's flyout menus (z-index: 20)
     elsewhere on the page. assets/paypal-checkout.js additionally
     .close()s the rendered component after a purchase completes, which
     is what actually removes anything the SDK appended outside this box
     entirely (e.g. to <body>) — this rule alone wouldn't reach that. */
  position: relative;
  z-index: 0;
}

.photo-buy-price {
  margin: 0 0 0.6rem;
  font-family: 'Playfair Display', Georgia, serif;
  font-weight: 600;
  color: var(--accent);
}

.photo-buy-status {
  margin: 0.5rem 0 0;
  font-size: 0.85rem;
  color: #555;
}

.photo-buy-download {
  margin: 0.5rem 0 0;
  font-size: 0.85rem;
  color: #555;
}

.photo-buy-start-btn {
  font-family: 'Playfair Display', Georgia, serif;
  font-variant: small-caps;
  letter-spacing: 0.05em;
  background: var(--ink);
  color: #fff;
  border: none;
  border-radius: 4px;
  padding: 0.5rem 1.2rem;
  font-size: 0.9rem;
  cursor: pointer;
}

.photo-buy-start-btn:hover { background: var(--accent); }

/* The Personal Use License agreement modal (view_photo.php,
   assets/paypal-checkout.js) — gates the real PayPal Buttons: "Buy this
   photo" opens this, and only "I Agree" actually renders them.
   display defaults to none, only becoming flex via the :not([hidden])
   rule below — an unconditional `display: flex` here would outrank the
   browser's own `[hidden] { display: none }` default (an author-
   stylesheet rule always wins over a user-agent one, regardless of
   selector specificity or source order), which is exactly what made
   toggling the modal's hidden attribute from JS silently do nothing:
   Cancel/Escape/backdrop-click/"I Agree" all set .hidden = true, but the
   modal stayed visually shown either way. */
.license-modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

.license-modal:not([hidden]) {
  display: flex;
}

.license-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
}

.license-modal-dialog {
  position: relative;
  background: var(--paper);
  border-radius: 6px;
  box-shadow: var(--shadow);
  max-width: 560px;
  width: 100%;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
  padding: 1.5rem;
  font-family: Georgia, serif;
}

.license-modal-dialog h2 {
  margin: 0 0 0.75rem;
  font-family: 'Playfair Display', Georgia, serif;
  color: var(--ink);
}

.license-modal-dialog h3 {
  margin: 1rem 0 0.4rem;
  font-family: 'Playfair Display', Georgia, serif;
  font-variant: small-caps;
  letter-spacing: 0.04em;
  color: var(--accent);
  font-size: 1rem;
}

.license-modal-body {
  overflow-y: auto;
  font-size: 0.9rem;
  line-height: 1.5;
}

.license-modal-body p { margin: 0 0 0.5rem; }
.license-modal-body ul { margin: 0 0 0.5rem; padding-left: 1.25rem; }
.license-modal-body li { margin-bottom: 0.4rem; }

.license-modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.75rem;
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid #ddd;
}

.license-modal-actions button {
  font-family: 'Playfair Display', Georgia, serif;
  font-variant: small-caps;
  letter-spacing: 0.05em;
  border-radius: 4px;
  padding: 0.5rem 1.1rem;
  font-size: 0.9rem;
  cursor: pointer;
  border: 1px solid #ccc;
  background: #fff;
  color: var(--ink);
}

#license-accept-btn {
  background: var(--ink);
  color: #fff;
  border-color: var(--ink);
}

#license-accept-btn:hover { background: var(--accent); border-color: var(--accent); }
#license-decline-btn:hover { background: #f1f1f1; }

/* ---------- Original photo viewer (view_original.php) ---------- */

.original-body {
  margin: 0;
  background: #000;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
}

.original-viewer {
  position: relative;
  max-width: 100vw;
  max-height: 100vh;
  line-height: 0;
}

.original-viewer img {
  display: block;
  max-width: 100vw;
  max-height: 100vh;
}

.watermark-overlay {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(-30deg);
  font-family: 'Playfair Display', Georgia, serif;
  font-size: clamp(1.5rem, 5.5vw, 3.5rem);
  font-weight: 600;
  color: rgba(255, 255, 255, 0.15);
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
  white-space: nowrap;
  pointer-events: none;
  user-select: none;
}

/* The image-based watermark (view_original.php's "Primary Signature
   White" from Manage Signatures) — same centered, diagonal, subtle
   placement as .watermark-overlay above (the plain-text fallback used
   when that signature doesn't exist), just an <img> instead of text.
   Sized relative to the viewport rather than the (very wide, ~2.9:1)
   source image's own native size, capped so it never dominates a small
   photo. */
.watermark-overlay-image {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(-30deg);
  width: 55vw;
  max-width: 640px;
  opacity: 0.3;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.18));
  pointer-events: none;
  user-select: none;
}

/* ---------- Park detail page (park.php) ---------- */

.park-detail {
  margin-bottom: 2rem;
}

.park-info { font-family: Georgia, serif; }
.park-history { white-space: pre-line; }

/* Scoped sizes for whatever formatting a park's History picked up from
   Manage Parks' toolbar (see RICH_TEXT_ALLOWED_TAGS) — browser-default
   h1-h4 sizing would otherwise look oversized/inconsistent stacked inside
   a sidebar-width column, and could read as confusingly page-title-sized
   next to the real <h1> above it. */
.park-history h1, .park-history h2, .park-history h3, .park-history h4 {
  font-family: 'Playfair Display', Georgia, serif;
  color: var(--ink);
  margin: 1rem 0 0.5rem;
  line-height: 1.25;
}
.park-history h1 { font-size: 1.5rem; }
.park-history h2 { font-size: 1.3rem; }
.park-history h3 { font-size: 1.15rem; }
.park-history h4 { font-size: 1rem; }
.park-history ul, .park-history ol { margin: 0.5rem 0; padding-left: 1.5rem; }
.park-history li { margin: 0; }
.park-history p { margin: 0 0 0.75rem; }
.travel-dates-visited { font-family: Georgia, serif; }

/* ---------- Species catalog pages (flowers.php, butterflies.php) ---------- */

/* One flower box per row, full width, stacked top to bottom in the
   query's alphabetical order — not a multi-column grid (with few
   flowers, a multi-column grid just puts them side by side, which reads
   as columns rather than rows). */
.species-catalog {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
  max-width: 1600px;
  margin: 0 auto;
}

.species-card {
  border: 1px solid #ddd;
  border-radius: 6px;
  padding: 1rem;
  background: #fff;
}

.species-card h2 { margin-top: 0; font-size: 1.3rem; }
.species-card h2 a { color: var(--ink); text-decoration: none; }
.species-card h2 a:hover { text-decoration: underline; }

.species-card-photos {
  display: grid;
  grid-template-columns: repeat(auto-fill, 175px); /* fixed size, not 1fr — otherwise they stretch to fill the now full-width card */
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.species-card-photos a { display: block; }

.species-card-photos img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 4px;
  background: #eee;
}

.species-card-description {
  font-family: Georgia, serif;
  white-space: pre-line;
}

.species-card-link { font-family: 'Playfair Display', Georgia, serif; }

.species-card-colors {
  font-family: 'Courier New', monospace;
  font-size: 0.85rem;
  color: #555;
  background: #f7f7f7;
  border: 1px solid #eee;
  border-radius: 4px;
  padding: 0.4rem 0.6rem;
  word-break: break-word;
}

.species-card-more { font-family: 'Playfair Display', Georgia, serif; font-size: 0.9rem; }

/* Stacked below the History (.park-detail) and above the Photos grid —
   full page width would stretch the fixed-height .photo-detail-map
   (300px tall) far wider than tall, so it keeps a reasonable max-width
   here instead of the side-by-side layout's old flex-basis. */
.park-map-wrap { max-width: 700px; margin-bottom: 2rem; font-family: Georgia, serif; }

/* ---------- Manage Places (admin/manage_places.php) ----------
   Finder-style cascading columns: clicking a row opens its children as a
   new column to the right (replacing any columns that were already open
   further right, same as macOS Finder's column view) — every visible
   column stays independently editable at once, rather than the popup/
   hover flyout used elsewhere on the site (site-nav.js's initGalleryMenu),
   since a hover-to-open flyout would collapse out from under an admin
   mid-edit the moment the mouse strayed off it. */

.places-columns {
  display: flex;
  align-items: stretch;
  border: 1px solid #ddd;
  border-radius: 4px;
  overflow-x: auto;
  max-width: 100%;
  margin-bottom: 1rem;
  background: #fff;
}

.places-column {
  flex: 0 0 200px;
  max-height: 420px;
  overflow-y: auto;
  border-right: 1px solid #eee;
  font-family: Georgia, serif;
}

.places-column:last-child { border-right: none; }

.places-column-header {
  position: sticky;
  top: 0;
  padding: 0.4rem 0.6rem;
  font-family: 'Playfair Display', Georgia, serif;
  font-size: 0.75rem;
  font-weight: 600;
  color: #777;
  background: #f7f7f7;
  border-bottom: 1px solid #eee;
}

.places-column-loading { padding: 0.6rem; color: #999; font-size: 0.85rem; }

.places-row {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.35rem 0.5rem;
  border-bottom: 1px solid #f3f3f3;
  cursor: pointer;
}

.places-row:hover { background: #f7f7f7; }

.places-row.selected { background: var(--accent); color: #fff; }

.places-row.selected .places-row-action { color: #fff; border-color: rgba(255,255,255,0.6); }

.places-row-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.places-row-name-input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0.15rem 0.3rem;
  font-family: Georgia, serif;
}

.places-row-arrow { flex-shrink: 0; opacity: 0.6; }

.places-row-action {
  flex-shrink: 0;
  background: none;
  border: 1px solid #ccc;
  border-radius: 3px;
  font-size: 0.7rem;
  line-height: 1;
  padding: 0.2rem 0.35rem;
  cursor: pointer;
  color: #555;
}

.places-row-action:hover { background: rgba(0,0,0,0.08); }

.places-add-row {
  padding: 0.35rem 0.5rem;
  color: var(--accent);
  cursor: pointer;
  font-size: 0.85rem;
}

.places-add-row:hover { background: #f7f7f7; }

.places-add-form {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0.5rem;
  background: #fafafa;
  border-bottom: 1px solid #f3f3f3;
}

.places-add-form input { padding: 0.3rem; font-family: Georgia, serif; }

.places-add-form-buttons { display: flex; gap: 0.4rem; }

.places-empty-column { padding: 0.6rem; color: #999; font-size: 0.85rem; font-style: italic; }

.banned-ip-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid #eee;
  font-family: Georgia, serif;
}

.banned-ip-info { display: flex; flex-direction: column; gap: 0.15rem; }

.banned-ip-address { font-weight: bold; }

.banned-ip-meta { font-size: 0.85rem; color: #777; }

.unban-ip-btn {
  font-family: 'Playfair Display', Georgia, serif;
  font-size: 0.75rem;
  padding: 0.3rem 0.6rem;
  border-radius: 4px;
  border: 1px solid var(--accent);
  background: none;
  color: var(--accent);
  cursor: pointer;
  flex-shrink: 0;
}

.unban-ip-btn:hover { background: var(--accent); color: #fff; }

.banned-ip-empty { font-family: Georgia, serif; color: #777; }

/* ---------- Manage Categories (admin/manage_categories.php) ---------- */

.category-manage-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid #eee;
}

.category-manage-row input[type="text"] {
  flex: 1 1 220px;
  min-width: 0;
  padding: 0.4rem;
  font-family: Georgia, serif;
}

.category-manage-count {
  flex-shrink: 0;
  font-family: Georgia, serif;
  font-size: 0.8rem;
  color: #777;
  min-width: 80px;
}

.category-manage-actions { display: flex; gap: 0.4rem; flex-shrink: 0; }

.category-manage-note {
  flex-basis: 100%;
  margin: 0;
  font-family: Georgia, serif;
  font-size: 0.75rem;
  color: var(--accent);
}

/* ---------- Manage Parks (admin/manage_parks.php) ---------- */

.park-manage-card {
  margin-bottom: 1.5rem;
}

.park-manage-card .delete-park-btn {
  font-family: 'Playfair Display', Georgia, serif;
  font-size: 0.75rem;
  padding: 0.3rem 0.6rem;
  border-radius: 4px;
  border: 1px solid var(--accent);
  background: none;
  color: var(--accent);
  cursor: pointer;
  margin-left: 0.5rem;
}

.park-manage-card .delete-park-btn:hover { background: var(--accent); color: #fff; }

/* A cascading Region -> County -> Location flyout (the same gallery-menu
   widget as the public nav and Manage Photos) used to link a Park to a
   Location, on both the "Add a Park" form and each existing row's edit
   card. (Landmarks dropped this link entirely — see Notes in README.md —
   so Manage Landmarks no longer uses this widget.) */
.location-picker {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0.5rem 0 1rem;
}

.location-picker .gallery-menu { display: inline-block; }

.location-picker .brand-nav-link {
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 0.4rem 0.9rem;
}

.clear-link {
  font-family: 'Playfair Display', Georgia, serif;
  font-size: 0.8rem;
  color: var(--accent);
  text-decoration: none;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
}

.clear-link:hover { text-decoration: underline; }

/* ---------- Links page (links.php) ---------- */

.links-list {
  font-family: Georgia, serif;
  line-height: 2;
  padding-left: 1.25rem;
}

/* ---------- Manage Orders (admin/manage_orders.php) ---------- */

.orders-table-wrap { overflow-x: auto; }

.orders-table {
  width: 100%;
  border-collapse: collapse;
  font-family: Georgia, serif;
  font-size: 0.9rem;
}

.orders-table th, .orders-table td {
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid #ddd;
  text-align: left;
  white-space: nowrap;
}

.orders-table th {
  font-family: 'Playfair Display', Georgia, serif;
  font-variant: small-caps;
  letter-spacing: 0.05em;
}

.orders-table-photo { display: flex; align-items: center; gap: 0.5rem; white-space: normal; }

.orders-table-thumb {
  width: 48px;
  height: 48px;
  object-fit: cover;
  border-radius: 3px;
  flex-shrink: 0;
}

.orders-table-id { font-family: monospace; font-size: 0.8rem; color: #666; }

.order-status {
  padding: 0.15rem 0.5rem;
  border-radius: 3px;
  font-size: 0.8rem;
}

.order-status--completed { background: #dcfce7; color: #166534; }
.order-status--created { background: #fef3c7; color: #92400e; }
