/* PRÔPAGANDA — design system + site styles
   Organized so the foundational pieces (tokens, fonts, headings, buttons,
   navigation) live in one predictable place near the top. Change one rule
   there and it applies everywhere. Page/component-specific styles live
   further down, grouped by the page they belong to. */

/* ==========================================================================
   1. Design tokens — colors
   ========================================================================== */

:root {
  --color-brand: #5700ff;
  --color-background: #000000;
  --color-body-text: #bdbdbd;
  --color-white: #ffffff;
}

/* ==========================================================================
   2. Fonts
   ========================================================================== */

@font-face {
  font-family: 'Propaganda';
  src: url('/fonts/propaganda-font-v5.otf') format('opentype');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

/* Self-hosted (no Google Fonts request at runtime) — a single variable
   file covers the whole 100–900 weight range used across the site. */
@font-face {
  font-family: 'Roboto';
  src: url('/fonts/roboto-variable.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Brand font: headings, nav/menu items, buttons — anything that's part
     of the title/brand system. Change this one line to swap it later.
     Falls back to Roboto (never Arial) if Propaganda fails to load. */
  --font-brand: 'Propaganda', 'Roboto', sans-serif;
  /* Body font: ordinary paragraph/UI text. Only Propaganda and Roboto are
     used anywhere on the site — this is the last line of fallback. */
  --font-body: 'Roboto', sans-serif;
}

/* ==========================================================================
   3. Base / reset
   ========================================================================== */

* {
  box-sizing: border-box;
}

/* Eases anchor-jump navigation (nav links, footer links, "back to X"
   links — all the /#section hrefs) into a scroll instead of an instant
   jump, since the homepage is a onepager. scroll-margin-top on section
   (below) already keeps the fixed header from covering the landing spot,
   and that same offset is respected here automatically — no extra work
   needed for smooth scrolling to land in the right place too. */
html {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

body {
  margin: 0;
  font-family: var(--font-body);
  background-color: var(--color-background);
  color: var(--color-body-text);
  line-height: 1.5;
  /* The full-bleed hero below breaks out to 100vw, which can trigger a
     tiny horizontal scrollbar on some browsers — this prevents that. */
  overflow-x: hidden;
}

a {
  color: inherit;
}

.container {
  max-width: 960px;
  margin: 0 auto;
  /* Top padding is taller than the others to clear the fixed header,
     which now floats over the page instead of sitting in normal flow
     (see .site-header). The hero section cancels this same amount via
     its own negative margin so it can still start flush at the very
     top of the viewport. */
  padding: 7rem 1rem 2rem;
}

/* Extra breathing room between homepage/page sections. The hero is
   excluded since it's a full-bleed edge-to-edge video block that fades
   directly into the section below it (see .hero, .hero-overlay). */
section {
  padding-top: 3rem;
  padding-bottom: 3rem;
  /* Keeps anchor-jump navigation (nav links, "back to X" links) from
     landing a section's heading underneath the fixed site header, which
     doesn't take up space in normal document flow. Matches .site-header's
     actual rendered height (1.575rem top/bottom padding + content — see
     .site-header) — keep these two in sync if the header's size changes
     again. Was 8rem/128px, stale from before the header was shrunk ~10%;
     that extra ~13px of slack was invisible against a plain black section
     but showed as a sliver of the previous section peeking through above
     #contact, the one landing spot right after the blue #about section. */
  scroll-margin-top: 7.2rem;
}

/* Contact and Demos (both nav links point here — the Demos form is the
   right-hand column of this same section) land with a bit more scroll
   past the header than other sections, on request — a smaller
   scroll-margin-top means less clearance is reserved, so the page settles
   further down instead of stopping flush with the header. Nudged down
   several times (5.5rem -> 4.5rem -> 3.75rem -> 3rem) since it still
   landed a bit high each time. */
#contact {
  scroll-margin-top: 3rem;
}

#hero {
  padding-top: 0;
  padding-bottom: 0;
}

/* Locks background scroll while the mobile nav sidebar is open. */
body.nav-open {
  overflow: hidden;
}

/* ==========================================================================
   4. Headings — central control point
   Change H1/H2/H3 styling here once, rather than per page.
   ========================================================================== */

h1,
h2,
h3 {
  font-family: var(--font-brand);
  color: var(--color-white);
  font-weight: normal;
  text-transform: uppercase;
}

/* ==========================================================================
   5. Forms (generic)
   ========================================================================== */

form {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 1rem;
}

.login-form {
  max-width: 320px;
}

/* --- Admin login screen --- */

.admin-login-page {
  background-color: var(--color-brand);
  background-image: url('/images/brand/background-blue-icon.jpg');
  background-size: cover;
  background-position: center;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

.admin-login-box {
  width: 100%;
  max-width: 320px;
  padding: 1rem;
}

.admin-login-page input {
  background-color: var(--color-white);
  color: var(--color-brand);
  border: none;
  border-radius: 999px;
  padding: 0.75rem 1.25rem;
  outline: none;
}

/* Email/Password are shown as placeholder text inside the field instead
   of separate <label> elements — a light grey so it reads clearly as a
   placeholder against the white field, without looking like typed text
   (which is brand blue). */
.admin-login-page input::placeholder {
  color: #cccccc;
  opacity: 1;
}

/* Buttons here use the shared .button system (brand blue by default, white
   on hover) — this is also what wires them up to button-align.js, which
   keeps their label precisely vertically centered regardless of the custom
   font's own metrics. Only the size is overridden, to stay compact. */
.admin-login-page .login-form .button {
  padding: 0.75rem 1.25rem;
  margin: 0;
  background-color: #000000;
  color: var(--color-white);
}

.admin-login-page .login-form .button:hover {
  background-color: var(--color-white);
  color: #000000;
}

/* Secondary navigation on the auth screens — "Forgot password?", "Back to
   login", "Request a new link" — small, white, centered, brand font. */
.auth-link {
  text-align: center;
}

.auth-link a {
  font-family: var(--font-brand);
  text-transform: uppercase;
  font-size: 0.75rem;
  color: var(--color-white);
  text-decoration: none;
}

label {
  font-weight: bold;
}

input,
select,
textarea,
button {
  padding: 0.5rem;
  font-size: 1rem;
  font-family: inherit;
}

textarea {
  resize: vertical;
  min-height: 3rem;
}

button {
  cursor: pointer;
  font-family: var(--font-brand);
}

.error {
  color: #ff6b6b;
  font-weight: bold;
}

.success {
  color: #4ade80;
  font-weight: bold;
}

/* ==========================================================================
   6. Buttons — central reusable brand buttons
   Three variants for now; add more following the same pattern if ever
   needed.
   ========================================================================== */

.button,
.button-secondary,
.button-tertiary,
.button-success {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  text-align: center;
  line-height: 1;
  padding: 1.75rem 3rem;
  margin: 1rem 0;
  text-decoration: none;
  border: none;
  border-radius: 999px;
  text-transform: uppercase;
  font-family: var(--font-brand);
  font-size: 1rem;
  cursor: pointer;
}

/* Smaller variant for admin list-page "+ Add X" triggers — the default
   .button pill is sized for a public-site call to action, which reads as
   oversized for a routine "add a new row" control sitting right above a
   table. */
.button-compact {
  padding: 1rem 1.75rem;
  font-size: 0.9rem;
}

/* Icon shown before a button's label (see .btn-icon-* below for specific
   icons). Rendered via mask-image rather than an <img> so it can inherit
   the button's current text color (white by default, brand blue on hover)
   from a single white source SVG. */
.btn-icon {
  display: inline-block;
  width: 1.5em;
  height: 1.5em;
  background-color: currentColor;
  mask-size: contain;
  mask-repeat: no-repeat;
  mask-position: center;
  -webkit-mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
  flex-shrink: 0;
}

.btn-icon-video {
  mask-image: url('/images/icons/media-video-icon-white.svg');
  -webkit-mask-image: url('/images/icons/media-video-icon-white.svg');
}

.btn-icon-gallery {
  mask-image: url('/images/icons/media-gallery-icon-white.svg');
  -webkit-mask-image: url('/images/icons/media-gallery-icon-white.svg');
}

.button {
  background-color: var(--color-brand);
  color: var(--color-white);
}

.button:hover {
  background-color: var(--color-white);
  color: var(--color-brand);
}

.button-secondary {
  background-color: var(--color-white);
  color: var(--color-brand);
}

.button-secondary:hover {
  background-color: var(--color-brand);
  color: var(--color-white);
}

.button-tertiary {
  background-color: var(--color-white);
  color: var(--color-brand);
}

.button-tertiary:hover {
  background-color: #000000;
  color: var(--color-white);
}

.button-success {
  background-color: #4ade80;
  color: #0a0a0a;
}

.button-success:hover {
  background-color: var(--color-white);
  color: #4ade80;
}

/* Wrapped around each button's label by button-align.js, which nudges it
   with a measured transform so the visual ink sits dead-center regardless
   of the custom font's own ascent/descent metrics (see that file for why
   padding/line-height alone can't guarantee this). */
.btn-label {
  display: inline-block;
}

.action-link,
.link-button {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  color: var(--color-brand);
  font-family: var(--font-body);
  font-weight: 700;
  text-decoration: underline;
  font-size: 1rem;
  cursor: pointer;
}

.actions-cell a {
  font-family: var(--font-body);
  font-weight: 700;
  color: var(--color-brand);
}

.link-button.danger {
  color: #ff6b6b;
}

.link-button.success {
  color: #4ade80;
}

/* ==========================================================================
   7. Navigation
   ========================================================================== */

.site-header {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  /* Extra breathing room on the sides (beyond the top/bottom space) so the
     logo and nav items don't sit flush against the viewport edge. Desktop
     header is ~10% smaller than it used to be (was 1.75rem 2.5rem / 128px
     total, now 115.2px) — .site-logo-img below is scaled down to match. */
  padding: 1.575rem 2.25rem;
  background-color: var(--color-background);
  /* Fixed and full-width so it stays put and spans the viewport on every
     page. Every other page gets .container's top padding (above) to keep
     content clear of it. */
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 100;
}

.site-logo {
  display: inline-flex;
  align-items: center;
}

.site-logo-img {
  display: block;
  height: 4.05rem;
  width: auto;
}

.site-logo-img-hover {
  display: none;
}

.site-logo:hover .site-logo-img-default {
  display: none;
}

.site-logo:hover .site-logo-img-hover {
  display: block;
}

.site-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 2.5rem;
}

/* Sidebar-only social icons — hidden on desktop, shown by the mobile
   media query below. */
.site-nav .social-buttons {
  display: none;
}

.site-nav a {
  font-family: var(--font-brand);
  font-size: 1.4rem;
  text-decoration: none;
  text-transform: uppercase;
  color: var(--color-white);
  transition: color 0.3s ease;
}

.site-nav a:hover {
  color: var(--color-brand);
}

.site-nav a.active {
  color: var(--color-brand);
}

/* Hamburger button — hidden on desktop, shown by the media query below. */
.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 32px;
  height: 32px;
  padding: 0;
  background: none;
  border: none;
}

