/* ============================================================
   Pierre's Film Club — public.css
   Design system: DESIGN.md v1.0 — "Midnight repertory program"
   Dark house, gold marquee ink, Pierre's burgundy thread.
   ============================================================ */

/* --- Google Fonts (Anton + Libre Franklin) loaded in base.html --- */

/* --- Custom properties / tokens --- */
:root {
  --black:       #171513;   /* house black — the canvas */
  --black-raise: #221e1a;   /* raised surfaces (panels, cards, inputs) */
  --line:        #3a342c;   /* hairline rules and borders on dark */
  --paper:       #f0e9d8;   /* primary ink — warm off-white */
  --muted:       #9b9080;   /* secondary ink — faded program text */
  --gold:        #c8a13a;   /* marquee gold — the one action color */
  --gold-dark:   #b08f35;   /* gold hover/press */
  --burgundy:    #700000;   /* Pierre's thread — ornament only, never text */
  --red:         #d4604a;   /* errors only — readable brick on dark */

  --ease-out:      cubic-bezier(0.23, 1, 0.32, 1);
  --ease-out-soft: cubic-bezier(0.16, 1, 0.3,  1);
  --ease-out-back: cubic-bezier(0.34, 1.56, 0.64, 1);  /* slight overshoot — the rating pop */

  --col-max: 680px;
  /* Side padding folds in the iOS safe-area inset so content never sits under
     a notch / rounded corner; viewport-fit=cover (base.html) makes this live. */
  --side-pad: calc(22px + env(safe-area-inset-left));
  --side-pad-r: calc(22px + env(safe-area-inset-right));
  --section-gap-mob: 3rem;
  --section-gap-desk: 5.25rem;

  /* A real vertical-rhythm scale — used to vary spacing for cadence (not one
     uniform gap everywhere). */
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-6: 1.5rem;
  --space-8: 2rem;

  color-scheme: dark;
}

/* --- Base reset --- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html { font-size: 16px; -webkit-text-size-adjust: 100%; }

body {
  background: var(--black);
  color: var(--paper);
  font-family: 'Libre Franklin', system-ui, sans-serif;
  font-size: 1rem;
  line-height: 1.55;
  min-height: 100dvh;
  position: relative;
  overflow-x: hidden;
}

::selection { background: var(--gold); color: var(--black); }

/* --- Film grain overlay (silver grain on dark — screen blend) --- */
.grain-overlay {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 1000;
  opacity: 0.05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23noise)'/%3E%3C/svg%3E");
  background-repeat: repeat;
  background-size: 200px 200px;
  mix-blend-mode: screen;
}

/* --- Typography helpers --- */
.font-display { font-family: 'Anton', sans-serif; letter-spacing: 0.02em; }

/* Masthead — "PIERRE'S" / "FILM CLUB", two lines.
   The one display moment per page: big, set tight, hard-left. */
.masthead-xl {
  font-family: 'Anton', sans-serif;
  font-size: clamp(58px, 18vw, 88px);
  line-height: 0.92;
  letter-spacing: 0.005em;
  color: var(--paper);
  text-transform: uppercase;
}
.masthead-xl__line { display: block; }
/* "PIERRE'S" reads a hair smaller and lighter-weight in the eye than the
   payload line; nudge "FILM CLUB" to carry the emphasis. */
.masthead-xl__line:first-child {
  font-size: 0.82em;
  color: var(--paper);
  margin-left: 0.02em;
}
@media (min-width: 640px) {
  .masthead-xl { font-size: clamp(88px, 12vw, 120px); }
}

