/* ==========================================================================
   Me You Us Them — site-wide styles
   Design language matched from imaginaldisk.world: fixed pastel iridescent
   gradient backdrop, glowing lavender serif type, rectangular holographic-
   bordered buttons, centered single-column layout.
   Organized: variables -> reset -> layout -> components -> page sections -> responsive
   ========================================================================== */

@font-face {
  font-family: "Unison Pro";
  src: url("../fonts/unison-pro-bold.otf") format("opentype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --gradient-stops: linear-gradient(
    45deg,
    #ebddca,
    #b6d2dd,
    #dea4bd,
    #a581c4,
    #60cbb6,
    #8adcde
  );
  --gradient-stops-soft: linear-gradient(
    45deg,
    rgba(239, 232, 223, 0.85),
    rgba(182, 210, 221, 0.85),
    rgba(222, 164, 189, 0.85),
    rgba(165, 129, 196, 0.85),
    rgba(170, 235, 152, 0.85),
    rgba(138, 220, 222, 0.85)
  );

  --color-accent: #B48EF0;
  --color-accent-deep: #9B6EE8;
  --color-text: rgba(0, 0, 0, 0.85);
  --color-text-soft: rgba(0, 0, 0, 0.6);
  --color-border: rgba(0, 0, 0, 0.15);
  --color-white: #ffffff;

  --font-serif: "Unison Pro", Impact, Haettenschweiler, "Franklin Gothic Bold", "Arial Narrow Bold", sans-serif;

  --nav-height: 76px;
  --max-width: 900px;
}

* {
  box-sizing: border-box;
}

/* Native scrolling is switched off completely on the home page (see
   #page-wrapper below) — there's nothing to scroll-behavior: smooth
   anymore, navigation is entirely a JS-driven transform animation. */
html {
  height: 100%;
  overflow: hidden;
}

body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  min-height: 100vh;
  color: var(--color-text);
  font-family: var(--font-serif);
  line-height: 1.4;
  text-transform: uppercase;
}

/* Fixed background photo, zoomed in 10% beyond a plain "cover" fit. A scale
   transform is used (rather than a background-size tweak) since "cover"
   is a fitting algorithm, not a percentage — this is the reliable way to
   go beyond it. Lives on a dedicated layer so the transform doesn't touch
   any real content, with a translucent scrim so body text stays legible
   no matter which part of the image ends up behind it. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.4)) center center / cover no-repeat,
    url("../img/hero-band-bg.jpg?v=3") center center / cover no-repeat;
  transform: scale(1.1) translateX(-1%);
  filter: blur(0);
  transition: filter 0.4s ease;
}

/* Added by main.js the moment the page scrolls off the very top, removed
   only once it's back at scrollY 0 — independent of scroll direction. */
body.is-scrolled::before {
  filter: blur(5px);
}

/* News/Live/Contact: background stays blurred all the time, regardless of
   scroll position — set directly in each page's <body> class. */
body.bg-blurred::before {
  filter: blur(5px);
}

/* TV static, drawn onto this canvas by initStaticOverlay() in main.js —
   sits directly above the background photo (same fixed layer, painted after
   it so it visually overlays it) and below all real page content. Rendered
   at a fraction of screen resolution and scaled up with crisp pixel edges
   (rather than smoothed) so it reads as chunky analog grain, not a blur. */
#static-overlay {
  position: fixed;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  opacity: 0.05;
  pointer-events: none;
  image-rendering: pixelated;
}

img {
  max-width: 100%;
  display: block;
}

a {
  color: inherit;
  text-decoration: none;
}

h1, h2, h3 {
  font-family: var(--font-serif);
  font-weight: 400;
  margin: 0;
}

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

/* Glowing lavender serif treatment, used on the band name / release titles */
.glow-text {
  color: var(--color-accent);
  filter: drop-shadow(0 0 24px rgba(180, 142, 240, 0.8));
  animation: glow-pulse 2.5s ease-in-out infinite alternate;
}

@keyframes glow-pulse {
  0% { filter: drop-shadow(0 0 8px rgba(180, 142, 240, 0.6)); }
  100% { filter: drop-shadow(0 0 28px rgba(180, 142, 240, 0.9)); }
}

@keyframes mbglow {
  0% { background-position: 25% 50%; }
  50% { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}

.btn {
  display: inline-block;
  padding: 1rem 2rem;
  border: 2px solid var(--color-accent);
  border-radius: 0;
  font-family: var(--font-serif);
  font-size: 1.1rem;
  text-transform: uppercase;
  color: var(--color-white);
  background: var(--gradient-stops-soft);
  background-size: 300% 300%;
  animation: mbglow 10s ease-in-out infinite alternate;
}

/* ---------------------------------- Nav ---------------------------------- */

.site-nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  min-height: var(--nav-height);
  display: flex;
  align-items: center;
  justify-content: flex-start;
  padding: 0.75rem 1.5rem;
}