.nav-toggle span {
  display: block;
  width: 100%;
  height: 2px;
  background-color: var(--color-white);
}

/* --- Admin panel (sidebar layout) ---
   Self-contained admin design tokens (--admin-*) rather than repurposing
   the public site's --color-* tokens directly — the light-mode toggle only
   needs to override these, and public-site colors (buttons, brand purple)
   stay untouched and correct on both admin themes. */

:root {
  --admin-bg: var(--color-background);
  --admin-surface: #0a0a0a;
  --admin-border: #262626;
  --admin-text: var(--color-body-text);
  --admin-heading: var(--color-white);
  --admin-row-a: rgba(255, 255, 255, 0.03);
  --admin-row-b: rgba(255, 255, 255, 0.065);
  --admin-row-hover: rgba(87, 0, 255, 0.16);
}

html.admin-theme-light {
  --admin-bg: #f4f4f5;
  --admin-surface: #ffffff;
  --admin-border: #e2e2e2;
  --admin-text: #4b4b4b;
  --admin-heading: #111111;
  --admin-row-a: rgba(0, 0, 0, 0.02);
  --admin-row-b: rgba(0, 0, 0, 0.05);
  --admin-row-hover: rgba(87, 0, 255, 0.08);
}

.admin-body {
  margin: 0;
  background-color: var(--admin-bg);
  color: var(--admin-text);
}

.admin-shell {
  display: flex;
  align-items: flex-start;
  min-height: 100vh;
}

/* Locks background scroll while the mobile admin drawer is open — same
   idea as body.nav-open on the public site. */
body.admin-nav-open {
  overflow: hidden;
}

/* Mobile-only top bar (logo + hamburger) — hidden on desktop, shown by the
   mobile media query further down. Persists outside the sidebar/drawer so
   the toggle stays reachable while the drawer itself is off-screen. */
.admin-mobile-bar {
  display: none;
}

.admin-nav-toggle {
  display: none;
}