/* Display sizes */
.display-lg {
  font-family: 'Anton', sans-serif;
  font-size: clamp(32px, 9vw, 56px);
  line-height: 1.0;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.display-md {
  font-family: 'Anton', sans-serif;
  font-size: clamp(22px, 6.5vw, 32px);
  line-height: 1.05;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* Body sizes */
.body-lg  { font-size: 1.125rem; line-height: 1.6; }
.body-md  { font-size: 1rem;     line-height: 1.55; }

/* Label — small caps eyebrow */
.label-sm {
  font-family: 'Libre Franklin', sans-serif;
  font-weight: 600;
  font-size: 0.75rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  line-height: 1.2;
}

/* Color utilities (legacy names kept — they now map to the dark palette) */
.text-ink    { color: var(--paper); }
.text-cream  { color: var(--paper); }
.text-orange { color: var(--gold); }
.text-gold   { color: var(--gold); }
.text-muted  { color: var(--muted); }
.text-red    { color: var(--red); }

/* --- Layout --- */
.page-wrap {
  max-width: var(--col-max);
  margin: 0 auto;
  padding-left: var(--side-pad);
  padding-right: var(--side-pad-r);
}

/* --- Hairline rule --- */
.hairline {
  border: none;
  border-top: 1px solid var(--line);
}
.hairline--muted { border-top-color: var(--line); }

/* --- Top bar — the burgundy thread back to Pierre's --- */
.top-bar {
  /* No outer vertical padding: the auth links carry a 44px min-height touch
     target and set the bar height (~44px, close to the original ~40px). */
  padding: 0;
  border-bottom: 1px solid var(--burgundy);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.top-bar__site  { color: var(--muted); }
.top-bar__auth  { display: flex; align-items: center; gap: 18px; }
.top-bar__user  { color: var(--muted); display: flex; align-items: center; gap: 10px; }
.top-bar__login {
  color: var(--muted);
  text-decoration: underline;
  text-underline-offset: 3px;
  font-size: 0.75rem;
  font-family: 'Libre Franklin', sans-serif;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 0.2rem;
}

.top-bar form,
.top-bar__signout { display: inline; }
.top-bar__signout-btn {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--muted);
  font-family: 'Libre Franklin', sans-serif;
  font-weight: 600;
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: underline;
  text-underline-offset: 3px;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 0.2rem;
}
@media (hover: hover) and (pointer: fine) {
  .top-bar__signout-btn:hover { color: var(--gold); }
  .top-bar__login:hover { color: var(--gold); }
}

/* --- Masthead section --- */
.masthead {
  padding-top: 2.25rem;
}
.masthead__tagline {
  margin-top: 1rem;
  max-width: 34ch;
  color: var(--muted);
  letter-spacing: 0.005em;
}
@media (min-width: 640px) {
  .masthead { padding-top: 3.5rem; }
}

/* --- Flash message --- */
.flash {
  margin-top: 1rem;
  padding: 0.6rem 0.75rem;
  background: var(--black-raise);
  border: 1px solid var(--line);
  font-size: 0.9375rem;
  color: var(--muted);
}

/* --- Poster panel (Veiled Marquee / Movie Reveal) --- */
.poster-panel {
  position: relative;
  margin-top: 1.75rem;
  background: var(--black-raise);
  border: 1px solid var(--line);
  overflow: hidden;
}
@media (min-width: 640px) {
  .poster-panel { margin-top: 2.5rem; }
}
/* A printed-plate inset frame — a fine paper-toned hairline held off the edge,
   the way a repertory program plate is ruled. Flat (no shadow), sits above the
   image but below the text overlay. */
.poster-panel::after {
  content: '';
  position: absolute;
  inset: 9px;
  border: 1px solid rgba(240, 233, 216, 0.16);
  z-index: 3;
  pointer-events: none;
}

/* Full-bleed image inside the panel (marquee art or movie still) */
.poster-panel__placeholder,
.poster-panel__movie-img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  object-position: center;
  position: relative;
  z-index: 2;
}

/* Text content pinned to the bottom of the panel, over the gradient */
.poster-panel__content,
.poster-panel__content--overlay {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 4;
  padding: 1rem 1.25rem 1.25rem;
}

/* Readability gradient — fades to black so the paper-white text pops */
.poster-panel__content--gradient::before,
.poster-panel__content--overlay::before {
  content: '';
  position: absolute;
  inset: -110px 0 0 0;
  background: linear-gradient(
    to bottom,
    transparent,
    rgba(10, 8, 7, 0.55) 45%,
    rgba(10, 8, 7, 0.96)
  );
  pointer-events: none;
  z-index: -1;
}

/* Eyebrow on the image is gold; headline is paper */
.poster-panel__eyebrow { color: var(--gold); }
.poster-panel__title   { color: var(--paper); margin-top: 0.25rem; }

/* Mystery state — a redaction "stamp" so the hidden title can't be mistaken
   for a real title. Paper bar, house-black ink, like a censored document. */
.poster-panel__withheld { margin-top: 0.45rem; line-height: 0; }
/* The redaction "stamp" — a censor bar struck over the curtain, set in
   house-black Anton on paper, tilted a hair so it reads hand-placed (the
   same ink-stamp character as the vote lock-in). */
.poster-panel__stamp {
  display: inline-block;
  background: var(--paper);
  color: var(--black);
  font-family: 'Anton', sans-serif;
  font-size: clamp(24px, 6.8vw, 34px);
  line-height: 1;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0.2em 0.42em 0.13em;
  transform: rotate(-1.6deg);
  transform-origin: left center;
  /* Hard-edged stamp keyline (0 blur — a ruled impression, not a shadow):
     black hairline, then a paper margin, then the house-black outline. */
  box-shadow: 0 0 0 2px var(--black), 0 0 0 5px var(--paper), 0 0 0 6px var(--black);
}
/* The hook, now clearly a caption beneath the stamp — not the title slot. */
.poster-panel__mystery-note {
  margin-top: 0.55rem;
  font-size: 1.0625rem;
  line-height: 1.3;
  color: var(--paper);
}

/* Runtime · rating line — public detail, shown even when the title isn't */
.poster-meta {
  margin-top: 0.5rem;
  font-family: 'Libre Franklin', sans-serif;
  font-weight: 600;
  font-size: 0.8125rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(240, 233, 216, 0.82);
}

/* On phones the overlay was crowding the image — lighten its footprint:
   smaller meta line, a touch smaller title, tighter bottom padding. */
@media (max-width: 480px) {
  .poster-panel__title { font-size: clamp(28px, 7.8vw, 40px); }
  .poster-meta {
    margin-top: 0.35rem;
    font-size: 0.6875rem;
    letter-spacing: 0.1em;
  }
  .poster-panel__content,
  .poster-panel__content--overlay { padding: 0.85rem 1rem 0.95rem; }
}

/* Unverified note below the panel */
.unverified-note {
  margin-top: 0.75rem;
  color: var(--muted);
}

/* Panel min-height safety for image states */
.poster-panel--no-img {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

/* ============================================================
   Vote week — two-up members vote (replaces the poster panel while
   voting is open). Side-by-side at all widths (Phill's call). Counts
   stay hidden until the member votes; the vote locks once cast.
   ============================================================ */
.vote-block { margin-top: var(--section-gap-mob); }
@media (min-width: 640px) { .vote-block { margin-top: var(--section-gap-desk); } }

.vote-block__eyebrow { color: var(--gold); margin-bottom: 0.4rem; }
.vote-block__title   { color: var(--paper); }
.vote-block__intro   { margin-top: 0.5rem; max-width: 44ch; }

.vote-grid {
  position: relative;               /* anchors the centered VS medallion */
  margin-top: 1.5rem;
  display: grid;
  grid-template-columns: 1fr;       /* mobile: stacked, full-width tap targets */
  gap: 1.1rem;
  align-items: start;
}
@media (min-width: 560px) {
  /* roomier screens: the classic two-up, side-by-side */
  .vote-grid { grid-template-columns: 1fr 1fr; gap: 1.25rem; }
}

/* The "VS" medallion sits dead-center of the grid — between the two cards
   whether they're stacked (mobile) or side-by-side (desktop). Absolutely
   positioned, so it's out of grid flow and never takes a cell. */
.vote-vs {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 5;
  pointer-events: none;
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--black);
  border: 1px solid var(--line);
  border-radius: 50%;               /* a stamp/medallion — the sanctioned circle use (§3) */
  font-family: 'Anton', sans-serif;
  font-size: 1.05rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--gold);
}
/* While a confirm is open, lift the proposed card above the VS so its overlay
   reads cleanly; the medallion tucks behind. */
