:root{
  --bg: #0a0b0f;
  --bg-alt: #101219;
  --card: #14161f;
  --card-border: #23262f;
  --text: #f4f5f7;
  --text-dim: #9a9ea9;
  --text-faint: #666a75;
  --accent: #ff8a1e;
  --accent-2: #ffb020;
  --accent-soft: rgba(255,138,30,0.12);
  --danger: #e5484d;
  --danger-2: #c93a3e;
  --radius: 12px;
  --maxw: 1160px;
}

*{ box-sizing: border-box; }
body{
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  line-height: 1.5;
}
a{ color: inherit; }

.wrap{ max-width: var(--maxw); margin: 0 auto; padding: 0 24px; }

/* Header */
.site-header{
  border-bottom: 1px solid var(--card-border);
  background: rgba(10,11,15,0.9);
}
.header-inner{
  height: 72px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.brand{
  display: flex; align-items: center; gap: 10px;
  font-weight: 800; letter-spacing: 0.02em; font-size: 1.02rem;
  text-decoration: none;
}
.brand-logo{
  height: 60px; width: auto; display: block; flex-shrink: 0;
  /* content-box override: with the site-wide box-sizing:border-box reset,
     padding would eat into the 60px height and shrink the visible logo
     instead of adding space around it. */
  box-sizing: content-box;
  padding: 5px;
}

.header-nav{ display: flex; gap: 24px; }
.header-nav a{
  color: var(--text-dim);
  text-decoration: none;
  font-size: 0.9rem;
  font-weight: 600;
}
.header-nav a:hover{ color: var(--text); }
.header-nav a.is-active{ color: var(--accent-2); }

/* Signed-in name dropdown (header only — the footer just links straight to
   signout.php, no dropdown there) */
.nav-user{ position: relative; display: flex; align-items: center; }
.nav-user-trigger{
  display: inline-flex; align-items: center; gap: 4px;
  background: none; border: none; padding: 0; margin: 0;
  font: inherit; font-size: 0.9rem; font-weight: 600;
  color: var(--text-dim); cursor: pointer;
}
.nav-user-trigger:hover{ color: var(--text); }
.nav-user-trigger[aria-expanded="true"]{ color: var(--text); }
.nav-user-caret{ font-size: 1.3em; line-height: 1; transition: transform 0.15s ease; }
.nav-user-trigger[aria-expanded="true"] .nav-user-caret{ transform: rotate(180deg); }
.nav-user-menu{
  position: absolute;
  top: calc(100% + 12px);
  right: 0;
  min-width: 150px;
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: 10px;
  box-shadow: 0 12px 28px rgba(0,0,0,0.35);
  padding: 6px;
  z-index: 60;
}
.nav-user-menu[hidden]{ display: none; }
.nav-user-menu a{
  display: block;
  padding: 8px 10px;
  border-radius: 6px;
  color: var(--text);
  text-decoration: none;
  font-size: 0.88rem;
  font-weight: 600;
}
.nav-user-menu a:hover{ background: var(--bg-alt); color: var(--accent-2); }

/* Page */
.page{ padding: 48px 24px 80px; }
.page-head{ margin-bottom: 28px; }
/* Opt-in modifier (added alongside .page-head, not merged into its base
   rule) used only by venues.php's own page head, which now has a
   second thing — the signed-in-only "Add Venue" button — sitting beside
   the title/subtitle block. Every other page's .page-head still has
   just the one child it always had, so leaving the base .page-head rule
   itself alone (rather than making it display: flex unconditionally)
   means this change can't affect any of those other pages' layout, even
   though they share the same class. align-items: flex-start (not
   center) keeps the button pinned to the title's own top line rather
   than vertically centering against the whole two-line title+subtitle
   block; flex-wrap: wrap lets the button drop to its own line at
   narrower widths instead of a dedicated breakpoint rule, since a
   title/subtitle block and a single button don't need anything more
   deliberate than that to stay readable. */
.page-head-with-actions{
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.page-head-actions{ flex: 0 0 auto; }
h1{ margin: 0 0 6px; font-size: 1.9rem; letter-spacing: -0.01em; }
.venue-total{
  display: inline-block;
  vertical-align: middle;
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--accent-2);
  background: var(--accent-soft);
  border: 1px solid rgba(255,138,30,0.3);
  border-radius: 999px;
  padding: 3px 12px;
  margin-left: 8px;
}
.subtitle{ margin: 0; color: var(--text-dim); font-size: 0.98rem; max-width: 560px; }

/* Search (venues.php) — re-added for the Name/City/State/Zip Code search
   box; this exact rule used to live here before the venue list became a
   collapsible Country -> State tree, got removed as dead code once that
   redesign dropped the search box entirely, and is now back verbatim
   now that search has returned (see venues.php's own comment on why
   search and the tree are two separate display modes, not merged into
   one). */
.search-form{
  display: flex; gap: 10px; flex-wrap: wrap;
  margin-bottom: 16px;
}
.search-form input[type="text"]{
  flex: 1; min-width: 220px;
  padding: 11px 14px;
  border-radius: 10px;
  border: 1px solid var(--card-border);
  background: var(--card);
  color: var(--text);
  font-size: 0.95rem;
}
.search-form input[type="text"]:focus{
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.btn{
  display: inline-flex; align-items: center; gap: 6px;
  padding: 11px 20px; border-radius: 10px;
  font-weight: 700; font-size: 0.9rem;
  text-decoration: none; border: 1px solid transparent; cursor: pointer;
  font-family: inherit;
}
.btn-primary{ background: var(--accent); color: #17110a; }
.btn-primary:hover{ background: var(--accent-2); }
.btn-ghost{ background: transparent; border-color: var(--card-border); color: var(--text); }
.btn-ghost:hover{ border-color: #3a3e4a; background: rgba(255,255,255,0.03); }
.btn-small{ padding: 7px 14px; font-size: 0.82rem; }
/* Delete Document's own submit button (venue.php) — the only genuinely
   destructive action in this app's UI so far (removes a venue_uploads
   row AND the file it points to, with no undo), so it gets a visually
   distinct color from every other button here rather than looking like
   an ordinary .btn-primary action. */
.btn-danger{ background: var(--danger); color: #fff; }
.btn-danger:hover{ background: var(--danger-2); }

.result-count{ color: var(--text-faint); font-size: 0.88rem; margin: 0 0 20px; }

/* Table */
.table-wrap{
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  overflow-x: auto;
}
table{ width: 100%; border-collapse: collapse; font-size: 0.9rem; min-width: 720px; }
th{
  text-align: left;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-faint);
  font-weight: 700;
  padding: 14px 16px;
  border-bottom: 1px solid var(--card-border);
  white-space: nowrap;
}
th a{ text-decoration: none; color: inherit; }
th a:hover{ color: var(--accent-2); }
td{
  padding: 14px 16px;
  border-bottom: 1px solid var(--card-border);
  vertical-align: top;
}
tr:last-child td{ border-bottom: none; }

.col-name{ min-width: 200px; }
.venue-name{ display: block; font-weight: 700; text-decoration: none; color: var(--text); }
a.venue-name:hover{ color: var(--accent-2); text-decoration: underline; }
.former-name{ display: block; font-size: 0.78rem; color: var(--text-faint); margin-top: 2px; }

/* Venue detail page */
.breadcrumb{ color: var(--text-faint); font-size: 0.85rem; margin: 0 0 24px; }
.breadcrumb a{ color: var(--text-dim); text-decoration: none; }
.breadcrumb a:hover{ color: var(--accent-2); }

.venue-detail{
  display: grid;
  grid-template-columns: 320px 1fr;
  gap: 32px;
  align-items: start;
}
@media (max-width: 720px){ .venue-detail{ grid-template-columns: 1fr; } }

.venue-photo-wrap{ position: sticky; top: 96px; }
.venue-photo{
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius);
  border: 1px solid var(--card-border);
  background: var(--card);
}
.venue-photo-placeholder{
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  color: var(--text-faint);
  font-size: 0.85rem;
  background:
    linear-gradient(135deg, rgba(255,138,30,0.16), rgba(255,176,32,0.05)),
    repeating-linear-gradient(135deg, rgba(255,255,255,0.03) 0 2px, transparent 2px 14px);
}

.venue-detail-body h1{ margin-bottom: 4px; }
.venue-detail-body .former-name{ margin-bottom: 20px; font-size: 0.9rem; }
/* Wraps the venue name h1 together with the owner-only "Edit Venue"
   button (see venue.php's $isVenueOwner) on one row — a signed-out
   visitor, or anyone who isn't this venue's owner, never renders the
   button at all, so for them this wrapper has exactly one child and
   looks identical to a bare h1. flex-wrap: wrap lets the button drop to
   its own line below a long venue name on a narrow screen rather than
   cramming both onto one line or needing a dedicated breakpoint rule. */
.venue-title-row{
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 4px;
}
/* Overrides .venue-detail-body h1's own margin-bottom: 4px (equal
   selector specificity, so source order alone would otherwise decide
   it) — that spacing now lives on .venue-title-row itself just above,
   so the h1 doesn't also add its own on top and double the gap. */
.venue-title-row h1{ margin-bottom: 0; }

.detail-grid{
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
  margin: 24px 0;
  padding: 0;
}
@media (max-width: 480px){ .detail-grid{ grid-template-columns: 1fr; } }
/* Power's SR/SL/Up Center row groups (see $specsRowGroups in venue.php) —
   each trio renders as its own <dl class="detail-grid detail-grid-3col">
   instead of flowing through the shared two-column layout above, so e.g.
   "SR Power Services", "SR Tails Required", and "Distance from SR Service
   to Up SR" always sit on one line together rather than splitting across
   rows. Declared after the plain two-column rule above so it wins the
   cascade on the elements that carry both classes; the mobile override
   below is written out explicitly rather than relied on via the base
   .detail-grid media query above, so this still collapses to one column
   on narrow screens even if the two rules are ever reordered later. */
.detail-grid-3col{ grid-template-columns: repeat(3, 1fr); }
@media (max-width: 480px){ .detail-grid-3col{ grid-template-columns: 1fr; } }
/* Needed once a .detail-grid is used as a collapsible target (see each
   .specs-category below) — same fix, same reason, as
   .gallery-panel[hidden]/.document-list[hidden]/#specsBody[hidden]
   elsewhere in this file: `.detail-grid{display:grid}` above has the same
   CSS specificity as the browser's own `[hidden]{display:none}` rule, so
   without this override the grid would silently stay visible even while
   `hidden`. The plain (non-collapsible) uses of .detail-grid elsewhere on
   this page never set `hidden`, so this rule has no effect on them. */
.detail-grid[hidden]{ display: none; }
.detail-item dt{
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-faint);
  font-weight: 700;
  margin-bottom: 4px;
}
.detail-item dd{ margin: 0; font-size: 1.05rem; font-weight: 600; }
/* Street address can wrap to two lines (street, then city/state/zip), so it
   gets the full row width instead of squeezing into one of the two grid
   columns like the shorter single-line facts around it. */
.detail-item-wide{ grid-column: 1 / -1; }

.cap-line{ display: block; font-weight: 600; }
.cap-label{ font-weight: 400; color: var(--text-faint); font-size: 0.78rem; }

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

.empty-state{ color: var(--text-faint); padding: 32px 0; text-align: center; }

.db-note{
  margin-top: 18px;
  color: var(--text-faint);
  font-size: 0.85rem;
  max-width: 640px;
}

/* Documents, Photos, and Specs (venue.php) — one shared wrapper section
   holding all three, rather than three separately-margined <section>s
   like before. Their toggle headings now sit together in one horizontal
   row (.section-tabs-row) — "one line" — instead of each heading sitting
   directly above its own content. Only one of the three panels
   (#documentList/#galleryGrid/#specsBody) can be expanded at a time:
   clicking any toggle collapses whichever of the other two was open
   before expanding (or just collapses itself, if it was already the one
   open) — see the shared accordion script in venue.php, which is the
   only thing that changed about how these three toggle; each panel's own
   internal behavior (item-level sign-in gating for Documents/Photos,
   category-level toggles and gating for Specs) is untouched. */
.venue-sections{ margin-top: 36px; }
.section-tabs-row{
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px 32px;
  margin-bottom: 18px;
}
/* .card-title normally supplies its own bottom margin (see its rule
   further down) for spacing below a standalone heading — not wanted here
   since these three headings sit side by side in a row, with the row
   itself (.section-tabs-row, above) providing the space below the whole
   row instead. */
.section-tabs-row .card-title{ margin: 0; }

/* Venue specs (venue.php) — Venue / Truck Loading / Bus Parking / Power /
   Rigging, each its own labeled group of production-info columns from
   `venue` itself (see $specs in venue.php). Reuses the same
   .detail-grid/.detail-item markup and styling as the summary specs near
   the top of the page (Address, Capacity, etc.) for one consistent
   label/value look across the whole page, rather than introducing a
   second table/list style just for this section. Loads collapsed and
   expands only when its "Specs" heading (reusing the same
   .gallery-toggle/.gallery-toggle-arrow toggle button styles as the
   Documents/Photos headings) is clicked, via the shared accordion script
   mentioned above (#specsToggle is one of the three panels it manages). */
/* #specsBody itself sets no `display` property (it's a plain wrapping
   <div>, block by default), so unlike .gallery-panel/.document-list it
   doesn't strictly need this override to actually hide — but every prior
   collapse/expand section on this page needed exactly this fix once
   something down the line ended up setting `display` on the collapsible
   element, so it's included defensively from the start here too. */
#specsBody[hidden]{ display: none; }
/* Same defensive [hidden]-vs-author-stylesheet fix as #specsBody above,
   this time for each category's own collapsible wrapper (see
   .specs-category-items in venue.php, which can hold more than one
   .detail-grid now — see .detail-grid-3col below — so the id/hidden pair
   moved from the <dl> itself onto a wrapping <div>). */
.specs-category-items[hidden]{ display: none; }
.specs-category{ margin-top: 28px; padding-left: 25px; }
.specs-category:first-of-type{ margin-top: 0; }
.specs-category-title{
  margin: 0;
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
/* The category title itself is now a .gallery-toggle button (see
   $specs/venue.php) so each category's items can collapse/expand
   independently of the section-level Specs toggle. .gallery-toggle
   hardcodes font-size:1.1rem/font-weight:700 to match the larger
   .card-title headings it's normally paired with (Documents/Photos/
   Specs) — this modifier hands font-size and font-weight back to the
   smaller .specs-category-title styling above instead, so the button
   still reads exactly like the plain uppercase category label it used
   to be, just clickable with an arrow next to it now. Color,
   text-transform, and letter-spacing were never overridden by
   .gallery-toggle in the first place, so those already inherit from
   .specs-category-title with no extra rule needed. */
.specs-category-toggle{
  font-size: inherit;
  font-weight: inherit;
}
/* .detail-grid already has its own top/bottom margin (see above); this
   collapses just the gap directly under a .specs-category-title so the
   category label and its first row of values sit close together, while
   still keeping .detail-grid's own bottom margin for spacing before
   whatever follows (the next category, or the end of the section). Targets
   .specs-category-items (the wrapping <div>, see venue.php) rather than
   .detail-grid directly, since a category can now render more than one
   .detail-grid in a row (Power's grouped rows below) — the wrapper is
   always the title's immediate next sibling regardless of how many grids
   are inside it. */
.specs-category-title + .specs-category-items{ margin-top: 12px; }
/* Tighter vertical spacing between item rows within a Specs category's
   grid than the shared `.detail-grid{gap:20px}` default — scoped to
   `.specs-category .detail-grid` specifically (rather than lowering
   .detail-grid's own gap) so the unrelated summary grid near the top of
   the page (Address, Capacity, Time Zone, etc., which also uses plain
   .detail-grid) keeps its original spacing untouched. Only row-gap is
   reduced; column-gap is left at the shared 20px so the two-column
   layout's left/right spacing is unaffected — "line spacing" here reads
   as the vertical gap between successive rows of items, not the gap
   between the two columns. */
.specs-category .detail-grid{ row-gap: 8px; }

/* Venue list (venues.php) — Country -> State collapsible tree that
   replaced the old search box/sortable-column table/pagination. Each
   State's own markup reuses .specs-category/.specs-category-title/
   .specs-category-toggle/.specs-category-items (just above) literally —
   see venues.php's own comment on why a State is structurally the same
   kind of thing as a Specs category, so no new CSS was needed for that
   level at all. Country, one level further out, isn't nested inside
   anything else collapsible the way a State is, so it gets its own
   (much simpler) spacing rule here instead of also borrowing
   .specs-category's 25px left-indent, which would wrongly indent every
   Country name relative to the page's own margin. */
.venue-tree{ margin-top: 12px; }
.venue-tree-country{ margin-top: 28px; }
.venue-tree-country:first-of-type{ margin-top: 0; }
/* Same defensive [hidden]-vs-author-stylesheet override as #specsBody/
   .specs-category-items above — .venue-tree-country-body is a plain
   block-level <div> with no `display` of its own, so it doesn't
   strictly need this to actually hide, but every other collapsible
   wrapper on this site carries this same override from the start rather
   than waiting to find out the hard way that something downstream ends
   up setting `display` on it after all (see .gallery-grid[hidden]'s own
   comment for exactly that story). */
.venue-tree-country-body[hidden]{ display: none; }
/* The venue-count label next to each Country/State name (e.g. "(42
   venues)") reuses .cap-label as-is — see venues.php's own comment on
   why. .gallery-toggle's own `gap: 8px` between its flex children
   already gives this the same breathing room on both sides as the arrow
   gets, so no extra spacing rule is needed just for this label. */

/* Venue documents (venue.php) — lists venue_uploads.file_name as
   downloadable links, built from UPLOAD_BASE_URL + the file name (see
   config.php), the same pattern PHOTO_BASE_URL uses for photos. Loads
   collapsed and expands only when its "Documents" heading (one of the
   three in .section-tabs-row above) is clicked.

   .document-list is the panel's own outer wrapper — the element the
   accordion script actually shows/hides (id="documentList") — not the
   <ul> of documents itself anymore. It used to be the <ul> directly,
   back when this panel held nothing but that list; now that a signed-in
   visitor can also see an "Add Document" button here (even when there
   are zero documents yet), the id/hidden toggling moved up to a wrapping
   <div>, and the actual list of files is the plain <ul class=
   "document-items"> below. The flex/column/gap layout stays on
   .document-list exactly as before — it's still what stacks this panel's
   children (the Add Document button, then the list or the "no documents"
   message) in a single column with even spacing. */
.document-list{
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* Same fix as `.gallery-grid[hidden]` below, and needed for the same
   reason: `.document-list{display:flex}` above has the exact same CSS
   specificity as the browser's own `[hidden]{display:none}` rule, so
   without this, `hidden` would be silently overridden and the list would
   stay visible even when collapsed — this was an actual shipped bug on
   the Photos gallery the first time around, so it's fixed here from the
   start rather than repeating that mistake. */
.document-list[hidden]{ display: none; }
/* The actual <ul> of document <li>s inside .document-list above — carries
   the plain-list-reset + stacking rules .document-list itself used to
   have back when it was the <ul>. Omitted entirely (along with its
   parent <ul>) when a venue has no documents yet; see .document-empty
   below for what shows instead. */
.document-items{
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.document-item{
  margin: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 10px;
}
/* Wraps the "Add Document" button (signed-in visitors only — see
   is_signed_in() around it in venue.php). align-self keeps the button
   its own natural width instead of stretching to fill .document-list's
   full width, which is what align-items' flex default (stretch) would
   otherwise do to every direct child of a column flex container. */
.document-actions{ align-self: flex-start; }
/* Shown instead of .document-items when a signed-in visitor opens a
   venue's Documents panel and nothing has been uploaded for it yet —
   same dimmed, small-text treatment as .document-description below so it
   reads as a quiet placeholder, not an error or a real document row. */
.document-empty{
  margin: 0;
  font-size: 0.85rem;
  color: var(--text-dim);
}
/* .document-link is shared between a real <a> (signed-in, downloadable)
   and a <button onclick="showRegisterNotice()"> (not signed in, gated —
   see $canEnlargePhotos in venue.php) — same pattern as .gallery-item
   being shared between <a> and <button> above. `font: inherit` and
   `cursor: pointer` matter here specifically because a bare <button>
   otherwise renders in the browser's default UI font and cursor, not
   this page's — without them the button variant would look subtly
   different from the link variant instead of identical. */
.document-link{
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-radius: 8px;
  border: 1px solid var(--card-border);
  background: var(--card);
  color: var(--text);
  text-decoration: none;
  font: inherit;
  font-size: 0.95rem;
  text-align: left;
  cursor: pointer;
  word-break: break-word;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.document-link:hover{ border-color: var(--accent); color: var(--accent); }
.document-icon{ font-size: 1.05rem; flex: none; }
/* The Address field's "View on Map" control — originally this reused
   .document-link/.document-icon as a plain text link (see venue.php's own
   history on this), but a later request turned it into a small (~96px)
   live map preview sitting to the right of the address text instead, so
   that text-link styling no longer applies here; see .address-with-map/
   .map-thumb below for its replacement. */
/* justify-content is deliberately left at its flex-start default (not
   space-between) and .address-text (below) deliberately does NOT grow to
   fill the row — together those two would stretch .map-thumb all the way
   out to .detail-item-wide's far right edge, which reads as "a map
   somewhere off to the side of the field" rather than "a map of this
   address," especially once the field's own two-column grid cell is
   wider than the address text itself. Packing both items at the row's
   start keeps the thumbnail anchored near the text it's a map of, no
   matter how wide that row gets — the 75px gap between them (below) is a
   deliberate, fixed amount of breathing room, not leftover row width, so
   it stays the same 75px whether the row is barely wide enough or much
   wider than it needs to be. */
.address-with-map{
  display: flex;
  align-items: flex-start;
  gap: 75px;
}
/* flex: 0 1 auto (no grow) — .address-text takes only the width its own
   text actually needs, rather than stretching to fill the flex row and
   shoving .map-thumb out to the row's far edge (see the comment on
   .address-with-map above for why that matters here specifically).
   min-width: 0 lets it still shrink/wrap below that natural width when
   there genuinely isn't room for both items — without it, a long street
   address on a narrow .detail-item-wide column (e.g. a mid-size screen,
   above the mobile breakpoint that stacks these two vertically) could
   push .map-thumb's fixed 96px box partially out of view instead of the
   text wrapping normally. */
.address-text{ flex: 0 1 auto; min-width: 0; }
/* The map-thumbnail "button" itself — a fixed 96x96 box (the request's
   own "approx 96px x 96px"), not just a scaled-down version of
   map.php's own full map: flex: none keeps it from being squeezed
   narrower than 96px by its flex sibling above, and it's deliberately a
   plain <div> (role="link"+tabindex, see venue.php's own comment on this
   element) rather than a real <a>, since Leaflet's pointer/touch handlers
   attach to the element it's given and a nested focusable/interactive
   Leaflet map inside a real <a> is its own source of double-handled
   clicks — venue.php's init script attaches one explicit click/keydown
   listener to this div instead. position: relative lets
   .map-thumb-status (below) overlay it exactly while the preview loads
   (or if it fails).

   isolation: isolate is load-bearing, not decorative — Leaflet gives its
   internal panes (tile pane, marker pane, popup pane, etc.) their own
   hefty z-index values (200 up to 1000 for controls), set directly via
   inline style on elements positioned absolute inside the map container.
   Those z-index values only ever compete against OTHER elements within
   the nearest ancestor that itself establishes a stacking context; a
   plain `position: relative` with no z-index of its own (what this rule
   had before) does NOT establish one, so Leaflet's panes fell straight
   through into the page's own root stacking context and stacked directly
   against it there — which is exactly how a 96x96 map thumbnail ended up
   rendering on top of the "Edit Venue" modal (`.modal-overlay`, z-index:
   100 — well below Leaflet's own 200-1000 range once nothing was
   containing it). `isolation: isolate` forces .map-thumb to start a new
   stacking context of its own without needing an arbitrary z-index
   number to "win" — nothing inside it can ever escape this box and stack
   above unrelated page elements like a modal again, however high
   Leaflet's own internal values go. */
.map-thumb{
  flex: none;
  width: 96px;
  height: 96px;
  position: relative;
  isolation: isolate;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--card-border);
  cursor: pointer;
  background: var(--card);
}
.map-thumb:hover,
.map-thumb:focus-visible{ border-color: var(--accent); }
/* Leaflet needs a container with real, non-zero pixel dimensions the
   moment L.map() initializes (same reasoning as map.php's own #mapView
   and photo360.php's #panoViewer) — here that's simply "fill the fixed
   96x96 box above" rather than a separate height rule of its own. */
.map-thumb-canvas{ width: 100%; height: 100%; }
/* Overlays the whole box with "Loading map…" (or, on failure, "Map
   unavailable") until venue.php's init script either hides this
   (success) or leaves it in place with updated text (failure) — same
   absolute-overlay approach as .viewer-flash-ish status text elsewhere,
   scoped to this box specifically via position: absolute + inset: 0
   rather than pushing the map canvas down to make room for it, since
   there's no spare height to give up inside a fixed 96px box. */
.map-thumb-status{
  position: absolute;
  inset: 0;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 8px;
  text-align: center;
  font-size: 0.72rem;
  line-height: 1.3;
  color: var(--text-dim);
  background: var(--card);
}
.map-thumb-status[hidden]{ display: none; }
@media (max-width: 480px){
  /* .detail-grid itself already collapses to one column at this
     breakpoint (see above) — stacking the address text above the
     thumbnail here too (instead of keeping them side by side in a
     narrowing flex row) keeps the thumbnail from being squeezed against
     wrapped address text on a phone-width screen. The 75px gap above is
     sized for sitting between the two SIDE BY SIDE — once they're
     stacked vertically instead, that same 75px would just be 75px of
     empty scroll space between the address and its map, so it's
     overridden down to a plain spacing-sized gap here instead. */
  .address-with-map{
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }
}
/* venue_uploads.description, shown next to the file name/link rather than
   as part of the clickable link itself — same row when there's room
   (.document-item is a wrapping flex row), wrapping to its own line below
   the link on narrow screens instead of being clipped or overlapping it. */
.document-description{
  font-size: 0.85rem;
  color: var(--text-dim);
}
/* Wraps each document's Edit/Delete buttons (venue.php, signed-in
   visitors only). margin-left: auto is what actually pushes these to
   the right-hand edge of .document-item's row — without it they'd just
   sit immediately after whichever of .document-link/.document-description
   happens to come before them, same flex-row reasoning as
   .document-actions above. flex-wrap: nowrap keeps Edit/Delete side by
   side rather than stacking if space gets tight; the whole row
   (.document-item) already wraps onto a new line first in that case. */
.document-item-actions{
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
  margin-left: auto;
}

/* Venue photo gallery (venue.php) */
/* The "Photos" heading is itself the collapse/expand toggle — a <button>
   inside the <h2> rather than a separate control, so the whole label (not
   just the small arrow glyph) is clickable. Button styles are reset to
   inherit the surrounding .card-title look exactly, so it reads as plain
   heading text with an arrow next to it, not as a form control. */
.gallery-toggle{
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  font-size: 1.1rem;
  font-weight: 700;
  color: inherit;
  cursor: pointer;
}
.gallery-toggle:hover{ color: var(--accent); }
.gallery-toggle-arrow{
  display: inline-block;
  font-size: 1.6em;
  transition: transform 0.2s ease;
}
/* Points right (collapsed, the default) and rotates to point down once the
   grid below is expanded — aria-expanded is the source of truth; this class
   just mirrors it for the rotation animation. */
.gallery-toggle-arrow{ transform: rotate(-90deg); }
.gallery-toggle.is-expanded .gallery-toggle-arrow{ transform: rotate(0deg); }
/* Wraps the "Add Photo" button (venue's owner only — see $isVenueOwner
   around it in venue.php) together with .gallery-grid below (or
   .gallery-empty, for an owner with nothing uploaded yet) in a single
   column — the id/hidden pair the accordion script toggles lives HERE
   now, not on .gallery-grid itself (see the Add Photo feature's own
   comment in venue.php for why this moved, the same restructure
   .document-list already went through for "Add Document"). Same flex/
   column/gap shape as .document-list above, for the same reason. */
.gallery-panel{
  display: flex;
  flex-direction: column;
  gap: 16px;
}
/* Same fix, same reason, as `.document-list[hidden]` above: without
   this, `[hidden]` would be silently overridden by this element's own
   `display: flex` and the panel would stay visible even while collapsed
   — this was an actual shipped bug on this exact panel the first time
   around (back when .gallery-grid itself carried the id/hidden pair —
   see its own history below), so it's included defensively from the
   start on the new wrapper too. */
.gallery-panel[hidden]{ display: none; }
/* Mirrors .document-actions above — align-self keeps the "Add Photo"
   button its own natural width instead of stretching to fill
   .gallery-panel's full width (align-items' flex default). */
.gallery-actions{ align-self: flex-start; }
/* Shown instead of .gallery-grid when this venue's OWNER opens the
   Photos panel and nothing has been uploaded for it yet — same quiet-
   placeholder treatment as .document-empty above. */
.gallery-empty{
  margin: 0;
  font-size: 0.85rem;
  color: var(--text-dim);
}
.gallery-grid{
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 12px;
}
/* STILL NEEDED — correcting an earlier, inaccurate version of this
   comment that claimed this override was dead code. The regular Photos
   gallery's own .gallery-grid stopped carrying `hidden` once that
   feature's "Add Photo" refactor moved the id/hidden pair up onto the
   wrapping .gallery-panel instead (see that class above). But the 360
   Photos panel below (`#photos360Grid` in venue.php) was NOT part of
   that refactor — it has no "Add 360 Photo" feature, so it was never
   given a .gallery-panel wrapper, and still carries the `id`/`hidden`
   pair directly on a bare `<div class="gallery-grid" id="photos360Grid"
   hidden>`, exactly like .gallery-grid itself used to. Removing this
   rule (as an earlier version of this file briefly did) silently broke
   that: `[hidden]` and this rule's own `display:grid` collide at equal
   CSS specificity, and this author rule wins (loads after the browser's
   built-in stylesheet) — so #photos360Grid rendered permanently
   visible regardless of its `hidden` attribute, the exact same class of
   bug `.document-list[hidden]`/`.gallery-panel[hidden]`/`#specsBody[hidden]`
   all defend against elsewhere in this file. Keep this rule as long as
   anything in venue.php puts `hidden` directly on a bare `.gallery-grid`
   — today, that's only `#photos360Grid`. */
.gallery-grid[hidden]{ display: none; }
.gallery-item{
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: 10px;
  border: 1px solid var(--card-border);
  background: var(--card);
  padding: 0;
  cursor: pointer;
  font: inherit;
}
.gallery-item img{
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  pointer-events: none;
}
/* Wraps a signed-in visitor's clickable .gallery-item (the <a>) together
   with its optional caption (venue_photos.description) — only present in
   that branch of the gallery markup; the non-signed-in / unverified
   "register to enlarge" branch uses a bare <button class="gallery-item">
   with no wrapper, since it has no caption to attach. */
.gallery-item-wrap{ margin: 0; }
/* Always visible — shown under its thumbnail from the moment the Photos
   panel is opened, the same as a document's description in the
   Documents list just above it, rather than needing a click on any
   photo first. A photo with an empty/blank `description` simply has no
   `<figcaption>` rendered for it at all (see venue.php), so there's
   nothing here for that one. */
.gallery-caption{
  margin-top: 6px;
  font-size: 0.85rem;
  line-height: 1.35;
  color: var(--text-dim);
}

/* 360 Photos panel (venue.php, venue_360) reuses .gallery-grid/.gallery-item
   from the regular Photos gallery above for the same grid-of-thumbnails
   look and the same clickable-thumbnail behavior — same column sizing,
   aspect ratio, rounded-corner framing, gating, and <a>/<button> pattern —
   rather than inventing a second grid style. Clicking a thumbnail opens
   photo360.php (a full-screen 360° pano viewer) in a new tab instead of
   navigating to photo.php; see venue.php and photo360.php for that. No
   extra CSS is needed here beyond what .gallery-item already provides.
*/

/* Full-screen "viewer" page layout — shared by photo.php and
   photo360.php, the site's two full-screen media viewers, each opened in
   a new tab from venue.php's gallery thumbnails. Scoped to
   body.viewer-page rather than the bare html/body elements, so it has no
   effect on any other page — every other page keeps its normal
   scrolling document layout, with the shared site <header>/<footer> and
   the .wrap/.page content width. Neither viewer page has a <footer> or
   uses .wrap/.page for its own content (only for their "Venue Not Found"
   fallback) — that's deliberate: the whole point of "full screen" here
   is using as much of the browser window as possible for the actual
   photo/pano, not the usual page chrome. */
body.viewer-page{
  height: 100vh;
  margin: 0;
  display: flex;
  flex-direction: column;
}
/* `height` here has to be a DEFINITE value (100vh), not `min-height` —
   flexbox's flex-grow (used by .viewer-frame below to fill the rest of
   the window) only distributes space when the flex container's own
   main-size is definite; give a column flex container only a min-height
   and its "auto" height instead sizes to its content's own flex-basis,
   which for .viewer-frame means "however tall the photo/pano naturally
   wants to be" — so the frame stops filling the window and instead grows
   to match the image's own aspect ratio (this exact regression shipped
   once already, caught by measuring a portrait test photo's frame at
   2133px tall in an 800px-tall viewport before being caught and fixed).
   `height: 100vh` doesn't clip an unusually tall "Venue Not Found" page
   either — plain `height` doesn't crop overflowing content the way
   `overflow: hidden` would (deliberately not set here), so content
   taller than 100vh still just overflows and the page scrolls normally
   to show it, same as if this rule weren't here at all. */
.viewer-title{
  flex: 0 0 auto;
  margin: 0;
  padding: 14px 20px;
  font-size: 1rem;
  font-weight: 700;
  color: var(--text-dim);
  background: var(--card);
  border-bottom: 1px solid var(--card-border);
}
/* photo.php only — photo360.php's own .viewer-title has no
   .viewer-title-row inside it, just a plain text node, so none of this
   ever applies there. This grid lives one level INSIDE .viewer-title
   rather than on .viewer-title itself specifically so .viewer-title's
   own display (shared with photo360.php) never has to change — making
   the shared bar itself a grid container would auto-place photo360.php's
   lone text node into just the first column instead of leaving it
   centered the way it already was.

   grid-template-columns: 1fr auto 1fr is what actually centers the
   middle (description) column on the bar's true center regardless of
   how long the left (venue/photo-count) or right (date) column's own
   text is — the two 1fr tracks always split whatever space the auto
   column doesn't need exactly evenly, unlike a flex row with
   justify-content: space-between, where the middle item only lands on
   the real center if the two side items happen to be the same width as
   each other. column-gap keeps the three from ever touching directly if
   the center and a side column's content both run long. */
.viewer-title-row{
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  column-gap: 16px;
}
/* Explicit grid-column on .viewer-title-left/.viewer-title-actions
   (1 and 3) — .viewer-title-center (2) always renders now that the date
   lives there too (see below), so it's no longer the kind of
   conditionally-absent element that needs a fixed column to avoid
   auto-placement drift. .viewer-title-actions is still exactly that
   kind of element (owner-only — see photo.php's $isVenueOwner), so it
   still gets one: without an explicit grid-column: 3 here, a non-owner
   visitor's bar (left + center only, no third element in the DOM at
   all) would just naturally end with two columns used and the third
   1fr track empty — which is actually the correct, intended result, but
   relying on that happening to work rather than stating it explicitly
   is the same kind of fragile assumption that caused a real bug in an
   earlier version of this bar (see git history/this file's own past
   comments on .viewer-title-date's auto-placement). */
.viewer-title-left{
  grid-column: 1;
  text-align: left;
}
/* Wraps the description (when this photo has one) AND the "Posted
   <date>" text together, centered as a single group — moved in here
   from its own right-hand column so the date reads as "when this
   caption was written" rather than a separate, unrelated fact pinned to
   the opposite edge of the bar. Always renders (every photo has SOME
   posted date, even one with no description), or a blank/missing
   photo_updated would show as fmt_date()'s own em-dash fallback — never
   fully empty — so unlike the old two-item version of this column,
   there's no auto-placement risk here (see .viewer-title-left's own
   comment above). flex-wrap: wrap matches .viewer-title-actions' own
   choice for the same reason: a long description plus the date are more
   likely to need a second line at this bar's narrower widths than a
   single short text node ever was. */
.viewer-title-center{
  grid-column: 2;
  display: flex;
  align-items: baseline;
  justify-content: center;
  flex-wrap: wrap;
  column-gap: 8px;
}
/* font-weight: 400 (vs. .viewer-title's own 700 — inherited by
   .viewer-title-left unless overridden) so the description reads as a
   secondary detail next to the bolder venue name/photo count on the
   left, the same weight relationship the date already had. */
.viewer-title-description{
  font-weight: 400;
  color: var(--text-faint);
}
/* A plain middle dot between the description and the date — only
   rendered alongside the description (see photo.php: both live inside
   the same `if ($currentPhoto['description'] !== '')` block), so a
   photo with no description never shows a stray separator with nothing
   on one side of it. aria-hidden since it's purely a visual divider —
   the two text nodes it sits between are perfectly readable to a screen
   reader without it. */
.viewer-title-sep{
  color: var(--text-faint);
  opacity: 0.6;
}
.viewer-title-date{
  font-weight: 400;
  font-size: 0.85rem;
  color: var(--text-faint);
  white-space: nowrap;
}
/* Edit Description/Set as Main Photo/Delete Photo — owner only (see
   photo.php's $isVenueOwner). flex-wrap: wrap rather than nowrap
   (contrast .document-item-actions below, which never wraps) because
   three full-text button labels in a single right-aligned row, at the
   narrower end of this bar's non-mobile width range, are more likely to
   need a second line than a document row's plain "Edit"/"Delete" ever
   would; justify-content: flex-end keeps that second line right-aligned
   too instead of drifting left. */
.viewer-title-actions{
  grid-column: 3;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: 8px;
}
/* The inline one-button form around "Set as Main Photo" (see photo.php)
   needs to sit flush alongside its sibling buttons in
   .viewer-title-actions' flex row, not stack above/below them the way a
   bare <form> (a block-level element by default) otherwise would. */
.viewer-title-inline-form{
  display: inline-flex;
}
/* Shown instead of a "Set as Main Photo" button when the photo on screen
   already IS this venue's main photo (see $isCurrentPhotoMain in
   photo.php) — a plain label rather than a disabled button, since a
   disabled <button> would need its own focus/contrast handling to stay
   accessible and this reads just as clearly without any of that. */
.viewer-title-main-badge{
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--text-faint);
  padding: 7px 14px;
  border: 1px solid var(--card-border);
  border-radius: 8px;
}
@media (max-width: 640px){
  /* Three side-by-side columns of independently-variable-length content
     (a venue name, a description+date pair, a row of buttons) have no
     single narrow-width arrangement that keeps everything on one line
     without cramming — stacking them instead, each its own centered
     line, is the same tradeoff the previous (two-zone) version of this
     bar made at this breakpoint. */
  .viewer-title-row{
    grid-template-columns: 1fr;
    row-gap: 2px;
  }
  .viewer-title-left,
  .viewer-title-center,
  .viewer-title-actions{
    /* Without resetting this back to column 1, the desktop rule's
       explicit grid-column: 2/3 above would force this single-column
       grid to generate extra IMPLICIT columns just to satisfy those
       indices, instead of actually stacking all three into one column
       as intended at this width. */
    grid-column: 1;
    text-align: center;
  }
  .viewer-title-center,
  .viewer-title-actions{
    justify-content: center;
  }
}
/* The flash messages photo.php now renders (edit_photo.php's/
   set_main_photo.php's/delete_photo.php's redirect results) — .flash
   itself has no horizontal margin of its own (it's normally rendered
   inside venue.php's padded <main class="wrap page">, which photo.php's
   full-bleed viewer layout has no equivalent of), so this adds the same
   horizontal breathing room .viewer-title already has via its own
   padding, plus a little top space since there's no element above it on
   this page playing that role. */
.viewer-flash{
  margin: 14px 20px 0;
}
/* The space below the title bar, filling the rest of the browser window.
   flex: 1 1 auto alone isn't enough to make a flex child actually shrink
   to the space available — without min-height: 0 it instead grows to fit
   its content's natural size (a photo or pano that can be larger than
   the viewport), pushing part of it off the bottom of the screen instead
   of filling exactly the space available. position: relative is here so
   photo.php's Prev/Next arrows (below) can anchor to this frame's own
   edges regardless of what's inside it. */
.viewer-frame{
  flex: 1 1 auto;
  min-height: 0;
  position: relative;
}

/* Full-size photo viewer (photo.php) — the photo fills as much of
   .viewer-frame as it can while keeping its aspect ratio, via
   object-fit: contain on the <img> itself, both enlarging photos smaller
   than the available space and shrinking ones larger than it — this
   page has always scaled photos up as well as down (never just shown
   them at native size), just previously to a fixed 1080px target instead
   of the actual browser window. object-fit: contain handles portrait and
   landscape identically, so unlike this page's earlier version there's
   no JS needed to read the image's naturalWidth/naturalHeight and toggle
   a class for each case — it's pure CSS now. .photo-viewer-stage centers
   the image within .viewer-frame and clips anything object-fit doesn't
   already contain (it never actually overflows, since object-fit:
   contain guarantees the image fits, but the browser's own use of
   intrinsic image size before it decides otherwise is contained here as
   a safety net). */
.photo-viewer-stage{
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.photo-viewer-stage img{
  width: 100%;
  height: 100%;
  object-fit: contain;
}
/* The Prev/Next arrows are positioned absolutely, pinned to the left/
   right edges of .viewer-frame itself (see above), rather than laid out
   as flex siblings of the image. That's deliberate, not just a style
   choice: flexbox centers an overflowing row on its own midpoint, which
   pushes part of that overflow off in BOTH directions from center —
   this shipped once as exactly that bug, with the prev arrow rendering
   off the left edge of the frame and the next arrow past the right,
   neither ever visible or clickable. Absolute positioning sidesteps that
   entirely — the arrows always sit at the frame's own edges no matter
   the image's size or the browser window's size. */
.photo-nav-arrow{
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.25);
  background: rgba(10, 11, 15, 0.65);
  color: #fff;
  font-size: 1.1rem;
  text-decoration: none;
  line-height: 1;
  transition: background 0.15s ease;
}
.photo-nav-arrow:hover{
  background: var(--accent);
  border-color: var(--accent);
}
.photo-nav-prev{ left: 12px; }
.photo-nav-next{ right: 12px; }
@media (max-width: 640px){
  .photo-nav-arrow{ width: 40px; height: 40px; font-size: 1rem; }
}

/* Shared base for this project's JS modal dialogs (venue.php's "you must
   register" notice, register_modal.php's registration form, and any
   future one). Shown/hidden via the `hidden` attribute, not a CSS
   display toggle. The .is-fading class below is opt-in infrastructure
   for a modal that wants to animate itself out via JS before setting
   `hidden` again — venue.php's notice modal doesn't currently use it
   (it now holds "Register"/"Sign In" links a visitor needs time to act
   on, so it stays open until manually dismissed), but the rule stays
   here for any modal that does. */
.modal-overlay{
  position: fixed;
  inset: 0;
  background: rgba(10, 11, 15, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 100;
  opacity: 1;
  transition: opacity 0.4s ease;
}
/* The plain .modal-overlay rule above sets display: flex unconditionally,
   which — because author styles win over the browser's own [hidden]
   default regardless of selector specificity — silently overrides the
   `hidden` attribute and leaves the modal visible even while `hidden` is
   set. This rule restores that default explicitly so JS toggling the
   `hidden` attribute (modal.hidden = true/false) actually shows/hides it. */
.modal-overlay[hidden]{
  display: none;
}
.modal-overlay.is-fading{
  opacity: 0;
  pointer-events: none;
}
.modal-box{
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: 14px;
  padding: 28px;
  max-width: 420px;
  width: 100%;
  text-align: center;
}
.modal-box p{ margin: 0 0 22px; color: var(--text); font-size: 1.02rem; }
.modal-actions{ display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }

/* Pagination */
.pagination{
  display: flex; align-items: center; justify-content: center; gap: 16px;
  margin-top: 28px;
}
.page-status{ color: var(--text-faint); font-size: 0.85rem; }

@media (max-width: 640px){
  .header-inner{ flex-wrap: wrap; height: auto; padding: 14px 0; gap: 12px; }
}

/* ===== Home page ===== */
/* Sits directly under the header, outside <main>, for signed-out visitors
   only (see the !is_signed_in() check around it in index.php) — the same
   slot the project's old .temp-signin-notice banner used to occupy before
   it was removed. Uses --accent-soft/--card-border rather than --card so
   it reads as a callout band rather than another content card. */
.register-banner{
  background: var(--accent-soft);
  border-bottom: 1px solid var(--card-border);
}
.register-banner-inner{
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
  padding: 14px 24px;
  text-align: center;
  flex-wrap: wrap;
}
.register-banner-inner p{
  margin: 0;
  font-weight: 700;
  color: var(--text);
}
/* Scoped to this one banner rather than shrinking .btn-primary globally —
   every other "Register" button on the site (nav, hero, venue.php's
   popup) should keep the standard .btn size; only this banner's button
   is smaller. Measured against the unscoped .btn-primary (~45.6px tall
   with this project's fonts) to land at roughly half that height (~22.7px)
   rather than just guessing at a padding value. */
.register-banner .btn-primary{ padding: 1px 14px; font-size: 0.78rem; }
@media (max-width: 640px){
  .register-banner-inner{ flex-direction: column; gap: 12px; padding: 16px 24px; }
}
/* Fades out automatically 10s after render (see the inline script right
   after #homeFlashWrap in index.php) — opacity: 1 here is the starting
   state the transition animates from, not just a reset. Unlike
   .modal-overlay above, this element has no display override of its own,
   so the browser's default `[hidden]{ display: none }` already applies
   correctly once JS sets the hidden attribute — no [hidden] override rule
   is needed here. Keep the transition duration equal to FADE_DURATION_MS
   in that script. */
.home-flash-wrap{ max-width: var(--maxw); margin: 0 auto; padding: 20px 24px 0; opacity: 1; transition: opacity 0.4s ease; }
.home-flash-wrap.is-fading{ opacity: 0; pointer-events: none; }
.hero{
  position: relative;
  padding: 96px 0 80px;
  overflow: hidden;
  border-bottom: 1px solid var(--card-border);
}
.hero::before{
  content: "";
  position: absolute;
  inset: -20% -10% auto -10%;
  height: 480px;
  background: radial-gradient(60% 60% at 50% 0%, rgba(255,138,30,0.16), transparent 70%);
  pointer-events: none;
}
.hero-inner{
  position: relative;
  display: grid;
  grid-template-columns: 1fr 563px;
  gap: 48px;
  align-items: center;
}
@media (max-width: 860px){ .hero-inner{ grid-template-columns: 1fr; } }
.hero-copy{ max-width: 680px; }
.eyebrow{
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 0.78rem; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--accent-2); background: var(--accent-soft);
  border: 1px solid rgba(255,138,30,0.25);
  padding: 6px 14px; border-radius: 999px; margin-bottom: 22px;
}
.hero h1{
  /* 1.5x the previous clamp(0.96rem, 2.18vw, 1.52rem) — each of the
     three values scaled by 1.5, so the headline scales up proportionally
     across the whole responsive range instead of just at the min/max
     ends. */
  font-size: clamp(1.44rem, 3.27vw, 2.28rem);
  line-height: 1.08; letter-spacing: -0.02em; margin: 0 0 18px; font-weight: 800;
}
.hero h1 .accent{
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.hero .lede{ font-size: 1.08rem; color: var(--text-dim); max-width: 520px; margin: 0 0 32px; }
.hero-ctas{ display: flex; flex-wrap: wrap; gap: 12px; }
.hero-media{
  position: relative;
  aspect-ratio: 4 / 3;
}
.hero-media img{
  width: 100%; height: 100%;
  object-fit: contain;
  display: block;
}
@media (max-width: 860px){ .hero-media{ order: -1; } }

.section{ padding: 72px 0; }
.section-alt{ background: var(--bg-alt); border-top: 1px solid var(--card-border); border-bottom: 1px solid var(--card-border); }
.section-head{ max-width: 600px; margin: 0 0 40px; }
.section-head h2{ font-size: clamp(1.5rem, 2.8vw, 1.9rem); margin: 0 0 12px; font-weight: 800; letter-spacing: -0.01em; }
.section-head p{ color: var(--text-dim); margin: 0; }
.section-head p a{ color: var(--accent-2); text-decoration: underline; }

.feature-grid{ display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
@media (max-width: 860px){ .feature-grid{ grid-template-columns: 1fr; } }
.feature-card{
  background: var(--card); border: 1px solid var(--card-border);
  border-radius: var(--radius); padding: 26px;
}
.feature-card h3{ font-size: 1.02rem; margin: 0 0 8px; font-weight: 700; }
.feature-card p{ color: var(--text-dim); font-size: 0.92rem; margin: 0; }

.cta-band{ text-align: center; padding: 72px 0; }
.cta-band h2{ margin: 0 0 12px; font-size: clamp(1.5rem, 2.8vw, 1.9rem); }
.cta-band p{ color: var(--text-dim); margin: 0 0 28px; }
.cta-band .hero-ctas{ justify-content: center; }

.app-inner{ display: grid; grid-template-columns: 1fr auto; gap: 40px; align-items: center; }
@media (max-width: 720px){ .app-inner{ grid-template-columns: 1fr; text-align: left; } }
.app-copy h2{ margin: 0 0 12px; font-size: clamp(1.5rem, 2.8vw, 1.9rem); }
.app-copy p{ color: var(--text-dim); margin: 0 0 24px; max-width: 460px; }
.store-buttons{ display: flex; flex-wrap: wrap; gap: 12px; }
.store-btn{
  display: flex; align-items: center; gap: 10px;
  background: #000; border: 1px solid #2a2c33; color: #fff;
  padding: 10px 18px 10px 14px; border-radius: 10px; text-decoration: none;
}
.store-btn:hover{ border-color: #54586a; }
.store-btn svg{ width: 22px; height: 22px; flex-shrink: 0; }
.store-btn .store-text{ display: flex; flex-direction: column; line-height: 1.15; }
.store-btn .store-text small{ font-size: 0.62rem; color: #c7c9d1; letter-spacing: 0.03em; text-transform: uppercase; }
.store-btn .store-text strong{ font-size: 0.96rem; font-weight: 700; }

footer{ border-top: 1px solid var(--card-border); padding: 40px 0 30px; }
.footer-grid{ display: flex; justify-content: space-between; flex-wrap: wrap; gap: 28px; margin-bottom: 28px; }
.footer-brand p{ color: var(--text-faint); font-size: 0.85rem; max-width: 280px; margin: 10px 0 0; }
.footer-cols{ display: flex; gap: 48px; flex-wrap: wrap; }
.footer-col h4{ font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-faint); margin: 0 0 12px; }
.footer-col ul{ list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.footer-col a{ text-decoration: none; color: var(--text-dim); font-size: 0.88rem; }
.footer-col a:hover{ color: var(--text); }
.footer-bottom{
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: 10px;
  padding-top: 20px; border-top: 1px solid var(--card-border);
  color: var(--text-faint); font-size: 0.8rem;
}

/* ===== Crew Chat ===== */
.card{
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  padding: 24px;
  margin-bottom: 28px;
}
.card-title{ margin: 0 0 18px; font-size: 1.1rem; font-weight: 700; }

.flash{
  padding: 12px 16px;
  border-radius: 10px;
  font-size: 0.88rem;
  margin-bottom: 18px;
  border: 1px solid transparent;
}
.flash-success{ background: rgba(31,138,76,0.12); color: #3ecb7d; border-color: rgba(62,203,125,0.3); }
.flash-error{ background: rgba(209,69,59,0.12); color: #ff6d63; border-color: rgba(255,109,99,0.3); }

.field{ margin-bottom: 16px; }
.field label{
  display: block;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-dim);
  margin-bottom: 6px;
}
.field input[type="text"],
.field input[type="email"],
.field input[type="password"],
.field textarea,
.field select{
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--card-border);
  border-radius: 8px;
  font-family: inherit;
  font-size: 0.94rem;
  background: var(--bg);
  color: var(--text);
}
/* `select` specifically needs `width: 100%` here, not just the shared
   styling — without it, an unstyled <select> sizes its own closed-box
   width to fit its WIDEST <option> text (Chromium and most other
   browsers both do this), not to its container. The Edit Venue form's
   only <select> (#edit_v_timezone, right above) is built from however
   long venue_timezones.timezone_GMT/timezone_city happen to be on the
   real server — a handful of long combined labels ("GMT-05:00 — Mexico
   City, Bogotá, Lima" and similar) is all it takes to make the select
   itself render wider than .edit-venue-modal-box, which was the actual
   root cause of that modal needing horizontal scrolling to be reported
   as a bug: every other field in this form already had width:100% (see
   the rule above), so the select was the one place a surprise-long value
   could silently blow out the box's width. This caps it at 100% of
   .field's own width (whatever the modal's current max-width is)
   regardless of how long any future option text gets — the option list
   itself can still show its full text when opened; only the select's own
   CLOSED width is constrained here. */
/* A native <input type="file"> (the Add Document form's PDF picker)
   can't take the same border/background/padding treatment as the text-
   style inputs above without looking broken in most browsers — this
   just keeps it full-width and visually in-line with the rest of the
   form (font size, dimmed text color) without fighting the browser's
   own "Choose File" button chrome. */
.field input[type="file"]{
  display: block;
  width: 100%;
  font-family: inherit;
  font-size: 0.88rem;
  color: var(--text-dim);
}
/* The Add Document form's drag-and-drop PDF picker (venue.php). This is
   a <label> (so clicking anywhere on it activates the <input> it
   contains, with no JS needed) styled as a dashed-border drop target,
   with the actual <input type="file"> stretched to cover it completely
   but made invisible — not swapped out for a plain, non-form <div> — so
   the native keyboard/click/file-picker behavior and no-JS fallback
   still work normally. A file dropped onto the zone is handled by
   venue.php's script rather than left to the browser's native drop
   action on the input (that wasn't reliable for an invisible,
   absolutely-positioned input — see that script's comments), which is
   also what gives us the .is-dragover highlight below and the
   .file-dropzone-text wording swap once a file's chosen or dropped. */
.file-dropzone{
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 22px 16px;
  border: 2px dashed var(--card-border);
  border-radius: 10px;
  text-align: center;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.file-dropzone:hover{ border-color: var(--accent); }
/* Toggled by venue.php's script on dragenter/dragleave/drop — CSS alone
   has no way to style "a file is currently being dragged over this
   element" (there's no native :drag-over pseudo-class), so this one
   piece of the dropzone's look needs JS either way. */
.file-dropzone.is-dragover{
  border-color: var(--accent);
  background: var(--accent-soft);
}
.file-dropzone-input{
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.file-dropzone-icon{ font-size: 1.4rem; }
.file-dropzone-text{
  font-size: 0.88rem;
  color: var(--text-dim);
}
/* Swapped in by venue.php's script (replacing the "Drag a PDF here..."
   wording) once a file's been chosen or dropped, so .field-hint below it
   isn't the only confirmation a visitor gets that their file registered. */
.file-dropzone-text.has-file{ color: var(--text); font-weight: 600; }
.field input[type="text"]:focus,
.field input[type="email"]:focus,
.field input[type="password"]:focus,
.field textarea:focus,
.field select:focus{
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
/* Was .crewchat-form .field-row — generalized to a plain .field-row once
   register.php's form needed the exact same 2-column layout for its
   First/Last Name row; both forms get it identically from one rule now. */
.field-row{ display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 560px){ .field-row{ grid-template-columns: 1fr; } }
.field-actions{ margin-top: 4px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.field-captcha input[type="text"]{ max-width: 100px; }
.field-hint{ margin: 6px 0 0; font-size: 0.78rem; color: var(--text-faint); }

/* ===== Sign In ===== */
.signin-card{ max-width: 400px; }
.password-field{ position: relative; }
.password-field input[type="password"],
.password-field input[type="text"]{ padding-right: 64px; }
.password-toggle{
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  background: none;
  border: none;
  color: var(--accent);
  font-size: 0.82rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  padding: 6px 8px;
}
.password-toggle:hover,
.password-toggle:focus{ text-decoration: underline; outline: none; }

/* Honeypot: present in the DOM (so simple bots fill it) but invisible and
   unreachable by keyboard/screen reader for real visitors. */
.hp-field{ position: absolute; left: -9999px; top: -9999px; }

/* ===== Register ===== */
/* register.php's own full-page card — wider than .signin-card since the
   First/Last Name row needs the room (see the generalized .field-row
   above), but still well short of .wrap's full 1160px. */
.register-card{ max-width: 560px; }
/* The register modal (register_modal.php) reuses .modal-overlay/.modal-box
   from the shared modal rules above wholesale — same overlay, same card
   background/border/radius/padding — this just overrides the two things
   that don't fit a multi-field form: .modal-box's default 420px max-width
   (too narrow for the Name row) and its centered text alignment (fine for
   a one-line notice, not for a form). */
.register-modal-box{ max-width: 520px; text-align: left; }
.register-modal-box .card-title{ text-align: center; }
/* The "Add Document" modal (venue.php) — same reasoning and same override
   as .register-modal-box just above: .modal-box's default centered text
   suits a one-line notice, not a form with labeled fields. */
.add-document-modal-box{ max-width: 440px; text-align: left; }
.add-document-modal-box .card-title{ text-align: center; }
/* The "Edit Venue" modal (venue.php) — originally just reused
   .add-document-modal-box wholesale, which was fine while this form was
   short enough to always fit the viewport. It grew past that point (9
   fields: Street Address, City, State, Zip Code, Country, Time Zone,
   Nearest Major City, Capacity, Previous Name) without ever getting its
   own max-height/overflow — on a shorter screen the overlay's own
   `align-items: center` (see .modal-overlay above) centers a box taller
   than the viewport by pushing it off both the top AND the bottom
   equally, with nothing on the page able to scroll to reach either end:
   the heading disappears above the fold, the Save/Cancel buttons
   disappear below it, and .modal-box itself has no scrollbar of its own
   to recover with. Same fix as .edit-specs-modal-box below (which hit
   this same ceiling first, being by far the largest form in this app) —
   max-height+overflow-y makes the FORM scroll within a fixed-height
   modal instead of the modal itself silently growing past the viewport.

   Widened from the original 440px (same as .add-document-modal-box) to
   520px (same as .register-modal-box) after a second, separate bug
   report of horizontal scrolling inside this specific modal — the real
   root cause of THAT was the Time Zone <select> (see `.field select`
   above): with no `width: 100%` of its own, an unstyled <select> sizes
   its closed box to fit its single widest <option>, not its container,
   so a long enough `venue_timezones.timezone_GMT`/`timezone_city`
   combination could silently render wider than the box around it. That
   select rule is the actual fix for the overflow itself; this width
   increase is in addition to it, both because it was requested
   explicitly and because it gives every field in this now-9-field form
   (especially Street Address and Previous Name, the two longest-text
   fields here) more comfortable room regardless of what any particular
   venue's data happens to be. Still no `.field-row` 2-column pairing
   here the way Edit Specs has, so this doesn't need to go as wide as
   that modal's 760px. */
.edit-venue-modal-box{
  max-width: 520px;
  max-height: 85vh;
  overflow-y: auto;
  text-align: left;
}
.edit-venue-modal-box .card-title{ text-align: center; }
/* The "Edit Specs" modal (venue.php) — 33 fields across 5 categories,
   paired into .field-row pairs, is by far the tallest form in this app.
   .modal-box has no scrolling of its own (every other modal's content
   always comfortably fits the viewport), so without max-height +
   overflow-y here, categories past the bottom of the screen would be
   visually cut off with no way to reach them — this makes the form
   itself scroll within the modal instead. Also wider than
   .add-document-modal-box's 440px (760px) since .field-row's 2-column
   pairing needs the extra room to read as two real side-by-side fields
   rather than two cramped ones. */
.edit-specs-modal-box{
  max-width: 760px;
  max-height: 85vh;
  overflow-y: auto;
  text-align: left;
}
.edit-specs-modal-box .card-title{ text-align: center; }
/* Reuses .specs-category-title's own uppercase/dim-text label styling
   (see above) so a category heading reads the same whether it's
   introducing read-only values in the Specs panel or editable inputs
   here — just with normal top/bottom spacing instead of being folded
   into a clickable .gallery-toggle button, since every category always
   renders in this form (nothing to expand/collapse once you're already
   editing). First category has no extra top margin so it sits flush
   under the modal title, matching every other modal's first field. */
.edit-specs-category-title{
  margin: 20px 0 10px;
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.edit-specs-category-title:first-of-type{ margin-top: 0; }
/* Sits above the Specs panel's category list, same spacing role
   .document-actions plays above Documents' own item list. */
.specs-actions{ margin-bottom: 16px; }
/* Shown instead of the category list when a signed-in OWNER opens a
   venue's Specs panel and nothing has been filled in for any category
   yet — same quiet-placeholder treatment as .document-empty above. */
.specs-empty{
  margin: 0 0 16px;
  font-size: 0.85rem;
  color: var(--text-dim);
}
.field-checkbox label{
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 0.85rem;
  font-weight: 400;
  color: var(--text-dim);
  cursor: pointer;
}
.field-checkbox input[type="checkbox"]{
  margin-top: 3px;
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  accent-color: var(--accent);
  cursor: pointer;
}
.field-checkbox a{ color: var(--accent-2); }

.post-card{
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  padding: 20px 24px;
  margin-bottom: 16px;
}
.post-subject{ margin: 0 0 4px; font-size: 1.02rem; font-weight: 700; }
.post-meta{ margin: 0 0 12px; color: var(--text-faint); font-size: 0.8rem; }

/* map.php's "Locating this address on the map…" / error message, shown in
   place of the map itself until Nominatim's geocoding request resolves (or
   fails) — see map.php's own comment on that swap. Styled like the site's
   other quiet, single-line status text (e.g. the empty-state copy under
   Photos/Documents/Specs when nothing's been uploaded yet) rather than
   calling attention to itself, since for most visits it's only on screen
   for a moment. */
.map-status{
  color: var(--text-dim);
  margin: 4px 0 20px;
}
/* .map-view is intentionally NOT inside photo.php/photo360.php's full-
   bleed body.viewer-page/.viewer-frame layout — map.php uses the site's
   normal bounded <main class="wrap page">, which has no flex-grow parent
   for a map container to stretch into the way #panoViewer does inside
   .viewer-frame. It needs an explicit height instead: Leaflet measures its
   container's actual pixel size the moment L.map() initializes and tiles
   itself to fit, so a zero-height container (the default for an empty
   <div>, which is all #mapView is before Leaflet touches it) would render
   nothing — same reasoning as photo360.php's #panoViewer, just solved with
   a fixed height here rather than flex-grow since this page's layout has
   no flex parent to grow into. [hidden] is the same defensive override
   this project adds to every collapsible/conditionally-shown element (see
   e.g. .venue-tree-country-body[hidden]) even though #mapView's own inline
   `hidden` attribute already suffices on its own — belt and suspenders,
   and consistent with the rest of this file. */
.map-view{
  height: 420px;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--card-border);
}
.map-view[hidden]{ display: none; }
@media (max-width: 640px){
  .map-view{ height: 320px; }
}
.post-comment{ margin: 0; color: var(--text); font-size: 0.94rem; line-height: 1.6; white-space: pre-wrap; }