.admin-sidebar {
  flex-shrink: 0;
  width: 240px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 2rem;
  padding: 1.75rem 1.25rem;
  background-color: var(--admin-surface);
  border-right: 1px solid var(--admin-border);
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.admin-sidebar-top {
  display: flex;
  flex-direction: column;
  gap: 2rem;
}

.admin-sidebar-logo {
  display: inline-flex;
  padding: 0 0.5rem;
}

.admin-sidebar-logo img {
  height: 2.25rem;
  width: auto;
}

.admin-sidebar-nav {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.admin-sidebar-link {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 0.65rem 0.75rem;
  border-radius: 999px;
  color: var(--admin-text);
  text-decoration: none;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.admin-sidebar-icon {
  width: 1.2rem;
  height: 1.2rem;
  flex-shrink: 0;
}

.admin-sidebar-label {
  font-family: var(--font-brand);
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.admin-sidebar-link:hover {
  background-color: rgba(128, 128, 128, 0.12);
  color: var(--admin-heading);
}

.admin-sidebar-link.active {
  background-color: var(--color-brand);
  color: var(--color-white);
}

.admin-sidebar-bottom {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  border-top: 1px solid var(--admin-border);
  padding-top: 1rem;
}

.admin-theme-toggle {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  width: 100%;
  padding: 0.65rem 0.75rem;
  background: none;
  border: none;
  border-radius: 999px;
  color: var(--admin-text);
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.admin-theme-toggle:hover {
  background-color: rgba(128, 128, 128, 0.12);
  color: var(--admin-heading);
}

.admin-theme-icon {
  width: 1.2rem;
  height: 1.2rem;
  flex-shrink: 0;
}

.admin-theme-label-light {
  display: none;
}

html.admin-theme-light .admin-theme-label-dark {
  display: none;
}

html.admin-theme-light .admin-theme-label-light {
  display: inline;
}

.admin-theme-icon-moon {
  display: none;
}

html.admin-theme-light .admin-theme-icon-sun {
  display: none;
}

html.admin-theme-light .admin-theme-icon-moon {
  display: block;
}

.admin-account-link {
  min-width: 0;
}

.admin-account-email {
  font-family: var(--font-body);
  text-transform: none;
  letter-spacing: normal;
  font-size: 0.8rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.admin-logout-form {
  margin: 0;
}

.admin-logout-button {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  width: 100%;
  padding: 0.65rem 0.75rem;
  background: none;
  border: none;
  border-radius: 999px;
  color: var(--admin-text);
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.admin-logout-button:hover {
  background-color: rgba(255, 107, 107, 0.12);
  color: #ff6b6b;
}

.admin-logout-button svg {
  width: 1.2rem;
  height: 1.2rem;
  flex-shrink: 0;
}

.admin-sidebar-version {
  margin: 0.5rem 0 0;
  padding: 0 0.75rem;
  font-size: 0.7rem;
  color: var(--admin-text);
  opacity: 0.5;
}

.admin-main {
  flex: 1;
  min-width: 0;
  padding: 3rem 3rem 4rem;
}

.admin-page {
  max-width: 1100px;
}

.admin-page h1,
.admin-page h2,
.admin-page h3 {
  color: var(--admin-heading);
}

.admin-page h1 {
  margin-top: 0;
}

.admin-code-field {
  font-family: 'SF Mono', Monaco, 'Courier New', monospace;
  font-size: 0.85rem;
  white-space: pre;
  overflow-wrap: normal;
  overflow-x: auto;
}

/* Admin tables — no per-row border lines. Rows are told apart by two
   subtly different low-opacity background tints instead (plus a brand
   wash on hover), which reads as "modern" rather than a spreadsheet grid. */
.admin-page table {
  border-collapse: separate;
  border-spacing: 0;
  border-radius: 10px;
  overflow: hidden;
}

.admin-page th {
  padding: 0.75rem 1rem;
  font-family: var(--font-brand);
  text-transform: uppercase;
  font-size: 0.8rem;
  letter-spacing: 0.03em;
  color: var(--admin-text);
  background-color: var(--admin-surface);
  border-bottom: 1px solid var(--admin-border);
}

.admin-page td {
  padding: 0.75rem 1rem;
  border-bottom: none;
}

/* A date is short and fixed-format — it should never be the column that
   wraps when a neighboring column (title, artist...) runs long. */
.admin-date-cell {
  white-space: nowrap;
}

.admin-page tbody tr:nth-child(odd) td {
  background-color: var(--admin-row-a);
}

.admin-page tbody tr:nth-child(even) td {
  background-color: var(--admin-row-b);
}

.admin-page tbody tr:hover td {
  background-color: var(--admin-row-hover);
}

.admin-dashboard-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 1rem;
  margin-top: 2rem;
}

/* Same visual treatment as .button + .button-compact (filled brand pill,
   inverts on hover), but staying block-level rather than inline-flex so
   each card still stretches to fill its grid cell — matching styling
   without giving up the grid's equal-width columns. */
.admin-dashboard-card {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem 1.75rem;
  border-radius: 999px;
  border: none;
  background-color: var(--color-brand);
  color: var(--color-white);
  text-decoration: none;
  font-family: var(--font-brand);
  text-transform: uppercase;
  font-size: 0.9rem;
  letter-spacing: 0.03em;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.admin-dashboard-card:hover {
  background-color: var(--color-white);
  color: var(--color-brand);
}

/* --- Dashboard analytics --- */

.admin-stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1rem;
  margin-top: 1.5rem;
}

.admin-stat-tile {
  padding: 1.25rem 1.5rem;
  border-radius: 10px;
  border: 1px solid var(--admin-border);
  background-color: var(--admin-surface);
}

.admin-stat-tile-label {
  font-family: var(--font-brand);
  text-transform: uppercase;
  font-size: 0.7rem;
  letter-spacing: 0.03em;
  color: var(--admin-text);
  margin-bottom: 0.5rem;
}

.admin-stat-tile-value {
  font-family: var(--font-brand);
  font-size: 2rem;
  color: var(--admin-heading);
}

.admin-section-heading {
  margin-top: 2.5rem;
}

.admin-recent-changes {
  list-style: none;
  margin: 1.5rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.admin-recent-changes li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.85rem 1.25rem;
  border-radius: 10px;
  border: 1px solid var(--admin-border);
  background-color: var(--admin-surface);
}

.admin-recent-changes a {
  color: var(--admin-heading);
  text-decoration: none;
  font-weight: 700;
}

.admin-recent-changes a:hover {
  color: var(--color-brand);
}

.admin-recent-changes-type {
  display: inline-block;
  font-family: var(--font-brand);
  text-transform: uppercase;
  font-size: 0.7rem;
  letter-spacing: 0.03em;
  color: var(--color-brand);
  margin-right: 0.6rem;
}

.admin-recent-changes-time {
  font-size: 0.85rem;
  color: var(--admin-text);
  white-space: nowrap;
}

.admin-charts-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
  gap: 1.5rem;
  margin-top: 1.5rem;
}

.admin-chart-card {
  padding: 1.5rem;
  border-radius: 10px;
  border: 1px solid var(--admin-border);
  background-color: var(--admin-surface);
  min-width: 0;
}

.admin-chart-card h3 {
  margin: 0 0 1rem;
  font-size: 1rem;
  color: var(--admin-heading);
}

.admin-chart-card canvas {
  max-height: 260px;
}

.admin-chart-card-wide {
  grid-column: 1 / -1;
}

.admin-chart-card-wide canvas {
  max-height: 320px;
}

.admin-referrer-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.admin-referrer-list li {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-size: 0.9rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--admin-border);
}

.admin-referrer-list li:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.admin-referrer-list span:last-child {
  color: var(--admin-text);
  flex-shrink: 0;
}

.admin-empty-note {
  color: var(--admin-text);
}

/* Admin buttons (Save, + Add, etc. — anything using the shared .button
   system) hover to white on dark, but to black on light — the public
   site's white hover would nearly disappear against a light background. */
html.admin-theme-light .admin-body .button:hover {
  background-color: #000000;
  color: #ffffff;
}

/* Sidebar logo — swaps to the blue icon on light mode, same technique as
   .site-logo's hover swap (two stacked images, one hidden via CSS). */
.admin-sidebar-logo-img-light {
  display: none;
}

html.admin-theme-light .admin-sidebar-logo-img-default {
  display: none;
}

html.admin-theme-light .admin-sidebar-logo-img-light {
  display: block;
}

/* Dark-mode form fields — no background of their own (transparent, so
   they sit flush against the page), just readable text and a
   brand-tinted placeholder. The focus border is what marks a field's
   edges now, not a background tint. Left alone in light mode, where the
   default white fields already sit fine against the light page
   background. */
html:not(.admin-theme-light) .admin-body input:not([type="checkbox"]):not([type="file"]),
html:not(.admin-theme-light) .admin-body select,
html:not(.admin-theme-light) .admin-body textarea {
  background-color: transparent;
  color: #e8e8e8;
}

html:not(.admin-theme-light) .admin-body input::placeholder,
html:not(.admin-theme-light) .admin-body textarea::placeholder {
  color: #8a6bff;
  opacity: 0.85;
}

/* All admin form fields — square corners, no border at rest. The border
   is reserved as a focus indicator (brand blue), kept at the same 2px
   width when resting (transparent) so focusing a field doesn't shift
   layout by adding width that wasn't there before. */
.admin-body input:not([type="checkbox"]):not([type="file"]):not([type="radio"]),
.admin-body select,
.admin-body textarea {
  border: 2px solid transparent;
  border-radius: 0;
  outline: none;
}

.admin-body input:not([type="checkbox"]):not([type="file"]):not([type="radio"]):focus,
.admin-body select:focus,
.admin-body textarea:focus {
  border-color: var(--color-brand);
}

/* Mobile admin nav: same pattern as the public site's hamburger + slide-in
   drawer (.site-header/.site-nav, see section 8 below and public/js/nav.js)
   — a persistent top bar with the logo and toggle, and the sidebar itself
   becomes an off-canvas drawer sliding in from the right. Toggled by
   public/js/admin-nav.js. Replaces the old horizontally-scrolling
   icon-only strip, which had no labels and was easy to lose items in. */
@media (max-width: 900px) {
  .admin-shell {
    flex-direction: column;
    /* The base rule's align-items: flex-start shrinks every child (in a
       column flex layout, that's the horizontal axis) to its own content
       width — .admin-mobile-bar was collapsing to just fit the logo and
       hamburger, which is why the hamburger sat right next to the logo
       instead of being pushed to the far right. */
    align-items: stretch;
  }

  .admin-mobile-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1rem 1.25rem;
    background-color: var(--admin-surface);
    border-bottom: 1px solid var(--admin-border);
  }

  .admin-mobile-logo {
    display: inline-flex;
  }

  .admin-mobile-logo img {
    height: 2rem;
    width: auto;
  }

  .admin-nav-toggle {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    width: 32px;
    height: 32px;
    padding: 0;
    background: none;
    border: none;
    cursor: pointer;
  }

  .admin-nav-toggle span {
    display: block;
    width: 100%;
    height: 3px;
    background-color: var(--admin-text);
  }

  .admin-sidebar {
    position: fixed;
    top: 0;
    right: 0;
    width: 260px;
    max-width: 80vw;
    height: 100vh;
    flex-direction: column;
    justify-content: flex-start;
    gap: 2rem;
    padding: 5rem 1.5rem 1.5rem;
    border-right: none;
    border-left: 1px solid var(--admin-border);
    /* With every section's label now visible (13 links plus the theme
       toggle/account/logout/version), the full list is taller than some
       phones — scrolls internally rather than clipping the bottom items. */
    overflow-y: auto;
    transform: translateX(100%);
    transition: transform 0.2s ease;
    z-index: 150;
    /* Reads as floating above the page — in light theme especially, the
       surface color is close enough to the page background that the 1px
       border alone doesn't read clearly as an overlay. */
    box-shadow: -4px 0 24px rgba(0, 0, 0, 0.3);
  }

  .admin-sidebar.is-open {
    transform: translateX(0);
  }

  /* The persistent top bar already shows the logo, so the drawer's own
     copy (only needed to anchor the desktop sidebar's layout) would just
     be a redundant second logo while open. */
  .admin-sidebar-logo {
    display: none;
  }

  .admin-sidebar-top,
  .admin-sidebar-bottom {
    flex-direction: column;
    align-items: stretch;
    gap: 0.25rem;
  }

  .admin-sidebar-bottom {
    border-top: 1px solid var(--admin-border);
    padding-top: 1rem;
    margin-left: 0;
  }

  .admin-main {
    padding: 2rem 1.25rem 3rem;
  }

  /* Admin tables, restacked as cards instead of a table someone has to
     swipe sideways to read. Each row becomes a bordered block; each cell
     becomes a label/value line using its data-label attribute (see the
     admin list views) for the label, so the header row (hidden here) is
     no longer needed to know what a value means. The actions cell has no
     data-label, so it gets no label line, just its row of links/buttons
     underneath a divider. */
  .admin-page table {
    display: block;
    border-radius: 0;
    overflow: visible;
  }

  .admin-page thead {
    display: none;
  }

  .admin-page tbody {
    display: block;
  }

  .admin-page tbody tr {
    display: block;
    margin-bottom: 0.85rem;
    padding: 0.85rem 1rem;
    border: 1px solid var(--admin-border);
    border-radius: 10px;
    background-color: var(--admin-surface);
  }

  /* The odd/even striping (desktop only, visually) sets background-color
     directly on each td, not the row — left alone, that would paint over
     the card background above with a stripe per label/value line instead
     of one flat card. Neutralized here so the card reads as one surface. */
  .admin-page tbody tr:nth-child(odd) td,
  .admin-page tbody tr:nth-child(even) td,
  .admin-page tbody tr:hover td {
    background-color: transparent;
  }

  .admin-page td {
    display: flex;
    align-items: baseline;
    gap: 0.75rem;
    padding: 0.35rem 0;
  }

  .admin-page td[data-label]::before {
    content: attr(data-label);
    flex: 0 0 auto;
    min-width: 6.5rem;
    font-family: var(--font-brand);
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--admin-text);
    opacity: 0.6;
  }

  .admin-page td.actions-cell {
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-top: 0.4rem;
    padding-top: 0.6rem;
    border-top: 1px solid var(--admin-border);
  }

  /* Wraps individual admin-page tables that opt into it (see
     views/admin/preregister/signups.ejs) — moot now that the table itself
     no longer needs sideways scrolling, but harmless to leave set. */
  .admin-table-wrap {
    overflow-x: visible;
  }

  /* A long button label (e.g. "Export marketing consent only (CSV)") is
     the one other thing on these pages wide enough to force horizontal
     scrolling on its own — scoped to .admin-page so this never touches
     the public site's own buttons. */
  .admin-page .form-actions .button,
  .admin-page .form-actions .button-secondary,
  .admin-page .form-actions .button-success {
    white-space: normal;
    text-align: center;
  }
}

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

/* ==========================================================================
   8. Responsive navigation (mobile + tablet)
   ========================================================================== */

@media (max-width: 900px) {
  .site-header {
    padding: 1rem 1.25rem;
  }

  /* Logo matches the hamburger button's own size exactly. */
  .site-logo-img {
    height: 32px;
  }

  .nav-toggle span {
    height: 3px;
  }

  .nav-toggle {
    display: flex;
  }

  .site-nav {
    position: fixed;
    top: 0;
    right: 0;
    height: 100vh;
    width: 260px;
    max-width: 80vw;
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
    padding: 5rem 1.5rem 1.5rem;
    background-color: var(--color-background);
    transform: translateX(100%);
    transition: transform 0.2s ease;
    z-index: 150;
  }

  .site-nav.is-open {
    transform: translateX(0);
  }

  /* Right under the last nav link, in normal flow — not pinned to the
     bottom of the sidebar, which can end up off-screen on shorter
     phones. Small enough that all 6 fit on a single row. */
  .site-nav .social-buttons {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.5rem;
    margin-bottom: 0;
  }

  .site-nav .social-button {
    height: 35px;
  }

  /* Date line wraps onto its own row under "ANTWERP 2026 ▶". */
  .hero-date-day {
    display: block;
  }

  /* Cancels the desktop-only downward nudge above — mobile keeps its
     original centering. */
  .hero-content {
    margin-top: 0;
  }

  /* Stacked instead of side-by-side, same compact size as the homepage
     event buttons. gap is set separately below, after the desktop rule —
     see the comment there for why. */
  .hero-buttons {
    flex-direction: column;
  }

  .hero-buttons .button,
  .hero-buttons .button-secondary {
    padding: 0.75rem 1.5rem;
    min-height: 2.5rem;
  }

  .social-button {
    height: 44px;
  }

  /* Two neat rows of 3 (Events/Media/Releases, then Demos/About/Contact)
     instead of however they'd naturally wrap by width. Extra specificity
     (.footer-sitemap parent) is needed to reliably beat the desktop
     display:contents rule below, which has equal specificity otherwise
     and comes later in the file. */
  .footer-sitemap .footer-sitemap-row {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 1.5rem;
    width: 100%;
  }
}

/* ==========================================================================
   9. Page and component-specific styles
   ========================================================================== */

/* --- Homepage hero (Section 1) --- */
/* Breaks out to full viewport width regardless of .container's max-width,
   since a background-video hero is meant to be edge-to-edge. Everything
   else on the page stays inside .container as before. */

.hero {
  position: relative;
  left: 50%;
  right: 50%;
  width: 100vw;
  margin-left: -50vw;
  margin-right: -50vw;
  /* Cancels .container's top padding (which exists to clear the fixed
     header on every other page) so the hero still starts flush at the
     very top of the viewport, with the header floating over it. */
  margin-top: -7rem;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.hero-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
}

.hero-overlay {
  position: absolute;
  inset: 0;
  background-color: rgba(0, 0, 0, 0.4);
  /* Fades to solid black over the bottom quarter of the hero, so the video
     blends smoothly into the black background below it instead of cutting
     off with a hard edge. */
  background-image: linear-gradient(to bottom, transparent 75%, #000000 100%);
  z-index: 1;
}

.hero-content {
  position: relative;
  z-index: 2;
  text-align: center;
  padding: 1rem;
  /* Nudges the whole title/date/buttons block down a little from dead
     center — desktop only, reset back to 0 in the mobile override below. */
  margin-top: 7vh;
}

/* .hero-title-group is left as a plain block div (the default) so it
   always takes its own line — an inline-level display would let the
   button below it sit beside it instead of underneath it. */

.hero-content h1 {
  margin: 0 0 0.5rem;
}

.hero-buttons {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 2rem;
  margin-top: 1rem;
}

/* Placed after the desktop rule above (not with the rest of the mobile
   .hero-buttons tweaks near the top of this file) so it actually wins —
   equal specificity means source order decides ties, and a mobile-scoped
   gap declared earlier in the file was silently losing to this
   unconditional one, regardless of viewport. */
@media (max-width: 900px) {
  .hero-buttons {
    gap: 0.8rem;
  }
}

.hero-buttons .button,
.hero-buttons .button-secondary {
  margin: 0;
}

.hero-content h2 {
  font-size: clamp(1.2rem, 4vw, 2rem);
  margin: 0 0 1.5rem;
  /* Shrink-wrapped to its own text, not stretched to the block's full
     width — hero.js measures this box to size the logo above it, so it
     must report the text's true rendered width. */
  display: inline-block;
}

.hero-date-arrow {
  height: 0.55em;
  width: auto;
  vertical-align: middle;
  margin: 0 0.5em;
  fill: #ff2fc2;
  filter: drop-shadow(0 0 4px #ff2fc2) drop-shadow(0 0 10px #ff2fc2);
}

/* Default/fallback size before hero.js measures .hero-date and sets an
   exact pixel width — keeps the two lines the same width at all times. */
.hero-logo {
  display: block;
  height: auto;
  width: min(90vw, 32rem);
  margin: 0 auto;
}

/* --- Tables (admin lists) --- */

table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 1rem;
}

th,
td {
  text-align: left;
  padding: 0.5rem;
  border-bottom: 1px solid #333;
}

/* display:flex directly on a <td> breaks its normal table-cell stretch
   behavior — when a sibling cell in the same row grows taller (e.g. a long
   title wrapping to multiple lines), a flexed actions-cell doesn't stretch
   to match, leaving a gap where its background doesn't reach the full row
   height. Kept as a normal table-cell instead: form{display:contents}
   strips the generic form{} rule's own box (see "Forms (generic)" above)
   so its button becomes a plain inline sibling of the row's <a> tags,
   which lay out left-to-right on their own without needing flex at all. */
.actions-cell {
  white-space: nowrap;
  vertical-align: middle;
}

.actions-cell form {
  display: contents;
}

.actions-cell a,
.actions-cell button {
  margin-right: 1rem;
}

/* --- Event form specifics --- */

.image-preview {
  display: block;
  max-width: 320px;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  margin-bottom: 0.5rem;
}

.image-preview-square {
  aspect-ratio: 1 / 1;
}

/* Custom-styled file inputs everywhere in the admin — the raw browser
   button otherwise looks out of place against the dark branded UI. */
.admin-page input[type="file"] {
  color: var(--admin-text);
  font-size: 0.9rem;
}

.admin-page input[type="file"]::file-selector-button {
  margin-right: 1rem;
  padding: 0.5rem 1rem;
  border-radius: 999px;
  border: none;
  background-color: var(--color-brand);
  color: var(--color-white);
  font-family: var(--font-brand);
  text-transform: uppercase;
  font-size: 0.8rem;
  cursor: pointer;
}

.admin-page input[type="file"]::file-selector-button:hover {
  background-color: var(--admin-heading);
}

.checkbox-label {
  flex-direction: row;
  align-items: center;
  gap: 0.5rem;
  display: flex;
}

.checkbox-label input {
  width: auto;
}

/* A real on/off switch (not just a checkbox) — used for the "email me
   on new demo" setting. The <input> stays in the DOM for real
   checkbox semantics (keyboard, form submission) but is visually
   hidden; the track + thumb next to it are what's actually shown, and
   change appearance off the input's own :checked state. */
.admin-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1.5rem 0;
  cursor: pointer;
}

.admin-toggle input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.admin-toggle-track {
  position: relative;
  flex-shrink: 0;
  width: 2.75rem;
  height: 1.5rem;
  background-color: var(--admin-border);
  border-radius: 999px;
  transition: background-color 0.15s ease;
}

.admin-toggle-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: calc(1.5rem - 4px);
  height: calc(1.5rem - 4px);
  background-color: var(--color-brand);
  border-radius: 50%;
  transition: transform 0.15s ease, background-color 0.15s ease;
}

.admin-toggle input:checked + .admin-toggle-track {
  background-color: var(--color-brand);
}

.admin-toggle input:checked + .admin-toggle-track .admin-toggle-thumb {
  transform: translateX(1.25rem);
  background-color: var(--color-white);
}

.admin-toggle input:focus-visible + .admin-toggle-track {
  outline: 2px solid var(--color-brand);
  outline-offset: 2px;
}

.admin-toggle-label {
  color: var(--admin-text);
  font-size: 0.9rem;
}

/* Read-only field list for a single record (e.g. one demo submission) —
   a small caption above each value, same visual language as
   .repeatable-field-label. */
.admin-detail-list {
  margin: 2rem 0;
  padding: 0;
}

.admin-detail-row {
  padding: 1rem 0;
  border-bottom: 1px solid var(--admin-border);
}

.admin-detail-row dt {
  font-family: var(--font-brand);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--admin-text);
  opacity: 0.65;
  margin-bottom: 0.35rem;
}