/* Occupies the same top-left spot as .site-nav__links — shown only on the
   hero, where NAV_VISIBILITY (see initPageNav in main.js) leaves that
   corner empty, and hidden everywhere the real menu takes over instead. */
.site-nav__brand {
  font-family: var(--font-serif);
  font-size: 2.25rem;
}

/* Keeps .glow-text's lavender color but not its glow — same treatment as
   .section__heading.glow-text below. */
.site-nav__brand.glow-text {
  filter: none;
  animation: none;
}

.site-nav__links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem 0.9rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.site-nav__links a {
  font-family: var(--font-serif);
  font-size: 1.875rem;
  color: var(--color-accent);
  text-decoration: none;
}

.site-nav__toggle {
  display: none;
}

/* Small brand-icon links, pinned to the top-right corner of the nav bar */
.site-nav__social {
  position: absolute;
  right: clamp(1rem, 4vw, 2rem);
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: 0.55rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.site-nav__social a {
  display: inline-flex;
  color: var(--color-accent);
}

.site-nav__social svg {
  width: 33.75px;
  height: 33.75px;
  fill: currentColor;
  display: block;
}

/* ------------------------------ Page sections ------------------------------ */

.section {
  padding: 3rem 1.5rem;
  max-width: var(--max-width);
  margin: 0 auto;
  text-align: center;
}

.section--tight {
  max-width: 560px;
}

.section__heading {
  font-size: clamp(3.75rem, 7.5vw, 4.6875rem);
  margin-bottom: 2rem;
}

/* "Contact/Join Email List" is long enough to wrap at the default size,
   which pushed the form below the fixed 100vh page (see .page-section) —
   scaled down specifically here rather than shrinking every heading. */
#contact-section .section__heading {
  font-size: clamp(2rem, 4.5vw, 2.8125rem);
  margin-bottom: 1.5rem;
}

/* News/Live/Contact keep .glow-text's lavender color but not its glow —
   same treatment as .site-nav__brand.glow-text above. */
.section__heading.glow-text {
  filter: none;
  animation: none;
}

.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.7s ease, transform 0.7s ease;
}

.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* Home page: a plain vertical stack of exactly-100vh "pages" (nav is
   fixed/overlaid, not in document flow, so 100vh is the whole frame, not
   100vh + nav height) — see initPageNav() in main.js for how this wrapper
   gets moved. Fixed height (not min-height) on every page matters here:
   the translateY(-N * 100vh) math driving navigation assumes every page is
   exactly one viewport tall, so any page whose content overflows that
   scrolls internally instead (see .page-section's own overflow-y) rather
   than growing taller and throwing that math off. */
#page-wrapper {
  transition: transform 0.7s cubic-bezier(0.65, 0, 0.35, 1);
  will-change: transform;
}

.hero {
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  padding-bottom: 3rem;
}

/* Positioned at the bottom of whichever section it's in — .hero (flex,
   justify-content: flex-end) or .page-section (absolute — see below — since
   .page-section is position: relative). The bounce animation lives on the
   inner svg rather than this element itself, since this element's own
   transform is already doing the horizontal-centering job on .page-section
   variants (see .page-section .scroll-cue) and a single transform property
   can't do both at once. */
.scroll-cue {
  display: inline-flex;
  color: #000;
  filter: drop-shadow(0 0 8px rgba(180, 142, 240, 0.85));
}

/* On the black pages, black-on-black would be invisible. */
.dark-section .scroll-cue {
  color: var(--color-white);
}

.scroll-cue svg {
  width: 2.25rem;
  height: 2.25rem;
  fill: currentColor;
  animation: scroll-cue-bounce 2s ease-in-out infinite;
}


@keyframes scroll-cue-bounce {
  0%, 100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(10px);
  }
}

/* Releases/News/Live/Contact: each a full 100vh page (see #page-wrapper
   above). position: relative so its own .scroll-cue can be pinned to its
   bottom edge; overflow-y: auto so a page whose content is taller than the
   viewport scrolls internally within its own fixed 100vh box, rather than
   growing taller and breaking the wrapper's translateY(-N * 100vh) math. */
.page-section {
  position: relative;
  height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  overflow-y: auto;
  padding: calc(var(--nav-height) + 2rem) 1.5rem 5rem;
}

/* News/Live/Contact only (not Releases, which is already deliberately
   sized/positioned to exactly fill the page — see .release__art) — shifts
   the heading and content down toward vertical center instead of sitting
   right below the top padding. */
.page-section--centered {
  justify-content: center;
}

.page-section .scroll-cue {
  position: absolute;
  bottom: 2rem;
  left: 50%;
  transform: translateX(-50%);
}