.vote-card.is-pending { z-index: 6; }

/* The card is the box; it anchors the in-card confirm overlay. */
.vote-card {
  position: relative;
  background: var(--black-raise);
  border: 1px solid var(--line);
  overflow: hidden;
  transition: border-color 140ms var(--ease-out), opacity 200ms var(--ease-out);
}
/* The poster + title is the tap target — a reset <button> (and the no-JS vote). */
.vote-card__tap {
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: none;
  border-radius: 0;
  padding: 0;
  margin: 0;
  width: 100%;
  text-align: left;
  font-family: inherit;
  color: var(--paper);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  -webkit-tap-highlight-color: transparent;
}
.vote-card__tap:not(:disabled):active { transform: scale(0.985); }
@media (hover: hover) and (pointer: fine) {
  .vote-card:not(.is-voted):hover { border-color: var(--gold); }
}

.vote-card__poster { position: relative; display: block; }
.vote-card__poster img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  object-position: center;
}

/* "Your Pick" flag — only on the chosen card, only after voting. */
.vote-card__pick {
  position: absolute;
  top: 0;
  left: 0;
  background: var(--gold);
  color: var(--black);
  font-family: 'Libre Franklin', sans-serif;
  font-weight: 600;
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 0.32em 0.6em;
  display: none;
}
.vote-card.is-mine .vote-card__pick { display: inline-block; }

/* Title pinned over the poster (like the reveal panel) — a readability
   gradient + paper Anton. Both cards stay the same height no matter the title. */
.vote-card__overlay {
  position: absolute;
  inset: auto 0 0 0;
  z-index: 2;
  padding: 1.5rem 0.8rem 0.7rem;
  background: linear-gradient(to bottom, transparent, rgba(10, 8, 7, 0.55) 45%, rgba(10, 8, 7, 0.93));
  pointer-events: none;
}
.vote-card__name {
  font-family: 'Anton', sans-serif;
  font-size: clamp(18px, 5vw, 24px);
  line-height: 1.02;
  letter-spacing: 0.015em;
  text-transform: uppercase;
  color: var(--paper);
}

/* Leaning bar — the only thing members see (no counts). Shown once the member
   has voted AND enough of the club has voted (the .results-on gate). Pinned to
   the poster's bottom edge so revealing it never changes the card's height. */
.vote-card__result { position: absolute; inset: auto 0 0 0; z-index: 3; display: none; }
.vote-card.is-voted.results-on .vote-card__result { display: block; }

.vote-card__bar { display: block; height: 6px; background: rgba(0, 0, 0, 0.45); overflow: hidden; }
.vote-card__bar-fill {
  display: block;
  height: 100%;
  width: 0;
  background: var(--gold);
  transition: width 440ms var(--ease-out);
}

/* Once voted: the pick is bordered gold; the other dims slightly. */
.vote-card.is-voted.is-mine { border-color: var(--gold); }
.vote-card.is-voted:not(.is-mine) { opacity: 0.78; }

/* While the confirm step is open: the proposed card is bordered gold, the other
   dims back so the choice reads clearly before it locks. */
.vote-card.is-pending { border-color: var(--gold); }
.vote-card.is-faded   { opacity: 0.5; }

/* The lock-in — a rare, deliberate moment, so it's allowed to feel good. The
   chosen card gives a quick spring pop and the "Your Pick" flag stamps in like
   an ink stamp hitting paper (on-brand for a printed program). Transform/opacity
   only; the gold border is already set by .is-mine. */