.admin-detail-row dd {
  margin: 0;
  color: var(--admin-heading);
  font-size: 1rem;
  word-break: break-word;
}

/* A conditional group of fields nested inside a form (event-type-specific
   fields, etc). Not a <form> itself, so it needs its own copy of the
   label-above-input stacking that `form` gives its own direct children —
   otherwise labels and inputs here fall back to default inline flow and
   sit awkwardly side by side. */
.type-fields {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/* One item in a repeatable list (Line-up, Timetable, FAQ, gallery photos).
   Styled as a distinct card so a row's fields read as a clear group
   instead of running into the row above/below it. */
.repeatable-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
  padding: 1rem;
  margin-bottom: 0.75rem;
  background-color: var(--admin-surface);
  border: 1px solid var(--admin-border);
  border-radius: 10px;
}

.repeatable-row input,
.repeatable-row textarea {
  flex: 1 1 200px;
}

/* Modifier for rows whose fields should stack one per line rather than
   run side by side — used where a row holds several text fields (FAQ's
   question/answer/button text/button url is the worst offender flattened
   into one line). Scrolling a bit further beats squeezing every field
   into an unreadable sliver. */
.repeatable-fields {
  flex-direction: column;
  align-items: stretch;
}

.repeatable-fields input,
.repeatable-fields textarea {
  flex: none;
  width: 100%;
  box-sizing: border-box;
}

.repeatable-field-label {
  display: block;
  font-family: var(--font-brand);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--admin-text);
  opacity: 0.65;
  margin-bottom: 0.2rem;
}

/* Toolbar of move/remove controls for one repeatable row — grouped
   instead of the buttons trailing loose after the fields. Centered with
   the row's fields by default (one-line rows like Line-up/Timetable);
   pinned to the bottom corner instead for stacked rows (FAQ), where the
   fields above it are much taller than the toolbar. */
.repeatable-row-actions {
  display: flex;
  gap: 0.5rem;
  align-self: center;
}

.repeatable-fields .repeatable-row-actions {
  align-self: flex-end;
}

.repeatable-row-actions button {
  background: none;
  border: 1px solid var(--admin-border);
  border-radius: 6px;
  color: var(--admin-text);
  padding: 0.3rem 0.65rem;
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
}

.repeatable-row-actions button:hover {
  border-color: var(--color-brand);
  color: var(--color-brand);
}

.repeatable-row-actions .remove-row {
  border-color: transparent;
  color: #ff6b6b;
}