/* Up arrow back to the previous page — mobile only (see the mobile
   override below). Desktop already has an obvious way back (mouse wheel
   reverse, or the nav itself), but on mobile the down arrows are the only
   on-screen hint that paging exists at all, so pages 2+ get a matching
   one pointing back up. Reuses the same down-chevron SVG rather than a
   separate mirrored path — rotating the whole link 180deg flips both the
   icon and its bounce animation's direction together, since the bounce
   lives on a child element inside this rotated coordinate space. */
/* .page-section .scroll-cue-- prefix (not just .scroll-cue--up) to match
   .page-section .scroll-cue's specificity (two classes) — a bare
   .scroll-cue--up loses to it regardless of source order, since higher
   specificity wins outright rather than being a tiebreaker. */
.page-section .scroll-cue--up {
  display: none;
}

.dark-section {
  background: rgba(0, 0, 0, 0.5);
  --color-text: var(--color-white);
  --color-text-soft: rgba(255, 255, 255, 0.65);
  --color-border: rgba(255, 255, 255, 0.15);
  /* Redefining the custom properties above only changes what elements that
     explicitly reference var(--color-text) resolve to (e.g. .show-row__date)
     — it doesn't retroactively change already-inherited color on elements
     that never set their own (e.g. .post__title, plain paragraph text),
     which would otherwise stay inherited-black-on-black. Setting color
     here too means THIS element's own resolved color updates, and that's
     what actually inherits down to everything under it. */
  color: var(--color-text);
}

/* Static grid, newest release first — three per row (not var(--max-width)'s
   usual 900px column: at this card size, 2 per row would need a 3rd row for
   5 releases and overflow one page — see .release__art below), wrapping to
   as many rows as needed. Replaced the old auto-scrolling single-row strip
   (see git history / prior conversation) with a plain grid once releases
   moved to their own full-page section with room to lay them out properly. */
#release-list {
  display: grid;
  /* Double resolution — 6 tracks standing in for the 3 visual columns — so
     the top row's 2 cards can be centered on a half-column boundary the
     bottom row's 3 cards don't land on. See the explicit placements below. */
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 1.5rem 2rem;
  width: 100%;
  max-width: 1200px;
}

/* Each release card — one per grid cell (see #release-list) */
.release {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0.25rem 0;
}

/* Explicit placement rather than the default left-to-right/top-to-bottom
   auto-flow: top row holds only the two newest releases, centered as a pair
   over the three-wide bottom row (each bottom card spans 2 of the 6 tracks;
   the top pair spans the middle 4, leaving 1 empty track on each side),
   with the newest (RELEASES[0], "It's Really Nothing") in the true center.
   Written by DOM order (RELEASES is newest-first) since #release-list has
   no ids/classes to hook per-release. */
#release-list > .release:nth-child(1) {
  grid-column: 2 / span 2;
  grid-row: 1;
}
#release-list > .release:nth-child(2) {
  grid-column: 4 / span 2;
  grid-row: 1;
}
#release-list > .release:nth-child(3) {
  grid-column: 1 / span 2;
  grid-row: 2;
}
#release-list > .release:nth-child(4) {
  grid-column: 3 / span 2;
  grid-row: 2;
}
#release-list > .release:nth-child(5) {
  grid-column: 5 / span 2;
  grid-row: 2;
}

/* Sized (not just width: 100%) so all 5 releases fit within one 100vh page
   without relying on .page-section's overflow-y: auto, which is no longer
   reachable: wheel/touch/keyboard are all claimed globally for page-to-page
   navigation (see initPageNav in main.js), so a section whose content
   doesn't fit has no way to be scrolled into view anymore. */
.release__art {
  width: min(360px, 36vh);
  margin: 0 auto;
}

/* Cover art doubles as a dropdown toggle for the release's streaming links */
.art-wrap {
  position: relative;
  width: 100%;
}