.vote-card.lock-in { animation: voteLockPop 280ms var(--ease-out-back) both; }
@keyframes voteLockPop {
  0%   { transform: scale(0.975); }
  55%  { transform: scale(1.02); }
  100% { transform: scale(1); }
}
.vote-card.lock-in .vote-card__pick {
  transform-origin: left top;
  animation: votePickStamp 320ms var(--ease-out-back) both;
}
@keyframes votePickStamp {
  0%   { opacity: 0; transform: scale(0.7) rotate(-8deg); }
  60%  { opacity: 1; transform: scale(1.08) rotate(2deg); }
  100% { opacity: 1; transform: scale(1) rotate(0); }
}

/* The confirm step lives INSIDE the tapped card as an overlay (Phill's call) —
   a near-black scrim over the poster with the film named and the two actions. */
.vote-card__confirm {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  padding: 0.6rem 0.8rem;
  text-align: center;
  background: rgba(18, 16, 14, 0.965);
  overflow: auto;                   /* backstop on very short cards (≤320px) */
}
.vote-card__confirm[hidden] { display: none; }
.vote-card__confirm-q {
  font-family: 'Libre Franklin', sans-serif;
  font-weight: 600;
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  line-height: 1.2;
}
.vote-card__confirm-film {
  font-family: 'Anton', sans-serif;
  font-size: clamp(15px, 4.4vw, 20px);
  line-height: 1.02;
  letter-spacing: 0.015em;
  text-transform: uppercase;
  color: var(--gold);
}
.vote-card__confirm-lock {
  margin-top: 0.2rem;
  width: 100%;
  min-height: 48px;                 /* a comfortable mobile tap target */
  background: var(--gold);
  color: var(--black);
  font-family: 'Anton', sans-serif;
  font-size: 1.0625rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  border: none;
  border-radius: 0;
  padding: 0.6rem 0.6rem;
  line-height: 1;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color 140ms var(--ease-out), transform 140ms var(--ease-out);
}
.vote-card__confirm-lock:active { background: var(--gold-dark); transform: scale(0.98); }
/* Cancel is a real, hairline-bordered target — not tiny tap-and-miss text. */
.vote-card__confirm-cancel {
  width: 100%;
  min-height: 38px;
  background: none;
  border: 1px solid var(--line);
  border-radius: 0;
  cursor: pointer;
  color: var(--muted);
  font-family: 'Libre Franklin', sans-serif;
  font-weight: 600;
  font-size: 0.8125rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.4rem 0.6rem;
  -webkit-tap-highlight-color: transparent;
  transition: color 140ms var(--ease-out), border-color 140ms var(--ease-out);
}
.vote-card__confirm-cancel:active { border-color: var(--muted); }
@media (hover: hover) and (pointer: fine) {
  .vote-card__confirm-lock:hover { background: var(--gold-dark); }
  .vote-card__confirm-cancel:hover { color: var(--paper); }
}
.vote-card__confirm--in { animation: cardConfirmIn 180ms var(--ease-out) both; }
@keyframes cardConfirmIn {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: scale(1); }
}


/* AUDIENCE CHOICE stamp on the winner reveal — gold-outlined tag (gold as ink,
   not a second filled button, keeping the one-gold-fill-per-block discipline). */
.audience-badge {
  display: inline-block;
  border: 1px solid var(--gold);
  color: var(--gold);
  font-family: 'Libre Franklin', sans-serif;
  font-weight: 600;
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 0.18em 0.5em;
  vertical-align: middle;
  margin-left: 0.35em;
}

@media (prefers-reduced-motion: reduce) {
  .vote-card__bar-fill { transition: none; }
  .vote-card__tap:not(:disabled):active { transform: none; }
  .vote-card__confirm-lock:active { transform: none; }
  /* Keep the state change (gold border, flag, dim) but drop the movement. */
  .vote-card.lock-in,
  .vote-card.lock-in .vote-card__pick,
  .vote-card__confirm--in { animation: none; }
}

/* --- Buy section — the climax of the page --- */
.buy-section {
  margin-top: 2.5rem;
}
@media (min-width: 640px) {
  .buy-section { margin-top: 3.25rem; }
}
/* A hairline draws the line between "the program" above and "the ask" below. */
.buy-section__rule { margin-bottom: 1.75rem; }
.buy-section__tba { margin-top: 0.5rem; }

.screening-date-display {
  margin-top: 0.4rem;
  display: flex;
  align-items: baseline;
  gap: 0;
}
.screening-date-display .date-text {
  font-family: 'Anton', sans-serif;
  font-size: clamp(46px, 14vw, 66px);
  line-height: 0.92;
  color: var(--paper);
  letter-spacing: 0.02em;
}
.screening-date-display .date-text .date-number { color: var(--gold); }

.showtime-line {
  margin-top: 0.6rem;
  color: var(--muted);
}

.buy-cta { margin-top: 1.5rem; }

/* --- Buttons --- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  font-family: 'Anton', sans-serif;
  font-size: 1.1875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border: none;
  border-radius: 0;
  cursor: pointer;
  padding: 0 1.35rem;
  height: 58px;
  transition: background-color 140ms var(--ease-out), color 140ms var(--ease-out),
              transform 140ms var(--ease-out);
  text-decoration: none;
  line-height: 1;
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
}
.btn:active { transform: scale(0.985); }

/* Primary — marquee gold with house-black ink */
.btn--primary {
  background: var(--gold);
  color: var(--black);
  width: 100%;
}
.btn--primary:active { background: var(--gold-dark); }