.repeatable-row-actions .remove-row:hover {
  border-color: #ff6b6b;
  color: #ff6b6b;
}

/* Compact outlined "+ Add row" trigger — the default <button> look (a
   plain gray browser button stretched full width by the form's flex
   layout) reads as broken, and the large brand pill button (.button /
   .button-secondary) is sized for primary calls to action, not a small
   repeated "add one more" control. */
.button-add-row {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.6rem 1.25rem;
  margin: 0.25rem 0 1rem;
  background-color: transparent;
  border: 1px solid var(--admin-border);
  border-radius: 999px;
  color: var(--admin-heading);
  font-family: var(--font-brand);
  text-transform: uppercase;
  font-size: 0.8rem;
  letter-spacing: 0.03em;
  cursor: pointer;
}

.button-add-row:hover {
  border-color: var(--color-brand);
  color: var(--color-brand);
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: center;
  margin-top: 1.5rem;
}

/* A standalone single-button POST form (Check Spotify Now, Disconnect
   Spotify, Delete...) sitting directly in a .form-actions row still picks
   up the generic form{} rule's own display/gap/margin-top (see "Forms
   (generic)" above) even though it's really just a submit wrapper, not a
   real multi-field form. That extra margin is invisible on its own but
   throws off vertical alignment against a plain .button <a> sibling that
   has no such wrapper. display:contents removes the <form> from the box
   model entirely — its button becomes a direct flex item of .form-actions,
   sized and centered exactly like every other item in the row — while the
   element still submits normally. */
.form-actions form {
  display: contents;
}

/* Scoped to .form-actions rather than the shared .button/.link-button
   rules — those are also used on the public site, where a long label
   wrapping on a narrow screen is fine. In an admin button row, though,
   letting a button's own label wrap mid-pill (rather than the whole
   button dropping to its own line via flex-wrap above) is what breaks
   vertical alignment between it and its neighbors. */
.form-actions .button,
.form-actions .button-secondary,
.form-actions .button-tertiary,
.form-actions .button-success,
.form-actions .link-button {
  white-space: nowrap;
}

/* --- Site footer (every public page) --- */

.site-footer {
  margin-top: 4rem;
  padding: 2rem 1rem;
  text-align: center;
}

/* Purely a grouping wrapper on desktop — invisible to layout, so the 6
   links still wrap naturally as one set. The mobile media query turns
   each row into its own real flex row of exactly 3. */
.footer-sitemap-row {
  display: contents;
}

.footer-sitemap,
.footer-legal {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1rem;
  margin: 0 0 1rem;
}

.footer-sitemap a {
  font-family: var(--font-brand);
  text-transform: uppercase;
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  color: var(--color-white);
  text-decoration: none;
  transition: color 0.3s ease;
}

.footer-sitemap a:hover {
  color: var(--color-brand);
}

.footer-legal {
  margin-bottom: 1.5rem;
}

.footer-legal a {
  font-family: var(--font-body);
  font-size: 0.75rem;
  color: #888;
  text-decoration: none;
  transition: color 0.3s ease;
}

.footer-legal a:hover {
  color: var(--color-white);
}

.footer-icon {
  display: inline-block;
  height: 2rem;
  width: auto;
  margin: 0 0 1rem;
}

.footer-copyright {
  color: var(--color-brand);
  font-size: 0.75rem;
  margin: 0;
}

/* Fixed bottom-right "back to top" button, present on every page (added
   in the footer partial). Hidden near the top of the page, fades in
   once there's something to scroll back up from. */
.back-to-top {
  position: fixed;
  right: 1.5rem;
  bottom: 1.5rem;
  width: 48px;
  height: 48px;
  border-radius: 5px; /* same as the latest release button, bottom left */
  background-color: #000;
  color: var(--color-brand);
  display: flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  z-index: 500;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease, color 0.3s ease;
}

.back-to-top.is-visible {
  opacity: 1;
  pointer-events: auto;
}

.back-to-top:hover {
  color: var(--color-white);
}

.back-to-top svg {
  width: 20px;
  height: 20px;
}

/* --- Cookie consent banner --- */

.consent-banner {
  position: fixed;
  left: 1.5rem;
  right: 1.5rem;
  bottom: 1.5rem;
  z-index: 600;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  max-width: 720px;
  margin: 0 auto;
  padding: 1.25rem 1.5rem;
  background-color: #000000;
  border-radius: 5px;
}

.consent-banner[hidden] {
  display: none;
}

.consent-banner p {
  margin: 0;
  flex: 1 1 320px;
  font-size: 0.9rem;
  color: var(--color-body-text);
}

.consent-banner p a {
  color: var(--color-brand);
}

.consent-banner-actions {
  display: flex;
  gap: 0.75rem;
  flex-shrink: 0;
}

.consent-banner-actions .button,
.consent-banner-actions .button-secondary {
  margin: 0;
  padding: 0.65rem 1.5rem;
  min-height: auto;
}

@media (max-width: 600px) {
  .consent-banner {
    left: 1rem;
    right: 1rem;
    bottom: 1rem;
    padding: 1.25rem;
  }
}

/* --- Public event list --- */
.event-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 2rem;
  margin-top: 1rem;
}

.event-card img,
.event-card-image {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

/* Every event card — homepage Upcoming Events and both sections of /events —
   is the same size: one per row at the full content width, exactly like the
   "featured" first card used to be. (Previously only the first was full
   width and the rest sat two-up at half size.) */
.event-list {
  grid-template-columns: 1fr;
}

.event-list .event-card-image {
  border-radius: 5px;
}

.event-card > a {
  text-decoration: none;
}

.event-card h3 {
  margin-bottom: 0.25rem;
}

.event-meta {
  color: #aaa;
  margin-top: 0;
}

.event-buttons {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  gap: 1rem;
}

.event-buttons .button,
.event-buttons .button-secondary {
  margin: 0;
  /* Same size as the Media section buttons — matched on padding AND height,
     since the Media buttons are taller than padding alone would suggest
     (their icon adds height that these icon-less buttons don't have). This
     is the size used on the event detail page; the homepage teaser cards
     use a smaller override below (.event-list .event-buttons). */
  padding: 1rem 2rem;
  min-height: 3.5rem;
}

/* Buttons on event *cards* (homepage and /events) are ~25-28% smaller than
   the detail-page size above, per explicit request — the event detail page
   is untouched. */
.event-list .event-buttons .button,
.event-list .event-buttons .button-secondary {
  padding: 0.75rem 1.5rem;
  min-height: 2.5rem;
}

/* --- Public event detail --- */
/* No max-width here — matches the full .container width used by every
   homepage section, rather than its own narrower column. */

/* Shown only when an unpublished event is viewed via its signed preview
   link (see lib/eventPreview.js) — sits in normal flow right under the
   header rather than as a fixed overlay, so it doesn't need to fight the
   header's own fixed positioning/z-index. */
.preview-banner {
  /* .container's top padding (7rem) is a few px shorter than the fixed
     header's real height (115.2px) — invisible against plain text, but
     this banner is a solid block, so without this its top edge sat
     partly behind the header. */
  margin-top: 0.5rem;
  margin-bottom: 1.5rem;
  padding: 0.75rem 1.25rem;
  background-color: var(--color-brand);
  color: var(--color-white);
  font-family: var(--font-brand);
  text-transform: uppercase;
  font-size: 0.85rem;
  text-align: center;
  border-radius: 5px;
}

/* Date / Hours / Location, each its own icon + text row — event's own
   detail page only (views/events/show.ejs; cards keep the older single-line
   .event-meta). A little larger than body text, on request. */
.event-info {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-top: 0.75rem;
}

.event-info-row {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  font-size: 1.15rem;
  color: var(--color-brand);
}

.event-info-icon {
  width: 1.3rem;
  height: 1.3rem;
  flex: none;
  color: var(--color-white);
}

/* BUY TICKETS (.button-secondary) stays white everywhere else — homepage
   cards, /events — on request. Only on the event's own detail page it's
   inverted to match .button's colors (and swap the same way on hover),
   so it pairs better with MORE INFO sitting right next to it here. */
.event-detail .button-secondary {
  background-color: var(--color-brand);
  color: var(--color-white);
}

.event-detail .button-secondary:hover {
  background-color: var(--color-white);
  color: var(--color-brand);
}

/* Slightly tighter than the sitewide section rhythm (3rem) — scoped to
   this page only, doesn't touch homepage section spacing. */
.event-detail section {
  padding-top: 2rem;
  padding-bottom: 2rem;
}

.event-hero-image {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 5px;
  margin-bottom: 1rem;
}

.event-map {
  display: block;
  width: 100%;
  height: 280px;
  border: none;
  border-radius: 5px;
}

.event-map-address {
  font-style: normal;
  color: var(--color-brand);
  margin-top: 0.75rem;
}

/* Line-up and Timetable stack (mobile default — Timetable under Line-up)
   unless there's room for two real columns side by side. */
@media (min-width: 901px) {
  .lineup-timetable-columns {
    display: flex;
    align-items: flex-start;
    gap: 4rem;
  }

  .lineup-timetable-columns > section {
    flex: 1;
    min-width: 0;
  }
}

/* Line-up as one run of text: "ARTIST / ARTIST / ARTIST /" — each name
   followed by a white slash, including after the last one. */
.lineup-list {
  font-family: var(--font-brand);
  text-transform: uppercase;
  margin: 0;
  line-height: 1.8;
}

.lineup-separator {
  color: var(--color-white);
  margin: 0 0.35rem;
}

/* Only an artist with a URL gets the blue/clickable treatment — one
   without a link stays plain white, so blue reliably signals "clickable". */
.lineup-artist-link {
  color: var(--color-brand);
  text-decoration: none;
  transition: color 0.3s ease;
}

.lineup-artist-link:hover {
  color: var(--color-white);
}

.lineup-artist-plain {
  color: var(--color-white);
}

.timetable-table {
  width: 100%;
  border-collapse: collapse;
}

.timetable-table td {
  font-family: var(--font-brand);
  text-transform: uppercase;
  color: var(--color-white);
  padding: 0.4rem 0;
}

.timetable-table td:last-child {
  text-align: right;
}


.faq-item {
  border-bottom: 1px solid #333;
  padding: 0.75rem 0;
}

.faq-item summary {
  cursor: pointer;
  font-family: var(--font-brand);
  text-transform: uppercase;
  color: var(--color-brand);
}

/* FAQ answer text — see lib/richText.js for the tiny markup it supports
   (bold subtitles, links, line breaks). */
.faq-answer {
  margin-top: 0.75rem;
}

.faq-answer a {
  color: var(--color-brand);
}

/* Same size as the smaller event-card buttons (.event-list .event-buttons
   .button) — scoped here rather than changing that shared size. */
.faq-button {
  padding: 0.75rem 1.5rem;
  min-height: 2.5rem;
  margin: 0.5rem 0 0;
}

/* --- Public releases --- */

.release-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 2rem;
  margin-top: 1rem;
}