.art-toggle {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

/* Overlaid directly on the cover art (not positioned below it) so it's
   always fully within view — the art itself is guaranteed visible already,
   since the user just clicked it. */
.art-dropdown {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: stretch;
  gap: 0.5rem;
  padding: 1.5rem;
  overflow-y: auto;
  border: 2px solid #000;
  background: rgba(10, 8, 12, 0.88);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease;
  z-index: 20;
}

/* Hover/keyboard-focus reveals it directly, for pointer and keyboard users.
   The click/tap toggle in main.js (.is-open) still applies on top of this,
   since touch devices have no real hover state to rely on. */
.art-wrap:hover .art-dropdown,
.art-wrap:focus-within .art-dropdown,
.art-wrap.is-open .art-dropdown {
  opacity: 1;
  visibility: visible;
}

.art-dropdown a {
  padding: 0.65em 1em;
  font-family: var(--font-serif);
  font-size: 1.05rem;
  text-transform: uppercase;
  color: var(--color-white);
  text-align: center;
  transition: background 0.2s ease;
}

.art-dropdown a:hover,
.art-dropdown a:focus-visible {
  background: rgba(180, 142, 240, 0.15);
}

/* Mobile fullscreen streaming-links modal — see initReleaseLinksModal() in
   main.js. Deliberately a separate element living at the top of <body>
   (see index.html) rather than reusing .art-dropdown fixed-position in
   place: .art-dropdown lives inside #page-wrapper, which main.js gives a
   scroll-animating `transform` — and per spec, position: fixed inside a
   transformed ancestor is positioned relative to THAT ancestor, not the
   real viewport (confirmed by testing: it rendered as a giant box spanning
   every stacked page and intercepted clicks on the nav). .site-nav sits
   outside #page-wrapper for the exact same reason. */
.release-links-modal {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.5rem;
  padding: 4rem 1.5rem 2rem;
  background: rgba(10, 8, 12, 0.94);
  color: var(--color-white);
}

.release-links-modal[hidden] {
  display: none;
}

.release-links-modal__links a {
  display: block;
  padding: 1em;
  font-family: var(--font-serif);
  font-size: 1.4rem;
  text-transform: uppercase;
  color: var(--color-white);
  text-align: center;
}

.release-links-modal__links a:active {
  background: rgba(180, 142, 240, 0.15);
}

.release-links-modal__close {
  position: absolute;
  top: 1rem;
  right: 1rem;
  width: 2.75rem;
  height: 2.75rem;
  line-height: 1;
  font-size: 2rem;
  color: var(--color-white);
  background: none;
  border: 2px solid var(--color-accent);
  cursor: pointer;
}

/* News/Live/Contact content used to inherit a centered max-width column
   from the old .section wrapper — now that those pages are full-bleed
   .page-section black cards (needed so the black background reaches the
   viewport edges), the same constraint is applied directly to each page's
   content instead. */
#posts-list,
#shows-list,
.follow-buttons,
#bandsintown-widget {
  width: 100%;
  max-width: var(--max-width);
}

/* display: contents makes the wrapper invisible to layout — News's
   heading and #posts-list behave exactly as if they were direct children
   of .page-section--centered, same as before this wrapper existed. Only
   overridden on mobile (see below), where it needs to be a real box so a
   single transform can shift both as one unit. */
#news-content,
#contact-content {
  display: contents;
}

/* Shows list (live page) */
.shows-empty {
  color: var(--color-text-soft);
}

.show-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: 0.5rem 1.5rem;
  padding: 1.25rem 0;
  border-top: 1px solid var(--color-border);
  font-size: 1.725rem;
}

.show-row:last-child {
  border-bottom: 1px solid var(--color-border);
}

.show-row__date {
  font-family: var(--font-serif);
  color: var(--color-text);
  font-weight: bold;
  min-width: 9ch;
}

.show-row__venue {
  flex: 1 1 240px;
}

.show-row__location {
  color: var(--color-text-soft);
}

.show-row__link {
  text-decoration: underline;
}

.follow-buttons {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1rem;
  margin-bottom: 2rem;
}

/* 25% smaller than the base .btn (1rem 2rem padding / 1.1rem font-size) —
   scoped here rather than changing .btn itself, which the Contact page's
   Send button also uses at full size. */
.follow-buttons .btn {
  padding: 0.75rem 1.5rem;
  font-size: 0.825rem;
}

/* Swaps .btn's usual moving gradient glare for TV static instead (drawn by
   initButtonStatic() in main.js onto a canvas prepended into the button —
   see .btn__static below) — position: relative/overflow: hidden contains
   it, background covers the moment before the canvas's first frame (and,
   at 50% rather than solid, lets whatever's behind the button show through
   a bit, matching .dark-section). z-index: 0 (with position: relative)
   forces this button to establish its own stacking context, so
   .btn__static's z-index: -1 is scoped to just this element — without it,
   the canvas compared its negative z-index against a much wider, unrelated
   set of elements (whatever ancestor actually establishes the nearest
   stacking context) and ended up painted behind other content entirely,
   invisibly, despite otherwise being correctly sized/positioned/populated
   with real noise. */
.btn--static {
  position: relative;
  z-index: 0;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.5);
  animation: none;
}

.btn__static {
  position: absolute;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  opacity: 0.05;
  pointer-events: none;
  image-rendering: pixelated;
}

#bandsintown-widget {
  margin-bottom: 1.5rem;
}

/* The Bandsintown widget renders real same-origin DOM (not an iframe — that
   part of it is a tiny hidden messaging frame), but hardcodes its own
   pixel font-sizes on these specific elements rather than inheriting, so a
   font-size on an ancestor has no effect (confirmed) — these need direct,
   !important overrides. Scoped to just the date/location/venue text, not
   the RSVP/Tickets buttons or anything else in the widget (a whole-widget
   transform: scale() enlarged those too, which wasn't wanted). */
#bandsintown-widget .bit-date,
#bandsintown-widget .bit-mobile-date,
#bandsintown-widget .bit-location-under-desktop,
#bandsintown-widget .bit-location-under-mobile,
#bandsintown-widget .bit-venue {
  font-size: 150% !important;
}