@media (hover: hover) and (pointer: fine) {
  .btn--primary:hover { background: var(--gold-dark); }
  .btn--primary:hover .btn__arrow { transform: translateX(4px); }
}

/* Secondary — quiet raised surface with a hairline */
.btn--secondary {
  background: var(--black-raise);
  color: var(--paper);
  border: 1px solid var(--line);
}
.btn--secondary:active { background: #2b2620; }

@media (hover: hover) and (pointer: fine) {
  .btn--secondary:hover { background: #2b2620; }
}

.btn__arrow {
  margin-left: auto;
  padding-left: 1rem;
  font-size: 1.15em;
  transition: transform 200ms var(--ease-out);
}

.btn-subtext {
  margin-top: 0.625rem;
  color: var(--muted);
  text-align: center;
}

/* Inline loading state — public.js swaps the button label for "···" on submit
   (DESIGN §5b: a three-dot loader replacing the label, no spinner). Centered in
   the button with a calm pulse; reduced-motion safe. */
.btn-loader {
  display: inline-block;
  width: 100%;
  text-align: center;
  letter-spacing: 0.3em;
  font-size: 1.35em;
  line-height: 1;
  animation: btnLoaderPulse 1000ms var(--ease-out) infinite;
}
@keyframes btnLoaderPulse {
  0%, 100% { opacity: 0.4; }
  50%      { opacity: 1; }
}

/* --- Keyboard focus ring (accessibility, WCAG 2.4.7) ---
   Shown only for keyboard / assistive-tech users (not on mouse click). Gold ink
   sits 2px OUTSIDE the element, so it's visible on the dark canvas even around a
   gold-filled button. Inputs keep their existing gold-border focus cue. */
:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .btn-loader { animation: none; opacity: 0.85; }
}

/* --- About section --- */
.about-section {
  margin-top: var(--section-gap-mob);
}
@media (min-width: 640px) {
  .about-section { margin-top: var(--section-gap-desk); }
}

/* ============================================================
   The Archive — past films + private member ratings (members only)
   ============================================================ */
.archive {
  margin-top: var(--section-gap-mob);
  padding-top: var(--section-gap-mob);
  border-top: 1px solid var(--burgundy);   /* the members-only threshold */
}
@media (min-width: 640px) {
  .archive { margin-top: var(--section-gap-desk); padding-top: var(--section-gap-desk); }
}
.archive__eyebrow { color: var(--gold); margin-bottom: 0.5rem; }
.archive__title   { color: var(--paper); }
.archive__intro   { margin-top: 0.5rem; max-width: 46ch; }

.archive__grid {
  margin-top: 1.75rem;
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
}
@media (min-width: 520px) {
  .archive__grid { grid-template-columns: 1fr 1fr; gap: 1.5rem 1.25rem; }
}

.archive-card {
  background: var(--black-raise);
  border: 1px solid var(--line);
}

/* Past-film poster: image + title only — no runtime, no rating (gated meta) */
.archive-card__poster { position: relative; overflow: hidden; }
.archive-card__poster img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  object-position: center;
}
.archive-card__overlay {
  position: absolute;
  inset: auto 0 0 0;
  padding: 0.7rem 0.85rem 0.8rem;
  background: linear-gradient(to bottom, transparent, rgba(10, 8, 7, 0.92));
}
.archive-card__date { color: var(--gold); display: block; margin-bottom: 0.15rem; font-size: 0.6875rem; }
.archive-card__name {
  font-family: 'Anton', sans-serif;
  font-size: clamp(18px, 5.5vw, 24px);
  line-height: 1.02;
  letter-spacing: 0.015em;
  color: var(--paper);
  text-transform: uppercase;
}

/* --- The rating control: a fluid 0–100 slider (v1.8) ---
   Drag left = liked least, right = liked most. Continuous — no preset steps.
   A film-scrubber playhead in marquee gold; the fill grows as you drag right.
   The position is server-rendered from --rate-pct (set on .rate), so the saved
   value shows before JS loads and on the no-JS path. */
.rate {
  padding: 0.85rem 0.9rem 0.95rem;
  --rate-track: var(--line);
  --rate-color: var(--gold);    /* overridden inline (server) + by JS with the sentiment colour */
}
.rate__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.7rem;
}
.rate__prompt { color: var(--muted); }
.rate__score { white-space: nowrap; line-height: 1; }
/* The score reads X.X out of ten, the number in the sentiment colour. */
.rate__num {
  font-family: 'Anton', sans-serif;
  font-size: 1.5rem;
  letter-spacing: 0.01em;
  color: var(--rate-color);
  font-style: normal;
  transition: color 200ms ease;
}
.rate__num-max {
  font-family: 'Libre Franklin', sans-serif;
  font-weight: 600;
  font-size: 0.8125rem;
  color: var(--muted);
  margin-left: 0.05em;
}
.rate.is-unset .rate__num { color: var(--muted); }

.rate__control {
  display: flex;
  align-items: center;
  gap: 0.7rem;
}
.rate__end {
  flex-shrink: 0;
  font-size: 1.15rem;
  line-height: 1;
  /* The nearer emoji swells, the far one shrinks — driven entirely by the
     slider's --rate-frac (0 at 🤮, 1 at 😍), so it tracks the drag live with
     no JS. Both sit at 1.0 when --rate-frac is 0.5 (centre / unrated). */
  transform: scale(var(--end-scale, 1));
  transform-origin: center;
  transition: transform 150ms var(--ease-out);
}
.rate__end--least { --end-scale: calc(1 + 0.7 * (0.5 - var(--rate-frac, 0.5))); }
.rate__end--most  { --end-scale: calc(1 + 0.7 * (var(--rate-frac, 0.5) - 0.5)); }