/* The 220px floor above naturally collapses to a single column once two
   cards plus the gap no longer fit a narrow phone's width — two columns
   fit fine, just at a smaller fixed card size instead of that floor. */
@media (max-width: 900px) {
  .release-list {
    grid-template-columns: repeat(2, 1fr);
    gap: 1rem;
  }
}

.release-card-image {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 5px;
}

.release-card h3 {
  margin-bottom: 0.25rem;
  font-size: 0.9rem;
}

/* Artist and title each get their own line rather than sitting side by
   side with a dash between them — used on both the homepage slider and
   the standalone /releases archive page. */
.release-card-artist,
.release-card-title {
  display: block;
}

.release-card > a {
  text-decoration: none;
}

/* Homepage Releases section only — plain black background (no feature
   panel), a horizontal auto-scrolling track instead of the archive
   page's static grid. A few teaser-specific tweaks (smaller title, no
   underline, rounded + slightly scaling artwork) don't touch the
   standalone /releases archive page's own layout, which still uses the
   plain .release-list grid above. */
.releases-slider {
  overflow: hidden;
  margin-top: 2rem;
  /* Vertical padding gives room for .release-card-image's hover scale
     (below) to grow without being clipped by this overflow:hidden. */
  padding: 0.5rem 0;
  cursor: grab;
  /* Let vertical touch gestures still scroll the page normally — only
     horizontal panning is handled ourselves, in releases-slider.js. */
  touch-action: pan-y;
  /* Without this, starting a drag on top of a card's artwork/text can
     kick off the browser's own native image-drag or text-selection
     instead of (or as well as) releases-slider.js's own dragging —
     the native gesture takes over pointer input, so our pointermove
     handler stops receiving events partway through. */
  user-select: none;
  -webkit-user-drag: none;
}

.releases-slider.is-dragging {
  cursor: grabbing;
}

.releases-track {
  display: flex;
  gap: 2rem;
  width: max-content;
  will-change: transform;
}

.releases-track .release-card {
  flex: 0 0 auto;
  width: 220px;
}

#releases .release-card > a {
  text-decoration: none;
}

/* Links are natively draggable in Chrome (e.g. drag-to-bookmark), and
   that native drag is tied directly to the anchor/image themselves —
   it isn't inherited from .releases-slider's own user-drag/user-select
   rules above. Without repeating those here, starting a real drag on
   a card hands the gesture to the browser's native link-drag instead
   of releases-slider.js, which silently swallows all pointermove
   events for that gesture. */
#releases .release-card > a,
#releases .release-card-image {
  user-select: none;
  -webkit-user-drag: none;
}

#releases .release-card-image {
  border-radius: 5px;
  transition: transform 0.3s ease;
}

#releases .release-card-image:hover {
  transform: scale(1.04);
}

.releases-load-more {
  text-align: center;
  margin-top: 3rem;
}

.releases-load-more .button {
  padding: 0.75rem 1.5rem;
  min-height: 2.5rem;
}

.release-detail {
  max-width: 480px;
}

.release-hero-image {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  margin-bottom: 1rem;
}

/* --- Video embeds (admin preview + public list/detail) --- */

.video-embed {
  position: relative;
  aspect-ratio: 16 / 9;
  margin-bottom: 1rem;
  border-radius: 5px;
  overflow: hidden;
}

.video-embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* One column — each video takes the full section width. */
.video-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2rem;
  margin-top: 1rem;
}

.video-card h3 {
  margin-bottom: 0.25rem;
}

.video-card h3 a {
  color: var(--color-white);
  text-decoration: none;
}

/* Custom thumbnail + play button shown instead of YouTube's own idle-state
   chrome — the real player only gets loaded in on click (see
   video-facade.js). */
.video-facade {
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.video-facade-thumbnail {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.video-play-button {
  position: relative;
  z-index: 1;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background-color: var(--color-brand);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.3s ease, transform 0.3s ease;
}

.video-play-button svg {
  width: 24px;
  height: 24px;
  fill: var(--color-white);
  margin-left: 3px;
  transition: fill 0.3s ease;
}

.video-facade:hover .video-play-button {
  background-color: var(--color-white);
  transform: scale(1.08);
}

.video-facade:hover .video-play-button svg {
  fill: var(--color-brand);
}

.video-detail {
  max-width: 720px;
}

/* --- Admin gallery photo rows --- */

.gallery-thumb {
  width: 100px;
  height: 100px;
  object-fit: cover;
}

/* Overrides .repeatable-row input's flex:1 1 200px (meant for the row's
   text/URL fields), which would otherwise stretch this radio button out. */
.gallery-cover-pick input {
  flex: 0 0 auto;
  width: auto;
}

/* --- Public photo gallery --- */

/* The "in between" archive page (views/gallery/list.ejs) — one card per
   event's gallery. Its own class rather than reusing .event-list, so it
   can have a fixed column count independent of the homepage Events
   section; .event-card/.event-card-image styling (image treatment) is
   still shared. */
.gallery-list {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 2rem;
  margin-top: 1rem;
}

.gallery-list .event-card-image {
  border-radius: 5px;
}

@media (max-width: 600px) {
  .gallery-list {
    grid-template-columns: 1fr;
  }
}

/* Masonry-style layout — images keep their original aspect ratio (no
   cropping) and stack to fill each column's height, which is what gives
   it the "dynamic" look. Positioned by gallery-masonry.js (each item
   absolutely placed into whichever column is currently shortest) rather
   than CSS `columns`, which rebalances — and so visibly reshuffles —
   every already-placed photo each time infinite scroll appends more.
   The gap (2rem, read from gallery-masonry.js's own `gap` constant)
   matches .release-list's gap, and the image radius matches
   .release-card-image's, for consistency with the homepage. Column
   count (3 desktop / 2 at <=900px) is decided in JS, not a media query,
   so both stay in sync. */
.gallery-grid {
  position: relative;
  margin-top: 1rem;
}

.gallery-grid-item {
  display: block;
}

.gallery-grid-image {
  width: 100%;
  height: auto;
  display: block;
  border-radius: 5px;
}

/* --- Gallery lightbox --- */

body.lightbox-open {
  overflow: hidden;
}

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(0, 0, 0, 0.94);
}

.lightbox[hidden] {
  display: none;
}

.lightbox-image {
  max-width: 88vw;
  max-height: 88vh;
  width: auto;
  height: auto;
  display: block;
}

.lightbox-close,
.lightbox-prev,
.lightbox-next {
  position: fixed;
  background: none;
  border: none;
  color: var(--color-white);
  cursor: pointer;
  line-height: 1;
  transition: color 0.2s ease;
}

.lightbox-close:hover,
.lightbox-prev:hover,
.lightbox-next:hover {
  color: var(--color-brand);
}

.lightbox-close {
  top: 1.5rem;
  right: 1.5rem;
  font-size: 2.5rem;
}

.lightbox-prev,
.lightbox-next {
  top: 50%;
  transform: translateY(-50%);
  font-size: 4rem;
  padding: 0 0.5rem;
}

.lightbox-prev {
  left: 1rem;
}

.lightbox-next {
  right: 1rem;
}

@media (max-width: 600px) {
  .lightbox-prev,
  .lightbox-next {
    font-size: 2.5rem;
  }

  .lightbox-close {
    top: 1rem;
    right: 1rem;
    font-size: 2rem;
  }
}

/* --- Aftermovie modal (shares .lightbox/.lightbox-close above) --- */

.aftermovie-modal-frame {
  width: 88vw;
  max-width: 960px;
  aspect-ratio: 16 / 9;
}

.aftermovie-modal-frame iframe {
  width: 100%;
  height: 100%;
  border: none;
  border-radius: 5px;
}

/* --- Public livesets --- */

.liveset-list {
  list-style: none;
  padding: 0;
  margin-top: 1rem;
}

.liveset-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 1rem 0;
  border-bottom: 1px solid #333;
}

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

/* --- Homepage media section --- */

/* Two-up grid for the two Media cards (Videoclips / Photo Gallery). Same
   gap as the event cards above. */
.media-links {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 2rem;
  margin-top: 1rem;
}

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

.media-card {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 16 / 9;
  background-size: cover;
  background-position: center;
  text-decoration: none;
  overflow: hidden;
  border-radius: 5px;
}

.media-card-recaps {
  background-image: url('/images/photos/media-section-thumbnail-recaps.jpg');
}

.media-card-gallery {
  background-image: url('/images/photos/media-section-thumbnail-gallery.jpg');
}

/* Subtle darken-on-hover, via a pseudo-element rather than an extra div so
   the markup stays a plain <a> per card. */