/* Hides the RSVP button specifically, leaving Tickets in place. */
#bandsintown-widget .bit-rsvp {
  display: none !important;
}

/* The small share-icon box above the buttons — data-social-share-icon
   ="false" on .bit-widget-initializer (see index.html) is supposed to turn
   this off but doesn't actually stop it rendering, so it's hidden directly
   instead. */
#bandsintown-widget .bit-desktop-social-share-button,
#bandsintown-widget .bit-mobile-social-share-button {
  display: none !important;
}

/* .bit-event is the widget's own outer wrapping link — the whole listing
   (date, venue, location) navigates to the Bandsintown event page by
   default. Only Tickets (.bit-offers, nested inside .bit-event) should
   stay clickable, so pointer-events: none here and pointer-events: auto
   re-enabling it specifically on that one descendant. */
#bandsintown-widget .bit-event {
  pointer-events: none;
}

#bandsintown-widget .bit-offers {
  pointer-events: auto;
}

/* News page: posts list, newest first */
.post {
  padding: 2rem 0;
  border-top: 1px solid var(--color-border);
  text-align: left;
}

.post:last-child {
  border-bottom: 1px solid var(--color-border);
}

.post__header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem 1.5rem;
  margin-bottom: 1rem;
}

.post__title {
  font-size: clamp(1.5rem, 4vw, 2rem);
}

.post__date {
  font-family: var(--font-serif);
  font-size: 1rem;
  color: var(--color-text-soft);
  white-space: nowrap;
}

.post__body p {
  margin: 0 0 1em;
}

.post__body p:last-child {
  margin-bottom: 0;
}

/* A post's body can include a hand-written <a> (see posts.js) — the site's
   general a { color: inherit; text-decoration: none; } would otherwise make
   it look identical to the surrounding plain text. */
.post__body a {
  color: #ffe066;
  text-decoration: underline;
}

/* Contact form */
.contact-form {
  display: grid;
  /* minmax(0, 1fr) rather than the implicit auto column — auto sizes to
     the widest child's max-content (the textarea's intrinsic width, which
     runs wider than the form itself in this font), and grid doesn't clamp
     that down to the container on its own, so the form silently overflowed
     sideways on narrow viewports. */
  grid-template-columns: minmax(0, 1fr);
  gap: 1.5rem;
  width: 100%;
  max-width: 520px;
  margin: 0 auto;
  text-align: left;
}

.form-field {
  display: grid;
  /* Same overflow-blowout fix as .contact-form above, one level deeper —
     the email input's intrinsic max-content width alone exceeds this
     field's own (already-constrained) width, and without minmax(0, 1fr)
     grid lets a track grow past its container to fit that. */
  grid-template-columns: minmax(0, 1fr);
  gap: 0.4rem;
}

.form-field label {
  font-family: var(--font-serif);
  font-size: 1.1rem;
  color: var(--color-text);
}

.form-field input,
.form-field textarea {
  background: rgba(255, 255, 255, 0.5);
  border: 2px solid var(--color-accent);
  border-radius: 0;
  padding: 0.75em 1em;
  color: var(--color-text);
  font-family: var(--font-serif);
  font-size: 1.1rem;
  text-transform: uppercase;
}

.form-field input:focus,
.form-field textarea:focus {
  outline: none;
  filter: drop-shadow(0 0 16px rgba(180, 142, 240, 0.7));
}

.form-field textarea {
  resize: vertical;
  min-height: 140px;
}

/* honeypot field, hidden from real users, visible to bots that fill everything */
.form-field--hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.contact-form .btn {
  justify-self: center;
  cursor: pointer;
  font-size: 1.15rem;
}

.form-status {
  font-family: var(--font-serif);
  font-size: 1.1rem;
}

.form-status--success {
  color: var(--color-text);
}

.form-status--error {
  color: #7f170e;
}


/* ---------------------------------- Responsive ---------------------------------- */