.rate__track {
  position: relative;
  flex: 1;
  height: 44px;                 /* a generous touch zone, even though the rail is thin */
  display: flex;
  align-items: center;
}
/* The rail. */
.rate__track::before {
  content: '';
  position: absolute;
  left: 0; right: 0;
  top: 50%;
  height: 3px;
  transform: translateY(-50%);
  background: var(--rate-track);
}
/* Fill grows via scaleX (GPU — transform only, never width). */
.rate__fill {
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  height: 3px;
  background: var(--rate-color);
  transform: translateY(-50%) scaleX(var(--rate-frac, 0.5));
  transform-origin: left center;
}
.rate__knob {
  position: absolute;
  top: 50%;
  left: var(--rate-pct, 50%);   /* set instantly while dragging — no left transition */
  width: 9px;
  height: 24px;                 /* a rectangular scrubber playhead — never a circle (§3) */
  background: var(--rate-color);
  transform: translate(-50%, -50%);
  z-index: 2;
}
/* Not-yet-rated: muted rail/knob, hollow playhead, no fill. */
.rate.is-unset { --rate-color: var(--muted); }
.rate.is-unset .rate__fill { display: none; }
.rate.is-unset .rate__knob { background: none; border: 1px solid var(--muted); }

/* Settle smoothly on load/save (transform + colour only — Emil: GPU props,
   ease-out for the move, ease for colour). During an active drag JS adds
   .is-dragging to kill the move-lag so the playhead tracks the finger. */
.rate__fill {
  transition: transform 160ms var(--ease-out), background-color 200ms ease;
}
.rate__knob {
  transition: transform 160ms var(--ease-out), background-color 200ms ease;
}
.rate.is-dragging .rate__fill { transition: background-color 120ms ease; }
.rate.is-dragging .rate__knob { transition: background-color 120ms ease; transform: translate(-50%, -50%) scale(1.22); }

/* The satisfying confirm on release: the playhead springs (a rare, deliberate
   act — delight earns its place). Subtle bounce per Emil; never from scale(0). */
.rate.is-saved .rate__knob { animation: rateKnobPop 340ms var(--ease-out-back); }
@keyframes rateKnobPop {
  0%   { transform: translate(-50%, -50%) scale(1.28); }
  55%  { transform: translate(-50%, -50%) scale(0.92); }
  100% { transform: translate(-50%, -50%) scale(1); }
}

/* The real control: a native range. No-JS → visible (gold accent) + the Save
   button. With JS → transparent overlay driving the custom visuals above. */
.rate__input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
  accent-color: var(--gold);
  background: none;
}
.rate__save {
  margin-top: 0.85rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 1.1rem;
  background: var(--gold);
  color: var(--black);
  border: none;
  border-radius: 0;
  font-family: 'Anton', sans-serif;
  font-size: 1rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  cursor: pointer;
}

/* JS present: hide the native chrome + Save, reveal the custom slider. */
.js .rate__input {
  opacity: 0;
  -webkit-appearance: none;
  appearance: none;
  z-index: 3;
}
.js .rate__input::-webkit-slider-thumb { -webkit-appearance: none; width: 36px; height: 44px; }
.js .rate__input::-moz-range-thumb { width: 36px; height: 44px; border: none; background: none; }
.js .rate__save { display: none; }
/* Without JS, the custom rail/fill/knob are redundant next to the native range. */
html:not(.js) .rate__track::before,
html:not(.js) .rate__fill,
html:not(.js) .rate__knob { display: none; }
html:not(.js) .rate__input { position: static; height: 36px; }

@media (prefers-reduced-motion: reduce) {
  .rate__fill, .rate__knob { transition: none; }
  .rate.is-dragging .rate__knob { transform: translate(-50%, -50%); }
  .rate.is-saved .rate__knob { animation: none; }
  /* Keep the size emphasis (it carries meaning), drop the easing. */
  .rate__end { transition: none; }
}

/* --- The club's score (v0.23) ---
   The member-facing average of everyone's private ratings, on a hairline row
   under the member's own slider. Unveils the morning after the screening once
   enough members have rated — the average alone, never counts or names. Inside
   the pre-reveal window the row teases that the score lands in the morning. */
.club-score {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin: 0 0.9rem;
  padding: 0.7rem 0 0.85rem;
  border-top: 1px solid var(--line);
}
.club-score__label { color: var(--muted); }
.club-score__num { white-space: nowrap; line-height: 1; }
.club-score__num b {
  font-family: 'Anton', sans-serif;
  font-weight: normal;
  font-style: normal;
  font-size: 1.5rem;
  letter-spacing: 0.01em;
  color: var(--club-color, var(--gold));
}
.club-score__max {
  font-family: 'Libre Franklin', sans-serif;
  font-weight: 600;
  font-size: 0.8125rem;
  color: var(--muted);
  margin-left: 0.05em;
}

/* ============================================================
   The Peek — locked teaser shown to non-members
   ============================================================ */