.media-card::before {
  content: '';
  position: absolute;
  inset: 0;
  background-color: rgba(0, 0, 0, 0);
  transition: background-color 0.3s ease;
  z-index: 1;
}

.media-card:hover::before {
  background-color: rgba(0, 0, 0, 0.35);
}

.media-card h3 {
  position: relative;
  z-index: 2;
  margin: 0;
  /* Smaller than the shared .button default — scoped here so it doesn't
     shrink PRE-REGISTER or any other button using the same class. */
  padding: 1rem 2rem;
}

/* --- Static statement pages/sections (About, Contact): deliberately more
   spacious typography than the rest of the site --- */

/* .static-page itself carries no width constraint — the homepage's About
   and Contact sections stay the same width as every other section this
   way. The standalone /about and /contact pages add .static-page-narrow
   below for their own centered, narrower column. */

.static-page-narrow {
  max-width: 640px;
  margin: 2rem auto;
  padding: 2rem 0;
}

.page-kicker {
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: #aaa;
  font-size: 0.85rem;
  margin: 0 0 0.5rem;
}

.static-page h1 {
  font-size: 3.5rem;
  letter-spacing: 0.03em;
  margin: 0 0 2rem;
}

.static-page p {
  font-size: 1.25rem;
  line-height: 1.6;
  margin: 0 0 1.75rem;
}

.static-page .statement {
  font-size: 1.5rem;
  font-weight: bold;
  letter-spacing: 0.03em;
  margin: 2rem 0;
}

/* --- Homepage About section: full-bleed blue "feature" panel with a
   subtle parallax background (background-attachment: fixed) and
   two-column body copy. Text styling intentionally matches the plain
   paragraph text in the Contact section (same font, size, line-height —
   just recolored to pure black for legibility on the blue background). */

.about-feature {
  position: relative;
  left: 50%;
  right: 50%;
  width: 100vw;
  margin-left: -50vw;
  margin-right: -50vw;
  background-image: url('/images/brand/background-blue.jpg');
  background-size: cover;
  background-position: center;
  background-attachment: fixed;
  padding-top: 6rem;
  padding-bottom: 6rem;
}

.about-feature-inner {
  max-width: 960px;
  margin: 0 auto;
  padding: 0 1rem;
}

.about-feature h2 {
  color: #000;
  margin: 0 0 2rem;
}

.about-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3rem;
}

.about-column p {
  color: #000;
  font-weight: 600;
  margin: 0;
}

@media (max-width: 900px) {
  .about-columns {
    grid-template-columns: 1fr;
    gap: 1.5rem;
  }

  /* background-attachment: fixed doesn't work on iOS Safari and can be
     janky on other mobile browsers — fall back to a normal scrolling
     background below the desktop breakpoint. */
  .about-feature {
    background-attachment: scroll;
  }
}

.contact-block {
  margin: 0 0 2.5rem;
}

.contact-block h2 {
  font-size: 1.1rem;
  letter-spacing: 0.05em;
  margin: 0 0 0.5rem;
  color: var(--color-brand);
}

/* The email paragraph's own default <p> top margin (not the h2's margin)
   was what created the visible gap here — collapsed with the h2's smaller
   margin-bottom, the browser default won out. Zero it so the email sits
   close under its title. */
.contact-block h2 + p {
  margin-top: 0;
}

/* --- Homepage Contact section: Contact info + Demos form side by side --- */

.contact-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3rem;
  margin-top: 1rem;
}

/* Grid items stretch to match the taller column by default — combined
   with .contact-address's margin-top: auto below, this pins the address
   to the bottom of the Contact column so it lines up with the bottom of
   the Demos card alongside it. */
.contact-column {
  display: flex;
  flex-direction: column;
}

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

/* Demos side of the Contact section: the blue card wraps only the <form>
   itself (title/intro copy above it stays on the plain background, matching
   the Contact column) — scoped here rather than in .demos-form's shared
   partial so the standalone /demos page keeps its plain look. */
.contact-column-demos .demos-form {
  background-color: var(--color-brand);
  background-image: url('/images/brand/background-blue.jpg');
  background-size: cover;
  background-position: center;
  padding: 2rem;
  border-radius: 5px;
  /* Matches the gap above (the title-to-text spacing set on
     .contact-column > h2), so title-to-text and text-to-form read as
     the same rhythm. */
  margin-top: 1.5rem;
}

.contact-column-demos label {
  font-family: var(--font-brand);
  /* Overrides the site-wide `label { font-weight: bold; }` rule — the
     Propaganda font only has one real weight, so requesting bold makes
     the browser synthesize a fake, chunky-looking one instead. */
  font-weight: normal;
  text-transform: uppercase;
  color: #000000;
}

/* Homepage demo card only — its blue textured background already gives
   the black fields enough contrast on its own, so the border can go
   (unlike the standalone /demos page, which sits on a black background
   and still needs that border to stay visible). Square corners, and a
   visible border only while actively focused. */
.contact-column-demos .demos-form input {
  border: 1px solid transparent;
  border-radius: 0;
}

.contact-column-demos .demos-form input:focus {
  border-color: var(--color-white);
  outline: none;
}

/* Demos form: same button size as the Media section cards (scoped here so
   it doesn't touch .button-tertiary's shared base size), and pure black
   fields instead of the browser-default white. Scoped to .demos-form so
   both the homepage card and the standalone /demos page match. */
.demos-form .button-tertiary {
  padding: 1rem 2rem;
  /* Reset the shared button's own margin so the space above/below is
     controlled entirely by .form-actions below, not doubled up here. */
  margin: 0;
}

.demos-form input {
  background-color: #000000;
  color: var(--color-white);
  border: 1px solid #333;
  border-radius: 5px;
}

/* Field groups: more space above each label (separating it from the
   previous field) than below it (staying close to its own input) — the
   form's default flex gap is uniform, so it's turned off here and replaced
   with explicit, asymmetric margins instead. */
.demos-form {
  gap: 0;
}

.demos-form label {
  margin-top: 1.25rem;
  margin-bottom: 0.4rem;
}

.demos-form label:first-of-type {
  margin-top: 0;
}

/* Space above and below the submit button matches the form's own 2rem
   padding — the same gap already visible beside the fields. */
.demos-form .form-actions {
  margin-top: 2rem;
}

/* "Contact" / "Drop Your Demo" column titles: explicit, equal space above
   and below (previously relied on the browser's default h2 margin, which
   made the gap above feel inconsistent with the gap below). */
.contact-column > h2 {
  margin: 1.5rem 0;
}

/* Reusable round social icon buttons — add more by dropping another
   .social-button link inside .social-buttons. */

.social-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin: 1rem 0;
}

.social-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 60px;
  position: relative;
}

.social-button-img {
  display: block;
  width: auto;
  height: 100%;
}

.social-button-img-hover {
  display: none;
}

.social-button:hover .social-button-img-default {
  display: none;
}

.social-button:hover .social-button-img-hover {
  display: block;
}

.contact-address {
  font-style: normal;
  margin-top: auto;
  padding-top: 1.5rem;
  color: var(--color-brand);
}

/* ==========================================================================
   Pre-register
   ========================================================================== */

/* --- Public sign-up page --- */

.preregister-page {
  max-width: 560px;
  margin: 0 auto;
}

.preregister-intro {
  margin-bottom: 0;
}

/* Same blue card as the homepage demo form, standalone so it doesn't
   depend on that form's markup or selectors. */
.preregister-form {
  gap: 0;
  margin-top: 1.5rem;
  padding: 2rem;
  background-color: var(--color-brand);
  background-image: url('/images/brand/background-blue.jpg');
  background-size: cover;
  background-position: center;
  border-radius: 5px;
}

.preregister-form label {
  font-family: var(--font-brand);
  /* The Propaganda font has one real weight; asking for bold makes the
     browser fake a chunky one (same reason as .contact-column-demos label). */
  font-weight: normal;
  text-transform: uppercase;
  color: var(--color-white);
  margin-bottom: 0.4rem;
}

.preregister-form input[type="email"] {
  background-color: #000000;
  color: var(--color-white);
  border: 1px solid transparent;
  border-radius: 0;
}

.preregister-form input[type="email"]:focus {
  border-color: var(--color-white);
  outline: none;
}

/* Marketing consent: checkbox beside wrapped text, not the uppercase brand
   label style used for the field above. */
.preregister-consent {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  margin-top: 1.25rem;
}

.preregister-consent input {
  flex-shrink: 0;
  width: 1.25rem;
  height: 1.25rem;
  margin: 0.1rem 0 0;
  padding: 0;
  accent-color: #000000;
  cursor: pointer;
}

.preregister-form .preregister-consent label {
  margin: 0;
  font-family: var(--font-body);
  text-transform: none;
  font-size: 0.9rem;
  line-height: 1.45;
  cursor: pointer;
}

.preregister-form .form-actions {
  margin-top: 2rem;
}

.preregister-form .button-tertiary {
  padding: 1rem 2rem;
  margin: 0;
}

/* Honeypot: off-screen rather than display:none, since some bots skip
   fields that are display:none. Shared by every public form that has one
   (pre-register, demos). */
.form-hp {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.preregister-note {
  margin-top: 1rem;
  font-size: 0.8rem;
}

/* --- Admin --- */

.slug-field {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.slug-field input {
  flex: 1 1 12rem;
  min-width: 8rem;
}

.slug-field-affix {
  color: var(--admin-text);
  white-space: nowrap;
}

.field-hint {
  margin: 0.35rem 0 0;
  font-size: 0.85rem;
  color: var(--admin-text);
}

.field-hint-inline {
  font-size: 0.85rem;
  font-weight: normal;
  color: var(--admin-text);
}

.admin-filter-bar {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 1rem;
  margin: 1.5rem 0 1rem;
}

.admin-filter-field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

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

/* Event preview link / pending-schedule banners (views/admin/events/form.ejs). */
.admin-notice {
  margin: 1rem 0;
  padding: 1rem 1.25rem;
  background-color: var(--admin-surface);
  border: 1px solid var(--admin-border);
  border-radius: 10px;
  font-size: 0.9rem;
}

.admin-notice-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 0.5rem;
}

.admin-notice-row input[type="text"] {
  flex: 1;
  min-width: 0;
}

.admin-schedule-row {
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--admin-border);
}