@media (max-width: 720px) {
  :root {
    --nav-height: 132px;
    /* Fallback before syncSocialBarHeight() (main.js) measures the real
       value on load — same reasoning as --nav-height above. */
    --social-bar-height: 64px;
  }

  .site-nav {
    flex-direction: column;
    gap: 0.6rem;
    padding: 0.85rem 1.25rem;
  }

  /* Moved out of the nav entirely and pinned to the bottom of the screen
     instead — position: fixed works here despite being nested inside
     .site-nav because .site-nav itself is never transformed (unlike
     #page-wrapper — see the big comment on #release-links-modal in
     index.html for why that distinction matters for fixed positioning).
     Every .page-section, .hero, and .scroll-cue's bottom clearance is
     sized against --social-bar-height (see below) so content and the
     down-arrows don't end up underneath this bar. */
  .site-nav__social {
    position: fixed;
    top: auto;
    /* history: 0 -> 6px (still reported cut off on a real device) -> 34px
       (fixed the cutoff, but then read as too far off the bottom edge to
       look like a bottom bar anymore) -> back down here. If the cutoff
       report was really mobile Safari's own bottom toolbar overlaying the
       page (nothing in this layout sits near the true viewport edge in
       plain browser testing — confirmed via measurement), a small offset
       alone can't reliably satisfy both "flush with the bottom" and
       "clear of that chrome" at once; revisit with env(safe-area-inset-
       bottom) (needs viewport-fit=cover added to the viewport meta tag,
       not currently set) if this keeps bouncing back and forth. */
    bottom: 8px;
    left: 0;
    right: 0;
    transform: none;
    justify-content: center;
    padding: 0.6rem 1rem;
    z-index: 100;
  }

  /* Desktop's 2.25rem wraps "Me You Us Them" onto two lines once the nav
     switches to its narrow column layout above — scale it down with
     viewport width so it reliably stays on one line down to small phones. */
  /* Sized to the largest that reliably stays on one line across common
     phone widths (measured via binary search at 320/375/390/430px — the
     line-wrap threshold runs ~6.5-6.8vw depending on width, with a small
     safety margin kept below that measured ceiling for cross-browser
     rendering differences). */
  .site-nav__brand {
    font-size: clamp(1.25rem, 6.4vw, 1.8rem);
  }

  /* On a phone's tall/narrow viewport, background-size: cover (see
     body::before) has to crop most of the photo's width away just to fill
     the height — combined with the desktop scale(1.1) zoom, that cut the
     outer band members out of frame entirely. Switching to contain shows
     the whole photo (all band members, matching what desktop sees) letter-
     boxed above/below instead of cropped left/right; the zoom is dropped
     since contain already fits the full image with nothing to zoom past.

     The gradient (white tint) layer can't use contain the same way the
     photo does, though: per spec, background-size: contain on a CSS
     <image> with no intrinsic ratio — which a linear-gradient() is —
     resolves as if it were 100% 100%, i.e. it just fills the whole box
     instead of "containing" to anything. Confirmed happening: the 40%-
     opacity white tint was painting across the full viewport rather than
     just the photo, which blended with the black letterbox background
     into a flat mid-grey (255*0.4 = 102 — exactly the RGB(102,102,102)
     sampled from a screenshot) instead of staying pure black there. Sized
     explicitly instead, using the same --hero-photo-* rect
     syncHeroPhotoRect (main.js) already computes for #static-overlay/
     .site-nav__brand/.site-nav__social below, rather than the contain
     keyword, sidesteps the whole "no intrinsic ratio" problem — an
     explicit length always works regardless of the image's own ratio. */
  body::before {
    background-size: var(--hero-photo-width, 100%) var(--hero-photo-height, 100%), contain;
    background-position: var(--hero-photo-left, 0px) var(--hero-photo-top, 0px), center center;
    background-color: #000;
    /* Same transform as desktop's body::before (scale(1.1) translateX(-1%)). */
    transform: scale(1.1) translateX(-1%);
  }
}

@media (max-width: 480px) {
  .site-nav__links {
    gap: 0.4rem 0.55rem;
  }

  /* Sized to the largest that keeps exactly two links per row (the
     original wrap pattern) rather than letting a row hold one or three —
     measured by scanning font-sizes per page/width combo and checking
     every row lands at exactly 2 links. Covers releases/news/live-section
     — each of those pages' visible link set includes the word "Contact"
     (longest of the set) paired with a short word on one row, which
     scales at ~6.9-7.2vw across widths; 6.6vw stays under that with a
     safety margin for cross-browser rendering differences.

     contact-section's own visible set has no long word in it ("Home News
     Live Store") and can go noticeably bigger before losing the 2-per-row
     wrap — its own window never overlaps this one (confirmed by scanning:
     at every tested width, the max font that still keeps *this* set at
     2-per-row is smaller than the min font contact-section's set needs to
     avoid 3-per-row), so it can't share one rule and gets its own,
     scoped via the body[data-page] main.js sets on page change. */
  .site-nav__links a {
    font-size: clamp(1.3rem, 6.6vw, 2rem);
  }

  body[data-page="contact-section"] .site-nav__links a {
    font-size: clamp(1.8rem, 9.3vw, 2.8rem);
  }
}

/* Below 720px there's no room for the desktop 3-visual-column release grid
   (each card up to 360px wide — see .release__art) or its centered-pair
   explicit placement (built for a 6-track grid wide enough to hold that).
   Drop to a plain 2-up auto-flow grid with much smaller art instead —
   simplest thing that reliably fits 5 items in three rows on a phone
   screen without overflowing the page's fixed 100vh (see .page-section). */