.peek {
  margin-top: var(--section-gap-mob);
  padding-top: var(--section-gap-mob);
  border-top: 1px solid var(--burgundy);
}
@media (min-width: 640px) {
  .peek { margin-top: var(--section-gap-desk); padding-top: var(--section-gap-desk); }
}
.peek__frame {
  position: relative;
  border: 1px solid var(--line);
  background: var(--black-raise);
  overflow: hidden;
  min-height: 240px;
}
.peek__tiles {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 10px;
  padding: 14px;
}
.peek__tile {
  background-image: url("/static/img/veiled-marquee.jpg");
  background-size: cover;
  background-position: center;
  filter: blur(7px) grayscale(0.3) brightness(0.5);
  transform: scale(1.1);              /* hide blurred edges */
}
.peek__veil {
  position: relative;
  z-index: 2;
  min-height: 240px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 2.25rem 1.5rem;
  background: radial-gradient(ellipse at center, rgba(23,21,19,0.78), rgba(23,21,19,0.94));
}
.peek__lock { width: 30px; height: 30px; color: var(--gold); margin-bottom: 0.9rem; }
.peek__headline { color: var(--paper); }
.peek__copy { margin-top: 0.6rem; max-width: 38ch; }

/* --- Footer --- */
.site-footer {
  margin-top: var(--section-gap-mob);
  padding-bottom: calc(2.25rem + env(safe-area-inset-bottom));
}
@media (min-width: 640px) {
  .site-footer { margin-top: var(--section-gap-desk); }
}

.site-footer__inner {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  flex-wrap: wrap;
  padding-top: 1.25rem;
}
.site-footer__mark {
  width: auto;
  height: 32px;          /* tall "P" glyph (232×400) — preserve its aspect */
  display: block;
  flex-shrink: 0;
  opacity: 0.92;
}
.site-footer__sign {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  line-height: 1.2;
}
.site-footer__sign .label-sm { color: var(--paper); }
.site-footer__place {
  color: var(--muted);
  font-size: 0.8125rem;
  letter-spacing: 0.02em;
}
.site-footer__links {
  display: flex;
  gap: 1.5rem;
  flex-wrap: wrap;
  margin-left: auto;
}
.site-footer__links a {
  color: var(--muted);
  text-decoration: none;
  font-size: 0.875rem;
  border-bottom: 1px solid transparent;
  padding-bottom: 1px;
}
@media (hover: hover) and (pointer: fine) {
  .site-footer__links a:hover {
    color: var(--paper);
    border-bottom-color: var(--gold);
  }
}

/* --- Choice screen (buy_choice.html) --- */
.back-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--muted);
  font-family: 'Libre Franklin', sans-serif;
  font-weight: 600;
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  margin-top: 1rem;
  margin-bottom: 0;
}
@media (hover: hover) and (pointer: fine) {
  .back-link:hover { color: var(--paper); }
}

/* Quantity stepper */
.qty-stepper {
  display: flex;
  align-items: center;
  gap: 0;
  margin-bottom: 1rem;
  border: 1px solid var(--line);
  width: fit-content;
}
.qty-stepper__btn {
  width: 44px;
  height: 44px;
  background: var(--black);
  border: none;
  cursor: pointer;
  font-family: 'Anton', sans-serif;
  font-size: 1.375rem;
  color: var(--paper);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 140ms var(--ease-out);
}
.qty-stepper__btn:active { background: var(--black-raise); transform: scale(0.98); }
@media (hover: hover) and (pointer: fine) {
  .qty-stepper__btn:hover { background: var(--black-raise); }
}
.qty-stepper__btn:disabled { color: var(--line); cursor: default; }
.qty-stepper__count {
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: 'Anton', sans-serif;
  font-size: 1.375rem;
  color: var(--gold);
  border-left: 1px solid var(--line);
  border-right: 1px solid var(--line);
  user-select: none;
}

/* --- Account / login screens --- */
.page-section {
  padding-top: 1.25rem;
  padding-bottom: 3rem;
}

.page-eyebrow {
  color: var(--muted);
  margin-bottom: 0.125rem;
}

.tabs {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--line);
  margin: 1.5rem 0 1.5rem;
}
.tab-btn {
  background: none;
  border: none;
  cursor: pointer;
  font-family: 'Libre Franklin', sans-serif;
  font-weight: 600;
  font-size: 0.8125rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 0.5rem 1rem 0.625rem;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color 140ms, border-color 140ms;
}
.tab-btn.is-active {
  color: var(--paper);
  border-bottom-color: var(--gold);
}
@media (hover: hover) and (pointer: fine) {
  .tab-btn:hover { color: var(--paper); }
}

.tab-panel { display: none; }
.tab-panel.is-active { display: block; }

/* Forms */
.form-group { margin-bottom: 1.125rem; }
.form-label {
  display: block;
  font-family: 'Libre Franklin', sans-serif;
  font-weight: 600;
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.4rem;
}
.form-input {
  width: 100%;
  background: var(--black-raise);
  border: 1px solid var(--line);
  border-radius: 0;
  padding: 0.625rem 0.75rem;
  font-family: 'Libre Franklin', sans-serif;
  font-size: 1rem;      /* 16px prevents iOS auto-zoom */
  color: var(--paper);
  line-height: 1.4;
  appearance: none;
  -webkit-appearance: none;
  transition: border-color 140ms;
}
.form-input:focus {
  outline: none;
  border-color: var(--gold);
}
.form-helper {
  margin-top: 0.35rem;
  color: var(--muted);
  font-size: 0.875rem;
}