.admin-schedule-row .admin-notice-row {
  align-items: stretch;
}

.admin-schedule-row input[type="datetime-local"] {
  flex: 0 0 auto;
}

.admin-pagination {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  margin-top: 1rem;
}

/* ==========================================================================
   Events dropdown (header) and /events page
   ========================================================================== */

/* Same technique as the pre-register honeypot, but for real content: text
   that's there for screen readers/SEO (the /events page's <h1>) without
   adding a visible second heading above "UPCOMING EVENTS". */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* /events: the first section starts flush at the top like any other page
   title, instead of taking the extra 3rem every homepage section gets. */
.events-page-first {
  padding-top: 0;
}

.nav-submenu a {
  display: block;
}

/* Blue arrow that folds the Events sub-menu in the mobile sidebar. Desktop
   uses hover/focus instead, so it's hidden there (the mobile media query
   below turns it on). */
.nav-submenu-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  background: none;
  border: none;
  color: var(--color-brand);
  cursor: pointer;
}

.nav-submenu-toggle svg {
  width: 1.4rem;
  height: 1.4rem;
  transition: transform 0.3s ease;
}

@media (min-width: 901px) {
  .nav-item {
    position: relative;
  }

  /* Dropdown: hidden until the "Events" item is hovered or keyboard-focused.
     The ::before strip bridges the gap between the item and the panel so the
     pointer never leaves the hover area on its way down. */
  .nav-submenu {
    position: absolute;
    top: calc(100% + 1rem);
    left: 50%;
    z-index: 200;
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    min-width: 100%;
    padding: 1.1rem 1.5rem;
    white-space: nowrap;
    background-color: var(--color-background);
    border-radius: 5px;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translate(-50%, -6px);
    transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s;
  }

  .nav-submenu::before {
    content: '';
    position: absolute;
    top: -1rem;
    left: 0;
    right: 0;
    height: 1rem;
  }

  .nav-item-has-menu:hover .nav-submenu,
  .nav-item-has-menu:focus-within .nav-submenu {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translate(-50%, 0);
  }

  .site-nav .nav-submenu a {
    font-size: 1.1rem;
  }
}

/* Mobile sidebar: no hover on touch, so the sub-menu is collapsed behind the
   blue arrow next to "Events" and unfolds with a short height/fade
   transition — by tapping the arrow, or by itself when the sidebar opens
   (public/js/nav.js sets .is-open on .nav-item-has-menu). While collapsed
   it is also visibility:hidden, so its links can't be tabbed to; the
   visibility change is delayed until the fold-up finishes. */
@media (max-width: 900px) {
  .nav-item-has-menu {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0 0.25rem;
  }

  .nav-submenu-toggle {
    display: inline-flex;
  }

  .nav-item-has-menu.is-open .nav-submenu-toggle svg {
    transform: rotate(180deg);
  }

  .nav-submenu {
    display: flex;
    flex-basis: 100%;
    flex-direction: column;
    gap: 0.9rem;
    max-height: 0;
    margin-top: 0;
    padding-left: 0.85rem;
    overflow: hidden;
    border-left: 1px solid #333;
    opacity: 0;
    visibility: hidden;
    transition: max-height 0.35s ease, margin-top 0.35s ease, opacity 0.25s ease, visibility 0s linear 0.35s;
  }

  .nav-item-has-menu.is-open .nav-submenu {
    max-height: 8rem;
    margin-top: 0.9rem;
    opacity: 1;
    visibility: visible;
    transition: max-height 0.35s ease, margin-top 0.35s ease, opacity 0.35s ease 0.05s, visibility 0s;
  }

  /* Sized so the longest item, "Videoclips & Recaps", stays on one line in
     the 260px sidebar. */
  .site-nav .nav-submenu a {
    font-size: 0.95rem;
    color: var(--color-body-text);
  }

  .site-nav .nav-submenu a:hover {
    color: var(--color-brand);
  }
}

@media (prefers-reduced-motion: reduce) {
  .nav-submenu,
  .nav-submenu-toggle svg {
    transition: none !important;
  }
}

/* ==========================================================================
   Latest release button — see views/partials/latest-release.ejs
   Bottom-left on desktop (hover expands a preview), centered at the bottom
   on mobile (no hover there, so the preview panel is left out and the play
   button does the talking).
   ========================================================================== */

/* z-index sits under the gallery/aftermovie popups (300) and the
   back-to-top button and cookie banner, so nothing ever hides behind it. */
.latest-release {
  position: fixed;
  /* Same distance from the edges as the back-to-top button on the right. */
  left: 1.5rem;
  bottom: 1.5rem;
  z-index: 90;
  display: block;
  width: 14rem;
  color: var(--color-white);
  text-decoration: none;
  /* Drop shadow follows the whole shape (bar + preview) as it grows. */
  filter: drop-shadow(0 6px 20px rgba(0, 0, 0, 0.55));
}

.latest-release-bar {
  position: relative;
  display: flex;
  align-items: center;
  overflow: hidden;
  background-color: #000000;
  border-radius: 5px;
  transition: border-radius 0.25s ease;
}

.latest-release-link {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
  padding: 0.5rem 0.5rem 0.5rem 0.5rem;
  color: inherit;
  text-decoration: none;
}

.latest-release-thumb {
  flex: none;
  width: 2rem;
  height: 2rem;
  object-fit: cover;
  border-radius: 3px;
}

.latest-release-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: var(--font-brand);
  font-size: 0.75rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
  /* The brand font's capitals sit slightly above the middle of their line
     box, so the label is optically ~1.5px too high next to the artwork and
     the play button, which are centered by their boxes. */
  transform: translateY(0.12em);
  transition: color 0.3s ease;
}

.latest-release-arrow {
  flex: none;
  width: 0.9rem;
  height: 0.9rem;
  color: var(--color-brand);
}

/* The preview sits flush on top of the bar (no gap) so the pointer can
   travel from one to the other without ever leaving the link, and reads
   as the bar growing upward. Hidden with visibility, not display, so its
   artwork can still lazy-load and is ready the moment it's needed. */
.latest-release-preview {
  position: absolute;
  left: 0;
  bottom: 100%;
  width: 100%;
  display: block;
  overflow: hidden;
  color: inherit;
  text-decoration: none;
  background-color: #000000;
  border-radius: 5px 5px 0 0;
  opacity: 0;
  visibility: hidden;
  transform: translateY(6px);
  transition: opacity 0.25s ease, transform 0.25s ease, visibility 0.25s;
}

.latest-release-art {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
}

.latest-release-meta {
  display: block;
  padding: 0.75rem 0.9rem 0.85rem;
}

.latest-release-artist,
.latest-release-title {
  display: block;
  font-family: var(--font-brand);
  text-transform: uppercase;
}

.latest-release-artist {
  font-size: 0.85rem;
  color: var(--color-white);
}

.latest-release-title {
  margin-top: 0.15rem;
  font-size: 0.75rem;
  color: var(--color-body-text);
}

.latest-release:hover .latest-release-preview,
.latest-release:focus-within .latest-release-preview {
  opacity: 1;
  visibility: visible;
  transform: none;
}

.latest-release:hover .latest-release-bar,
.latest-release:focus-within .latest-release-bar {
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

.latest-release:hover .latest-release-label {
  color: var(--color-brand);
}

.latest-release-link:focus-visible,
.latest-release-play:focus-visible {
  outline: 2px solid var(--color-brand);
  outline-offset: -2px;
}

/* Round play/pause button, shown by latest-release.js only once a preview
   has been found. Hover/pressed feedback is a brightness shift, not a new
   colour, to stay inside the site palette. */
.latest-release-play {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  margin-right: 0.5rem;
  padding: 0;
  color: var(--color-white);
  background-color: var(--color-brand);
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  transition: filter 0.2s ease;
}

/* The play button takes the arrow's place at the right of the bar. */
.latest-release.has-preview .latest-release-arrow {
  display: none;
}

.latest-release-play[hidden] {
  display: none;
}

.latest-release-play:hover {
  filter: brightness(1.2);
}

.latest-release-play svg {
  width: 0.85rem;
  height: 0.85rem;
}

.latest-release-play .icon-pause,
.latest-release.is-playing .latest-release-play .icon-play {
  display: none;
}

.latest-release.is-playing .latest-release-play .icon-pause {
  display: block;
}

/* Thin playback line along the bottom edge of the bar. */
.latest-release-progress {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background-color: var(--color-brand);
  transform: scaleX(0);
  transform-origin: left;
}

/* The cookie banner is centered along the bottom and, on a narrower
   desktop window, would run underneath this button — so the button waits
   until the visitor has answered the banner. */
body:has(#consent-banner:not([hidden])) .latest-release {
  display: none;
}

@media (max-width: 900px) {
  /* Centered, but never wide enough to run into the back-to-top button in
     the right-hand corner (24px gap + 48px button + 10px air, mirrored on
     both sides to keep it centered). */
  .latest-release {
    left: 50%;
    width: min(14rem, calc(100% - 164px));
    transform: translateX(-50%);
  }

  .latest-release-preview {
    display: none;
  }

  .latest-release:hover .latest-release-bar,
  .latest-release:focus-within .latest-release-bar {
    border-radius: 5px;
  }

  .latest-release-link {
    gap: 0.6rem;
  }

  .latest-release-label {
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 0.7rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .latest-release-preview,
  .latest-release-bar {
    transition: none !important;
  }
}