@media (max-width: 720px) {
  #release-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.75rem;
    max-width: 100%;
  }

  #release-list > .release:nth-child(1),
  #release-list > .release:nth-child(2),
  #release-list > .release:nth-child(3),
  #release-list > .release:nth-child(4),
  #release-list > .release:nth-child(5) {
    grid-column: auto;
    grid-row: auto;
  }

  .release__art {
    width: min(130px, 20vh);
  }

  /* Mirrors .page-section .scroll-cue's down-arrow positioning, but
     anchored under the nav at the top instead of the bottom. Needs the
     same .page-section prefix (not just .scroll-cue--up) to actually win
     the bottom: 2rem set by .page-section .scroll-cue — a lower-
     specificity selector loses outright regardless of source order or an
     explicit bottom: auto here, and having both top and bottom set on an
     absolutely positioned element with no explicit height stretches it to
     fill the gap between them instead of sizing to its content (confirmed
     happening: it rendered ~600px tall and sat on top of the page's real
     content, eating its clicks). */
  .page-section .scroll-cue--up {
    display: inline-flex;
    position: absolute;
    top: calc(var(--nav-height) + 0.5rem);
    bottom: auto;
    left: 50%;
    transform: translateX(-50%) rotate(180deg);
  }

  /* Desktop's per-thumbnail dropdown (see .art-dropdown) is replaced by a
     fullscreen modal at this width — see .release-links-modal and
     initArtDropdowns() in main.js for why this can't just be .art-dropdown
     with position: fixed. Stays in the DOM (its markup is the source the
     modal clones its links from) but must never be visible here: it has
     no width-scoped :hover/:focus-within guard, so a stray hover/focus
     event would otherwise still reveal it, tiny and in-place, on top of
     the art — confirmed happening during testing (Playwright's simulated
     hover-then-click revealed it and it ate the click meant for the
     toggle underneath). */
  .art-dropdown {
    display: none;
  }

  /* Less top/bottom breathing room than desktop so 3 rows of art plus the
     dynamic nav height (see syncNavHeight in main.js) reliably fit within
     one 100vh page without vertical overflow. Top padding reserves space
     for .scroll-cue--up (2.25rem tall, positioned nav-height + 0.5rem
     down, plus a small gap before content) rather than the plain
     nav-height + 1rem it used to be — .page-section--centered's vertical
     centering shifts content up when there's more room above it, which,
     once the nav got shorter (see .site-nav__social above), was no
     longer enough clearance and let News's heading creep up into the
     arrow. Bottom padding clears the fixed social-icons bar (see
     .site-nav__social above and syncSocialBarHeight in main.js) — extra
     room beyond the bare minimum (2rem, not 0.5rem) is deliberate, per
     explicit request for more breathing room above the arrow/social bar;
     on .page-section--centered pages this also pulls the centered content
     up along with it, which was the actual goal. */
  .page-section {
    padding: calc(var(--nav-height) + 3.25rem) 1rem calc(var(--social-bar-height) + 2rem);
  }

  /* The down-arrow (see .scroll-cue) would otherwise sit at a fixed
     bottom: 2rem, which is now underneath the social bar. Excludes
     .scroll-cue--up (it also carries the base .scroll-cue class, so a
     plain .page-section .scroll-cue selector matches it too) — that
     variant sets its own top/bottom near the nav instead, and a later
     rule here re-adding a bottom value on top of that reintroduces the
     exact top+bottom-both-set stretch bug already fixed once for it.
     1.25rem gap, not 0.5rem — the arrow's own bounce animation (see
     scroll-cue-bounce) moves it an extra 10px on every cycle, so an 8px
     resting gap wasn't actually enough: confirmed via measurement that it
     dipped ~2px into the social bar at the peak of every single bounce.
     Plus 0.5in on top of that gap — raised a further 1in per explicit
     request, then split back to half that once it read as too high. Pure
     position shift (bottom on an absolutely positioned element), doesn't
     touch any other page content, so no knock-on overflow risk the way
     padding/margin changes here would have. */
  .page-section .scroll-cue:not(.scroll-cue--up) {
    bottom: calc(var(--social-bar-height) + 1.25rem + 0.5in);
  }

  /* The social bar no longer sits at the very bottom of the hero page —
     see .site-nav__social's body[data-page="hero"] override below, which
     moves it up against the photo instead — so its down-arrow (placed via
     flex justify-content: flex-end + padding-bottom, not .scroll-cue's
     absolute positioning) just needs plain clearance for itself again,
     not extra room for the social bar underneath it. The extra +0.5in
     raises it further, split the same way as the rule above. .hero has no
     other content competing for this padding (the arrow is its only flex
     child sitting against this edge), so this is a pure position shift. */
  .hero {
    padding-bottom: calc(2rem + 0.5in);
  }

  /* .scroll-cue defaults to black (see there) for legibility against the
     desktop hero's edge-to-edge cropped photo — on mobile the arrow sits
     below the photo, in the plain black letterbox area (see body::before
     above), where a black arrow is invisible. .hero isn't a .dark-section
     (that's what makes the OTHER pages' arrows white already), so it
     needs its own override here. */
  .hero .scroll-cue {
    color: var(--color-white);
  }

  /* Mobile only: body::before shows the full photo via background-size:
     contain (see there) instead of cropping it, letterboxed above/below
     within the viewport — see syncHeroPhotoRect in main.js for the exact
     math. Static grain, "Me You Us Them", and the social icons are all
     positioned against the photo's own edges here rather than the
     viewport's, per explicit request: no static over the black letterbox
     bars (only the photo itself), the brand name sitting right above the
     photo, and the social icons right below it. */
  body[data-page="hero"] #static-overlay {
    inset: auto;
    top: var(--hero-photo-top, 0);
    left: var(--hero-photo-left, 0);
    width: var(--hero-photo-width, 100%);
    height: var(--hero-photo-height, 100%);
  }

  /* Only ever visible on the hero (see initPageNav's brand.hidden in
     main.js) — safe to position unconditionally against the photo here
     without a body[data-page="hero"] scope, since it's invisible
     everywhere else regardless. translateY(-100% - gap) rather than a
     fixed height-based offset: sits the brand's bottom edge exactly at
     the photo's top edge (plus a small gap) without needing to know its
     own rendered height, which varies with the responsive clamp() it's
     already sized with. white-space: nowrap is required, not cosmetic —
     with left: 50% and no right set, a position: fixed element's
     shrink-to-fit width is computed against the space from left to the
     container's far edge (390 - 195 = 195px here), not the viewport's
     full width, so it was wrapping to two lines despite "Me You Us Them"
     easily fitting on one within the real 390px available (confirmed via
     measurement: it rendered at exactly half the viewport's width). */
  .site-nav__brand {
    position: fixed;
    top: var(--hero-photo-top, 0);
    left: 50%;
    transform: translate(-50%, calc(-100% - 0.75rem));
    white-space: nowrap;
  }

  /* Overrides the general fixed-to-viewport-bottom rule above — needs the
     body[data-page="hero"] prefix to outrank it (one more selector
     segment beats plain .site-nav__social regardless of source order). */
  body[data-page="hero"] .site-nav__social {
    top: calc(var(--hero-photo-top, 0px) + var(--hero-photo-height, 0px) + 0.75rem);
    bottom: auto;
  }

  /* The desktop clamp's floor (3.75rem = 60px) doesn't shrink further at
     phone widths since 7.5vw only overtakes it above ~800px — on News/Live
     that oversized heading alone was a big chunk of the vertical overflow
     pushing content past the fixed 100vh page. */
  .section__heading {
    font-size: clamp(2rem, 9vw, 2.8125rem);
    margin-bottom: 0.25rem;
  }

  /* #posts-list now scrolls internally (see below) instead of every post
     needing to be crushed down far enough to all fit on screen at once —
     comfortable reading spacing again, just a bit tighter than desktop's. */
  /* .page-section--centered's justify-content: center splits leftover
     space between top and bottom padding evenly, but News's top/bottom
     padding aren't actually symmetric around the up/down arrows (nav
     height, social bar height, and the arrows' own offsets don't match
     each other), so plain centering doesn't land the content centered
     between the two arrows specifically — measured ~40px too low.
     display: contents above turns off for this rule specifically so the
     transform below can shift the heading+list as one unit, without
     touching .page-section--centered's own centering math (which is used
     as-is on Live/Contact and works fine there — this is a News-specific
     correction). */
  #news-content {
    display: block;
    width: 100%;
    transform: translateY(-40px);
  }

  /* Same technique as #news-content above — per explicit request, shifts
     the status message+form up as one unit to close the gap under the
     heading and open up more room above the social bar instead. */
  #contact-content {
    display: block;
    width: 100%;
    transform: translateY(-28px);
  }

  .post {
    padding: 1.25rem 0;
  }

  .post__header {
    margin-bottom: 0.75rem;
  }

  .post__body p {
    margin: 0 0 0.75em;
  }

  /* Shows only the most recent post by default, with the rest reachable
     by scrolling within this box, per explicit request — rather than
     every post being crushed down far enough to all fit within the fixed
     100vh page at once (see initPageNav's touchstart/touchmove/touchend
     in main.js for how a swipe starting inside this element scrolls it
     natively instead of changing pages, which is what makes this actually
     usable — everywhere else on the site, all scrolling is intercepted
     for page-to-page navigation). */
  #posts-list {
    max-height: 37vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }

  /* "Contact/Join Email List" is long enough that even the already-reduced
     desktop mobile size (see #contact-section .section__heading above)
     doesn't reliably wrap within a ~350px-wide phone screen — shrink it
     further and force wrapping regardless of where natural break points
     fall, so it can't push the page wider than the viewport. */
  #contact-section .section__heading {
    font-size: clamp(1.5rem, 7vw, 2rem);
    overflow-wrap: break-word;
    word-break: break-word;
  }
}