/* Honeypot — visually hidden, out of tab flow */
.hp-field {
  position: absolute;
  left: -9999px;
  top: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
  opacity: 0;
}

/* Error states */
.form-error {
  margin-bottom: 1.25rem;
  padding: 0.625rem 0.75rem;
  border: 1px solid var(--red);
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  color: var(--red);
  font-size: 0.9375rem;
}
.form-error--lockout {
  border-color: var(--line);
  color: var(--muted);
}
.form-error__icon { flex-shrink: 0; margin-top: 0.1rem; }

/* Rotating-QR join banners (§4b): gold affirmation when the lobby code is live,
   gentle muted fallback when it's expired. */
.join-note {
  margin-bottom: 1.25rem;
  padding: 0.625rem 0.75rem;
  border: 1px solid var(--gold);
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  color: var(--gold);
  font-size: 0.9375rem;
}
.join-note--muted { border-color: var(--line); color: var(--muted); }
.join-note__icon { flex-shrink: 0; margin-top: 0.1rem; }

/* --- Payment page --- */
.order-summary {
  margin-top: 1.25rem;
  background: var(--black-raise);
  border: 1px solid var(--line);
  padding: 1.25rem 1.25rem 1.5rem;
}
.order-summary__row { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; }
.order-summary__total { color: var(--gold); }
.order-summary__final-note {
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.875rem;
}

/* Test mode block */
.test-mode-block {
  margin-top: 1.5rem;
  background: var(--black-raise);
  border: 2px solid var(--line);
  padding: 1.25rem;
}
.test-mode-block__label {
  color: var(--paper);
  margin-bottom: 0.5rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.test-mode-block__desc {
  color: var(--muted);
  margin-bottom: 1.25rem;
  font-size: 0.9375rem;
}

/* Test button — looks like primary but marked unmistakably */
.btn--test {
  background: var(--black);
  color: var(--paper);
  border: 2px dashed var(--gold);
}
.btn--test:active { background: var(--black-raise); }

/* --- Welcome page (post-payment: "Welcome to the Club") --- */
.welcome-page {
  min-height: 80dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 3rem var(--side-pad);
  text-align: center;
}

/* The gold "P" mark — replaces the old dot, same choreography.
   The hidden-then-revealed states are a JS ENHANCEMENT, gated on `.js` so a
   no-JS / slow-JS visitor sees the whole welcome screen (mirrors the homepage
   reveal, DESIGN §5b — the reveal must never blank content). */
.welcome-dot {
  display: block;
  width: auto;
  height: 104px;
  margin: 0 auto 1.75rem;
}
.js .welcome-dot {
  opacity: 0;
  transform: scale(0.94);
}
.welcome-dot.is-revealed {
  animation: markReveal 450ms var(--ease-out-soft) forwards;
}
@keyframes markReveal {
  to { opacity: 1; transform: scale(1); }
}

.js .welcome-headline {
  opacity: 0;
  transform: translateY(4px);
}
.welcome-headline.is-revealed {
  animation: fadeUp 300ms var(--ease-out) 300ms forwards;
}

.welcome-copy {
  margin-top: 0.75rem;
  color: var(--muted);
  max-width: 360px;
  margin-left: auto;
  margin-right: auto;
}
.js .welcome-copy {
  opacity: 0;
  transform: translateY(4px);
}
.welcome-copy.is-revealed {
  animation: fadeUp 300ms var(--ease-out) 500ms forwards;
}

.welcome-actions {
  margin-top: 1.5rem;
}
.js .welcome-actions {
  opacity: 0;
}
.welcome-actions.is-revealed {
  animation: fadeUp 300ms var(--ease-out) 600ms forwards;
}

@keyframes fadeUp {
  to { opacity: 1; transform: translateY(0); }
}

/* Reduced motion: override all transforms with simple fades */
@media (prefers-reduced-motion: reduce) {
  .welcome-dot.is-revealed {
    animation: simpleFade 200ms ease-out forwards;
    transform: none;
  }
  .welcome-headline.is-revealed,
  .welcome-copy.is-revealed,
  .welcome-actions.is-revealed {
    animation: simpleFade 200ms ease-out forwards;
    transform: none;
  }
}
@keyframes simpleFade { to { opacity: 1; } }

/* --- Login / Join standalone pages --- */
.standalone-page {
  max-width: 400px;
  margin: 0 auto;
  padding: 2rem var(--side-pad) 3rem;
}
.standalone-page__masthead {
  margin-bottom: 1.5rem;
}

/* --- Page-load reveal states (managed by public.js) ---
   The reveal is an ENHANCEMENT, never a gate: content is fully visible by
   default. Only when JS is present (.js on <html>, set synchronously in
   base.html) do we hide-then-reveal. No JS / slow JS → the page just shows. */
.js .fc-reveal-target {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 460ms var(--ease-out), transform 460ms var(--ease-out);
}
.fc-reveal-target.is-visible {
  opacity: 1;
  transform: translateY(0);
}
.fc-reveal-target.no-motion {
  transition: opacity 200ms ease-out;
  transform: none;
}
.fc-reveal-target.no-motion.is-visible {
  opacity: 1;
}
