/* ===========================================================
   AL FALLO — Design system
   Palette: warm black + wine red + cream. Elegant, minimal.
   =========================================================== */

/* Google Fonts is loaded via <link> tags in each page's <head> (with
   preconnect), not a CSS @import here — @import forces the browser to
   fetch this stylesheet, parse it, discover the import, then fetch the
   font CSS in sequence; a <link> lets it start in parallel with everything
   else in <head>. */

/* Tokens live in css/tokens.css (linked before this file). */

*, *::before, *::after{ box-sizing: border-box; }

/* No :focus-visible rule existed anywhere before this — keyboard users
   fell back to each browser's own default outline (often a hard-edged
   blue rectangle that clashes with the dark palette, or barely visible
   against it). One on-brand ring, everywhere, restores visible focus
   without changing anything for mouse/touch users. */
:focus-visible{
  outline: 2px solid var(--rose);
  outline-offset: 3px;
  border-radius: 2px;
}

.sr-only{
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}
html{
  scroll-behavior: smooth;
  scroll-snap-type: y proximity;
  /* The full-bleed sections (.timeline-scroll, etc.) use `width:100vw` to
     break out of .container, but 100vw includes the vertical scrollbar's
     own width in most browsers — that sliver of overflow was enough to let
     the whole page pan sideways. This is the fix, not a workaround: the
     page itself should never scroll horizontally; only the timeline's own
     inner rail (which keeps its own overflow-x:auto) does. */
  overflow-x: hidden;
  scrollbar-width: thin;
  scrollbar-color: var(--wine) var(--bg-elev);
}
::-webkit-scrollbar{ width: 10px; height: 10px; }
::-webkit-scrollbar-track{ background: var(--bg-elev); }
::-webkit-scrollbar-thumb{
  background: var(--wine);
  border-radius: 999px;
  border: 2px solid var(--bg-elev);
}
::-webkit-scrollbar-thumb:hover{ background: var(--wine-light); }

/* cursor glow trail — decorative, desktop pointer only */
/* Cursor follower: a small twinkling star instead of a generic glow
   blob, echoing the doodle stars used throughout the site. The outer
   .cursor-glow element is purely a JS-positioned anchor (main.js sets
   its transform every frame to lerp toward the pointer) — all visual
   styling and the twinkle animation live on the inner .cursor-star
   child instead, so the two `transform` animations (position vs.
   scale/rotate) never fight over the same property on the same
   element. Uses a plain inline <svg> (not mask-image): a CSS mask on a
   local file:// page has proven unreliable in this project before, and
   an inline SVG has no such risk. */
.cursor-glow{
  position: fixed;
  top: 0; left: 0;
  width: 1px; height: 1px;
  pointer-events: none;
  z-index: 1;
  will-change: transform;
  opacity: 0;
  transition: opacity 400ms var(--ease-out);
}
.cursor-glow.is-active{ opacity: 1; }
.cursor-star{
  position: absolute;
  top: -14px; left: -14px;
  width: 28px; height: 28px;
  color: var(--cream);
  filter: drop-shadow(0 0 6px rgba(255,59,48,.7));
  animation: cursor-star-twinkle 1.6s ease-in-out infinite;
}
.cursor-star svg{ display: block; width: 100%; height: 100%; }
@keyframes cursor-star-twinkle{
  0%, 100%{ transform: scale(0.8) rotate(0deg); opacity: .75; }
  50%{ transform: scale(1.15) rotate(12deg); opacity: 1; }
}
@media (hover: none), (pointer: coarse){
  .cursor-glow{ display: none; }
}
@media (prefers-reduced-motion: reduce){
  .cursor-glow{ display: none; }
  .cursor-star{ animation: none; }
  html{ scroll-behavior: auto; scroll-snap-type: none; }
  ::view-transition-old(root), ::view-transition-new(root){ animation: none; }
}

/* Ticket confetti: small shards flung from the click point on "Get
   tickets" links (see js/main.js). Each span is positioned at the click
   coordinates and animates outward via CSS custom properties (--dx/--dy/
   --rot) set per-shard in JS, then removes itself on animationend. */
.ticket-confetti{
  position: fixed;
  top: 0; left: 0;
  width: 7px; height: 10px;
  border-radius: 1px;
  pointer-events: none;
  z-index: 999;
  transform: translate(-50%, -50%);
  animation: ticket-confetti-burst 700ms cubic-bezier(.2,.8,.3,1) forwards;
}
@keyframes ticket-confetti-burst{
  0%{ transform: translate(-50%, -50%) translate(0, 0) rotate(0deg); opacity: 1; }
  100%{ transform: translate(-50%, -50%) translate(var(--dx), calc(var(--dy) + 90px)) rotate(var(--rot)); opacity: 0; }
}
@media (prefers-reduced-motion: reduce){
  .ticket-confetti{ display: none; }
}

/* Lazy-image shimmer: a moving highlight placeholder shown on any
   <img loading="lazy"> until main.js marks it .is-loaded on load, so
   photos never pop in against a flat blank gap. */
img[loading="lazy"]{
  background: linear-gradient(100deg, rgba(255,255,255,.03) 30%, rgba(255,255,255,.09) 50%, rgba(255,255,255,.03) 70%);
  background-size: 200% 100%;
  animation: img-shimmer 1.6s ease-in-out infinite;
}
img[loading="lazy"].is-loaded{
  animation: none;
  background: none;
}
@keyframes img-shimmer{
  0%{ background-position: 200% 0; }
  100%{ background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce){
  img[loading="lazy"]{ animation: none; }
}

body{
  margin: 0;
  background: var(--bg);
  color: var(--cream);
  font-family: var(--font-body);
  font-weight: 400;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img{ max-width: 100%; display: block; }
a{ color: inherit; text-decoration: none; }
ul{ list-style: none; margin: 0; padding: 0; }
button{ font: inherit; color: inherit; background: none; border: none; cursor: pointer; }

.container{
  max-width: var(--container);
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

/* grain overlay for mood, applied on dark hero sections */
.grain{
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: .35;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- Typography ---------- */
.eyebrow{
  font-family: var(--font-body);
  font-size: 0.72rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  /* --wine-light only clears ~3.3:1 against --bg — enough for large text,
     not for a label this small (WCAG AA needs 4.5:1 below ~18.5px).
     --rose is the same family, one step lighter, and clears ~4.9:1. */
  color: var(--rose);
  font-weight: 600;
}

h1, h2, h3{
  font-family: var(--font-display);
  font-weight: 500;
  line-height: 1.05;
  margin: 0;
  letter-spacing: -0.01em;
}

h2{ font-size: clamp(2rem, 4vw, 3.2rem); }
h3{ font-size: clamp(1.3rem, 2.2vw, 1.7rem); font-weight: 500; }

p{ color: var(--cream-dim); margin: 0; }

.section-head{
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-bottom: clamp(32px, 5vw, 56px);
}
.section-head-row{
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
}
.section-head-row .section-head{ margin-bottom: 0; }
.page-hero-row{
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
}

/* ---------- Countdown glass ----------
   The same real traced glass/clip-path used everywhere else (see
   .tl-glass2d in the gallery-timeline section below), shrunk to a badge
   and driven by days-until-the-next-party instead of which edition is
   selected. Empty starting COUNTDOWN_WINDOW_DAYS out, full on the day —
   see injectCountdown() in calendar.js. */
.countdown-glass{
  display: flex;
  align-items: center;
  gap: 12px;
}
.countdown-glass #countdown-glass2d{
  position: relative !important;
  width: 72px;
  height: 90px;
  flex-shrink: 0;
}
@media (min-width: 900px){
  .countdown-glass #countdown-glass2d{ width: 88px; height: 109px; }
}
.countdown-text{ display: flex; flex-direction: column; line-height: 1.15; }
.countdown-text b{
  font-family: var(--font-display);
  font-size: 1.8rem;
  color: var(--cream);
}
.countdown-text span{
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cream-faint);
}

/* ---------- Buttons ---------- */
.btn{
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 15px 28px;
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 500;
  letter-spacing: 0.03em;
  transition: transform var(--dur-press) var(--ease-out), background .3s var(--ease), border-color .3s var(--ease), color .3s var(--ease);
  white-space: nowrap;
}
.btn-primary{
  background: var(--wine);
  color: var(--cream);
  border: 1px solid var(--wine);
}
.btn-primary:hover{ background: var(--wine-light); border-color: var(--wine-light); }
.btn-primary:active{ transform: scale(0.97); }

.btn-ghost{
  background: transparent;
  color: var(--cream);
  border: 1px solid var(--line-strong);
}
.btn-ghost:hover{ border-color: var(--cream); }
.btn-ghost:active{ transform: scale(0.97); }

.btn-arrow{ transition: transform .35s var(--ease); }

/* Hover-triggered transforms only for real hover devices — a touch tap
   would otherwise leave the button "stuck" translated until an unrelated
   tap elsewhere clears the :hover state. */
@media (hover: hover) and (pointer: fine){
  .btn-primary:hover{ transform: translateY(-2px); }
  .btn-ghost:hover{ transform: translateY(-2px); }
  .btn:hover .btn-arrow{ transform: translateX(4px); }
}

/* ---------- Nav ---------- */
.nav{
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 22px var(--gutter);
  background: linear-gradient(to bottom, rgba(11,6,5,0.75), transparent);
  transition: background .4s var(--ease), padding .4s var(--ease), border-color .4s var(--ease), filter .35s var(--ease), opacity .35s var(--ease);
  border-bottom: 1px solid transparent;
}
.nav.is-scrolled{
  background: rgba(11,6,5,0.88);
  backdrop-filter: blur(10px);
  padding-top: 14px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
}
.nav-logo{
  font-family: var(--font-display);
  font-size: 1.35rem;
  letter-spacing: 0.04em;
  display: flex;
  align-items: center;
  gap: 8px;
}
.nav-logo span{ color: var(--wine-light); font-style: italic; }

/* 3D spinning logo: several copies of the artwork stacked along Z to fake
   real thickness (a flat plane spinning on Y just flips/squishes — this
   reads as an actual extruded object because the transparent letterforms
   show a stacked edge as they turn side-on). */
.nav-logo-3d{
  display: inline-block;
  width: 148px;
  height: 50px;
  perspective: 900px;
}
.nav-logo-3d-inner{
  display: block;
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  animation: nav-logo-spin 5s var(--ease-in-out) infinite;
}
.nav-logo-layer{
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
}
/* A full 360deg spin of near-flat content passes through edge-on (where it
   all but disappears) twice per turn. Oscillating instead keeps it legible
   at every moment while still reading as a real, rotating 3D object. */
@keyframes nav-logo-spin{
  0%, 100%{ transform: rotateY(-42deg); }
  50%{ transform: rotateY(42deg); }
}
@media (prefers-reduced-motion: reduce){
  .nav-logo-3d-inner{ animation: none; }
}

.nav-links{
  display: flex;
  align-items: center;
  gap: 40px;
}
.nav-links a{
  font-size: 0.82rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--cream-dim);
  position: relative;
  padding-bottom: 4px;
  transition: color .3s var(--ease);
}
.nav-links a::after{
  content: '';
  position: absolute;
  left: 0; bottom: 0;
  width: 0; height: 1px;
  background: var(--wine-light);
  transition: width .35s var(--ease);
}
.nav-links a:hover, .nav-links a.is-active{ color: var(--cream); }
.nav-links a:hover::after, .nav-links a.is-active::after{ width: 100%; }

/* Nav promo CTA — a real button among the plain text nav links (always
   first, to the left of Home), reusing .btn/.btn-primary for the exact
   same look and hover lift as the hero's "View calendar" button, just
   sized to sit comfortably inline with the nav text. */
.nav-links .btn-nav-promo{
  padding: 8px 16px;
  font-size: 0.7rem;
}
.nav-links .btn-nav-promo::after{ display: none; }
.nav-links .btn-nav-promo:hover, .nav-links .btn-nav-promo.is-active{ color: var(--cream); }

.nav-cta{ display: flex; align-items: center; gap: 22px; }
.nav-burger{
  display: none;
  flex-direction: column;
  gap: 5px;
  width: 26px;
  z-index: 200;
}
.nav-burger span{
  height: 1px;
  background: var(--cream);
  transition: transform .35s var(--ease), opacity .35s var(--ease);
}

.mobile-menu{
  position: fixed;
  inset: 0;
  background: var(--bg-elev);
  z-index: 90;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 28px;
  padding: 0 var(--gutter);
  transform: translateY(-100%);
  transition: transform .5s var(--ease);
}
.mobile-menu.is-open{ transform: translateY(0); }
.mobile-menu a{
  font-family: var(--font-display);
  font-size: 2rem;
  color: var(--cream);
}
/* Promo link gets its own compact pill treatment instead of the giant
   serif nav headings, so it reads as "the offer" rather than a page. */
.mobile-menu .mobile-menu-promo{
  display: inline-flex;
  align-items: center;
  padding: 12px 24px;
  border-radius: 999px;
  background: var(--wine);
  border: 1px solid var(--wine);
  font-family: var(--font-body);
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

/* ---------- Hero ---------- */
.hero{
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  overflow: hidden;
  background:
    radial-gradient(ellipse 80% 60% at 50% 15%, rgba(216,40,30,0.35), transparent 60%),
    radial-gradient(ellipse 60% 50% at 85% 85%, rgba(216,40,30,0.18), transparent 60%),
    var(--bg);
}
.hero-inner{
  position: relative;
  z-index: 2;
  padding: 160px var(--gutter) 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
}
.hero-eyebrow{
  margin-bottom: 22px;
  /* A direct child of a column flex container sizes to its own
     max-content width by default (align-items isn't "stretch"), so this
     long, letter-spaced line never wrapped and instead ran off the edge
     on narrow phones. Capping it at the flex container's own width lets
     it wrap like normal text again. */
  max-width: 100%;
}
.hero-title{
  font-size: clamp(3.2rem, 11vw, 8.5rem);
  text-transform: uppercase;
  display: flex;
  flex-direction: column;
  gap: 0.02em;
}
.hero-title em{
  font-style: italic;
  font-weight: 400;
  color: var(--wine-light);
  text-transform: none;
}

/* logo entrance: curtain reveal + soft rise, masks the crossfade per design-eng blur guidance */
.hero-logo{
  width: min(560px, 78vw);
  clip-path: inset(0 0 0 0);
  filter: blur(0);
  opacity: 1;
  transform: translateY(0) scale(1);
  transition: clip-path 900ms var(--ease-in-out), filter 700ms var(--ease-out), opacity 700ms var(--ease-out), transform 900ms var(--ease-out);
}
.hero-logo[data-loading]{
  clip-path: inset(0 100% 0 0);
  filter: blur(6px);
  opacity: 0;
  transform: translateY(14px) scale(0.97);
}
@media (prefers-reduced-motion: reduce){
  .hero-logo{ transition: opacity 300ms ease; }
  .hero-logo[data-loading]{ clip-path: inset(0 0 0 0); transform: none; filter: none; }
}
.hero-sub{
  max-width: 480px;
  margin: 26px auto 36px;
  font-size: 1.05rem;
}
.hero-ctas{
  margin-top: 40px;
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  justify-content: center;
}

/* ---------- Ambient drifting glow ----------
   Two soft, very slow-moving blurred blobs so the hero (and the intro
   overlay, which shares this class) never reads as an empty flat
   rectangle. Movement is subtle and looping — a mood, not a focal point. */
.ambient-glow{
  position:absolute; inset:0; overflow:hidden; z-index:0; pointer-events:none;
}
.ambient-glow span{
  position:absolute; border-radius:50%; filter: blur(70px);
  opacity:.5; will-change: transform;
}
.ambient-glow span.a{
  width:46vmax; height:46vmax; left:-10vmax; top:-14vmax;
  background: radial-gradient(circle, rgba(255,59,48,.55), transparent 70%);
  animation: drift-a 26s ease-in-out infinite alternate;
}
.ambient-glow span.b{
  width:38vmax; height:38vmax; right:-12vmax; bottom:-16vmax;
  background: radial-gradient(circle, rgba(216,40,30,.6), transparent 70%);
  animation: drift-b 32s ease-in-out infinite alternate;
}
@keyframes drift-a{
  0%{ transform: translate(0,0) scale(1); }
  100%{ transform: translate(6vmax, 8vmax) scale(1.12); }
}
@keyframes drift-b{
  0%{ transform: translate(0,0) scale(1); }
  100%{ transform: translate(-7vmax,-6vmax) scale(1.08); }
}
@media (prefers-reduced-motion: reduce){
  .ambient-glow span{ animation: none; }
}

/* ---------- Hero doodles ----------
   Small hand-drawn-style accents (stars/sparkles) scattered around the
   hero, echoing the flyer artwork. Each twinkles on its own slow, offset
   loop — never in unison — so it reads as ambient sparkle, not a UI
   element competing for attention. */
.hero-doodles{
  position:absolute; inset:0; z-index:1; pointer-events:none;
  overflow:hidden;
}
.hero-doodle{
  position:absolute; color:var(--cream);
  opacity:.85; filter:drop-shadow(0 2px 4px rgba(60,20,0,.25));
  animation: doodle-twinkle 3.6s ease-in-out infinite;
  /* --px/--py: pointer-parallax offset, updated per-frame by main.js */
  transform: translate(var(--px, 0px), var(--py, 0px));
}
.hero-doodle svg{ display:block; width:100%; height:100%; }
@keyframes doodle-twinkle{
  0%, 100%{ opacity:.35; transform: translate(var(--px, 0px), var(--py, 0px)) scale(0.85) rotate(var(--rot, 0deg)); }
  50%{ opacity:1; transform: translate(var(--px, 0px), var(--py, 0px)) scale(1) rotate(var(--rot, 0deg)); }
}
@media (prefers-reduced-motion: reduce){
  .hero-doodle{ animation: none; opacity:.7; }
}

/* ---------- Hero scroll cue ---------- */
.hero-scroll-cue{
  position:absolute; left:50%; bottom:48px; z-index:2;
  transform: translateX(-50%);
  color: var(--cream);
  opacity:.8;
  animation: scroll-cue-bounce 1.8s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce){
  .hero-scroll-cue{ animation: none; }
}
@keyframes scroll-cue-bounce{
  0%, 100%{ transform: translate(-50%, 0); }
  50%{ transform: translate(-50%, 10px); }
}

/* Torn edge at the foot of the hero: an irregular zigzag filled in the
   next section's own colour, so the warm hero looks torn into the dark
   rest of the page instead of cut along a ruled line. */
.hero-torn-edge{
  position: absolute; left: 0; right: 0; bottom: -1px;
  width: 100%; height: 34px;
  fill: var(--bg);
  z-index: 2;
  pointer-events: none;
}

/* ---------- Sticker-style button hover ----------
   A little wiggle instead of a flat lift, like a sticker peeling — only
   on the hero CTAs for now. */
@media (hover: hover) and (pointer: fine){
  .hero-ctas .btn:hover{ animation: sticker-wiggle 420ms ease-in-out; }
}
@keyframes sticker-wiggle{
  0%, 100%{ transform: translateY(-2px) rotate(0deg); }
  25%{ transform: translateY(-2px) rotate(-2.5deg); }
  75%{ transform: translateY(-2px) rotate(2.5deg); }
}
@media (prefers-reduced-motion: reduce){
  .hero-ctas .btn:hover{ animation: none; }
}

/* ---------- Sections generic ---------- */
section{ position: relative; padding: clamp(72px, 10vw, 140px) 0; }
.section-alt{ background: var(--bg-elev); }
/* Snap only applies to the home hero — snapping every content block (or even
   the shorter inner-page heroes) trapped the scroll partway down the page. */
.hero{ scroll-snap-align: start; }

/* About */
.about-grid{
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: clamp(40px, 6vw, 100px);
  align-items: start;
}
.about-lead{
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 2.6vw, 2.1rem);
  font-weight: 400;
  line-height: 1.35;
  color: var(--cream);
}
.about-lead em{ font-style: italic; color: var(--wine-light); }
.about-stats{
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 28px;
  margin-top: 40px;
}
.about-stat b{
  display: block;
  font-family: var(--font-display);
  font-size: 2.2rem;
  color: var(--cream);
}
.about-stat span{
  font-size: 0.8rem;
  color: var(--cream-faint);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
/* ---------- About photo collage ----------
   Real party photos scattered like polaroids instead of an empty mood
   panel. The container reveals as a whole (existing [data-reveal]
   system); each photo then scatters into its own resting position/
   rotation with a short stagger, so they feel dropped into place one
   after another rather than fading in as a flat block. */
.about-collage{
  position: relative;
  aspect-ratio: 4/5;
}
.about-photo{
  position: absolute;
  width: 62%;
  aspect-ratio: 4/5;
  object-fit: cover;
  border-radius: 6px;
  border: 6px solid var(--cream);
  box-shadow: 0 18px 34px rgba(0,0,0,0.45);
  opacity: 0;
  transition: opacity 500ms var(--ease-out), transform 500ms var(--ease-out);
}
.about-collage.is-visible .about-photo{ opacity: 1; }
.about-photo.p1{ left: 0; top: 0; width: 58%; transform: rotate(-7deg); z-index: 2; transition-delay: 60ms; }
.about-collage.is-visible .about-photo.p1{ transform: rotate(-7deg); }
.about-photo.p2{ right: 0; top: 6%; width: 50%; transform: rotate(6deg) translateY(-10px); z-index: 1; transition-delay: 160ms; }
.about-collage.is-visible .about-photo.p2{ transform: rotate(6deg) translateY(0); }
.about-photo.p3{ left: 8%; bottom: 0; width: 48%; transform: rotate(4deg) translateY(10px); z-index: 3; transition-delay: 260ms; }
.about-collage.is-visible .about-photo.p3{ transform: rotate(4deg) translateY(0); }
.about-photo.p4{ right: 4%; bottom: 4%; width: 42%; transform: rotate(-5deg) translateY(10px); z-index: 4; transition-delay: 360ms; }
.about-collage.is-visible .about-photo.p4{ transform: rotate(-5deg) translateY(0); }
@media (hover: hover) and (pointer: fine){
  .about-photo{ transition: opacity 500ms var(--ease-out), transform 300ms var(--ease-out), z-index 0s; }
  .about-photo:hover{ transform: rotate(0deg) scale(1.05) !important; z-index: 5; }
}
@media (prefers-reduced-motion: reduce){
  .about-photo{ transition: opacity 300ms ease; }
}

/* ---------- Newsletter / discount signup ---------- */
.newsletter-inner{
  max-width: 620px;
  text-align: center;
}
.newsletter-inner .section-head{ margin-bottom: 32px; }
.newsletter-sub{
  margin-top: 12px;
  color: var(--cream-faint);
  max-width: 480px;
  margin-left: auto;
  margin-right: auto;
}
.newsletter-form{ margin-top: 32px; }
.newsletter-row{
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  justify-content: center;
}
.field-group{
  position: relative;
  flex: 1 1 260px;
  min-width: 0;
}
.newsletter-row input[type="email"]{
  width: 100%;
  padding: 14px 18px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--bg-elev-2);
  color: var(--cream);
  font-family: var(--font-body);
  font-size: 0.95rem;
}
.newsletter-row input[type="email"]::placeholder{ color: var(--cream-faint); }
.newsletter-row input[type="email"]:focus-visible{
  outline: 2px solid var(--rose);
  outline-offset: 2px;
}
.newsletter-consent{
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-top: 28px;
  text-align: left;
  max-width: 480px;
  margin-left: auto;
  margin-right: auto;
  font-size: 0.82rem;
  color: var(--cream-faint);
  cursor: pointer;
}
.newsletter-consent input{ margin-top: 3px; flex-shrink: 0; }
.field-group-consent{
  position: relative;
  max-width: 480px;
  margin: 0 auto;
}
/* Custom validation tooltip, replacing the browser's native error bubble
   (unstyleable, breaks the page's look) with one in the wine/cream theme.
   JS (main.js) intercepts the 'invalid' event, prevents the native bubble,
   and toggles .is-visible with its own message text instead. */
.field-error{
  position: absolute;
  left: 0;
  top: calc(100% + 10px);
  z-index: 5;
  display: none;
  padding: 10px 14px;
  border-radius: 10px;
  background: var(--wine-deep);
  border: 1px solid var(--wine-light);
  color: var(--cream);
  font-size: 0.78rem;
  font-family: var(--font-body);
  text-align: left;
  line-height: 1.4;
  max-width: 280px;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
}
.field-error.is-visible{ display: block; animation: field-error-in 200ms var(--ease-out); }
.field-error::before{
  content: '';
  position: absolute;
  left: 20px;
  top: -6px;
  width: 12px;
  height: 12px;
  background: var(--wine-deep);
  border-left: 1px solid var(--wine-light);
  border-top: 1px solid var(--wine-light);
  transform: rotate(45deg);
}
.field-group-consent .field-error{ left: 32px; top: calc(100% + 6px); }
.field-group-consent .field-error::before{ left: 14px; }
@keyframes field-error-in{
  from{ opacity: 0; transform: translateY(-4px); }
  to{ opacity: 1; transform: translateY(0); }
}
.newsletter-honeypot{
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}
.newsletter-success{
  margin-top: 20px;
  color: var(--rose);
  font-weight: 600;
}
@media (max-width: 560px){
  .newsletter-row{ flex-direction: column; }
  .newsletter-row .btn{ width: 100%; justify-content: center; }
}

/* ---------- Footer ---------- */
/* Ultra-minimal footer */
.footer-mini{
  padding: 28px var(--gutter);
  border-top: 1px solid var(--line);
  background: var(--bg);
  filter: blur(0);
  transition: filter .35s var(--ease), opacity .35s var(--ease);
}
body.is-gallery-panning .footer-mini,
body.is-gallery-panning .nav{
  filter: blur(14px);
  opacity: .35;
  pointer-events: none;
}
.footer-mini-inner{
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 14px;
}
/* index.html only: no footer-logo left, so keep the remaining links
   where they read most naturally (right-aligned) instead of the empty
   flex snapping them to the left. */
.footer-mini-inner-solo{ justify-content: flex-end; }
.footer-mini-links{
  display: flex;
  align-items: center;
  gap: 24px;
  font-size: 0.8rem;
  color: var(--cream-faint);
}
.footer-mini-links a{ transition: color .3s var(--ease); }
.footer-mini-links a:hover{ color: var(--cream); }

/* ---------- Reveal animation (minimal, per brief) ---------- */
[data-reveal]{
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 350ms var(--ease-out), transform 350ms var(--ease-out);
  transition-delay: calc(var(--reveal-i, 0) * 60ms);
}
[data-reveal].is-visible{ opacity: 1; transform: translateY(0); }
@media (prefers-reduced-motion: reduce){
  [data-reveal]{ transition-duration: 200ms; transform: none; }
}

/* ---------- Events carousel (Fourvenues-style) ---------- */
.carousel{
  display: flex;
  align-items: stretch;
  gap: 20px;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding-bottom: 8px;
  margin: 0 calc(var(--gutter) * -1);
  padding-left: var(--gutter);
  padding-right: var(--gutter);
  scrollbar-width: thin;
  scrollbar-color: var(--wine) transparent;
}
.carousel::-webkit-scrollbar{ height: 6px; }
.carousel::-webkit-scrollbar-thumb{ background: var(--wine); border-radius: 999px; }

.event-card{
  scroll-snap-align: start;
  flex: 0 0 clamp(260px, 34vw, 360px);
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
  background: var(--bg-elev-2);
  display: flex;
  flex-direction: column;
  transition: transform 200ms var(--ease-out), border-color 200ms var(--ease-out);
}
.event-card:hover{ border-color: var(--line-strong); }
@media (hover: hover) and (pointer: fine){
  .event-card:hover{ transform: translateY(-4px); }
}
.event-card-media{
  /* No fixed ratio: the flyer sets its own height, so a square or 3:4 flyer
     fills the card instead of leaving empty bands (and is never cropped). */
  position: relative;
  background:
    radial-gradient(circle at 30% 20%, rgba(255,59,48,0.5), transparent 55%),
    linear-gradient(160deg, var(--wine-deep), var(--bg) 80%);
}
.event-card-media.no-cover{ aspect-ratio: 4/5; }
.event-card-media img{ display: block; width: 100%; height: auto; }
.event-card-body{ padding: 22px; flex: 1; display: flex; flex-direction: column; }
.event-card-body > .btn, .event-card-body > .event-past-label{ margin-top: auto; align-self: flex-start; }
.event-card-body .event-tags{ margin-bottom: 12px; }
.event-card-body h3{ margin-bottom: 8px; font-size: 1.2rem; }
.event-card-body .meta{
  display: flex; flex-direction: column; gap: 4px;
  font-size: 0.82rem; color: var(--cream-faint);
  margin: 10px 0 18px;
}

/* ---------- Timeline gallery ---------- */
/* A slim horizontal node rail (oldest -> newest, left to right) stays on
   screen at all times. Clicking a node's photo opens a full-screen zoom
   overlay with the photo in perspective + big date/title; clicking the
   backdrop drops you back to the timeline; clicking the title goes straight
   into the full album grid. */
.timeline-scroll{
  /* full-bleed: break out of .container to the true edges of the screen */
  position: relative;
  left: 50%;
  right: 50%;
  margin-left: -50vw;
  margin-right: -50vw;
  width: 100vw;
  overflow-x: auto;
  overflow-y: visible;
  padding: 14px 0 115px;
  /* Still scrollable by touch/drag/wheel to browse further editions, but no
     visible scrollbar chrome — the glass/tap/first photos read as complete
     on their own without a bar hinting there's a cut-off section to scroll. */
  scrollbar-width: none;
}
.timeline-scroll::-webkit-scrollbar{ display: none; }
/* Let the camera push-in (.is-cam-glass/.is-cam-pan below) scale past the
   scroll container's own bounds instead of being clipped by overflow-x. */
.timeline-scroll:has(.is-cam-glass), .timeline-scroll:has(.is-cam-pan){
  overflow: visible;
}
/* Perspective for the 3D turn in .is-cam-pan below. */
.timeline-scroll{ perspective: 1400px; }

.timeline{
  position: relative;
  display: flex;
  justify-content: space-evenly;
  align-items: flex-start;
  min-width: 100%;
  gap: 32px;
  padding: 110px clamp(24px, 6vw, 100px) 0 clamp(220px, 20vw, 300px);
  transition: transform 550ms cubic-bezier(0.23, 1, 0.32, 1);
  transform-style: preserve-3d;
}
/* Pour-sequence camera move: a hard push-in that fills the screen with the
   glass — the sole focus while its drops fall, everything else dimmed by
   .tl-spotlight below — then a fast 3D turn to the left right before the
   photo reveals. Reduced motion never adds either class (see openZoom() in
   gallery.js), so the base transform stays put. */
.timeline.is-cam-glass{
  transform: scale(2.2);
}
.timeline.is-cam-pan{
  transform: scale(2.4) translateX(-42vw) rotateY(24deg);
  transition: transform 260ms cubic-bezier(0.55, 0, 0.85, 0.35);
}
/* Vignette that makes the glass the sole focus during the push-in: fully
   transparent over the glass, fading to near-opaque everywhere else. Its
   center tracks the glass's live position (--spot-x/--spot-y, set in
   buildTimelineBeam()). Sits above the nodes/tube (z-index) so it actually
   dims them, and fades out again during the turn so the photo reveal isn't
   fighting a dark overlay. */
.tl-spotlight{
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(
    circle at var(--spot-x, 60px) var(--spot-y, 50%),
    rgba(11,6,5, 0) 0,
    rgba(11,6,5, 0) 95px,
    rgba(11,6,5, 0.94) 260px
  );
  transition: opacity 450ms var(--ease-out);
}
.timeline.is-cam-glass .tl-spotlight{ opacity: 1; }
.timeline.is-cam-pan .tl-spotlight{
  opacity: 0;
  transition: opacity 220ms ease-in;
}
/* The timeline as a glass tube of wine, curving through every party's
   junction point (computed in JS from real node positions): a soft ambient
   halo, a faint glass outline, a wine-gradient fill, a flowing highlight
   inside the liquid, and a specular shine streak along the top of the glass. */
.tl-beam{
  position: absolute;
  inset: 0;
  overflow: visible;
  pointer-events: none;
  z-index: 0;
}
.tl-beam path{ fill: none; }
.tl-beam-halo{
  stroke: var(--wine);
  stroke-width: 26;
  opacity: 0.22;
}
.tl-tube-glass{
  stroke: rgba(255,241,224, 0.14);
  stroke-width: 20;
  stroke-linecap: round;
}
.tl-tube-fill{
  stroke: url(#tl-tube-flow-gradient);
  stroke-width: 13;
  stroke-linecap: round;
  filter:
    drop-shadow(0 0 4px rgba(216,40,30, 0.65))
    drop-shadow(0 0 14px rgba(216,40,30, 0.4));
}
/* Far wall of the glass tube, seen through the liquid — a fixed dark
   underline (no motion) that gives the tube roundness/depth. */
.tl-tube-underside{
  stroke: rgba(15,6,3, 0.4);
  stroke-width: 9;
  stroke-linecap: round;
  transform: translateY(4px);
}
.tl-tube-shine{
  stroke: rgba(255, 255, 255, 0.32);
  stroke-width: 1.6;
  stroke-linecap: round;
  transform: translateY(-4.5px);
}

/* Procedural 3D aluminum tap (js/tap3d.js) the tube feeds into, mounted
   right above the glass. Positioned in JS (buildTimelineBeam) so its pipe
   inlet (right edge) meets the tube and its spout tip sits above the
   glass — see tapAnchors() in gallery.js. */
.tl-tap3d{
  position: absolute;
  width: 130px;
  height: 98px;
  z-index: 1;
  pointer-events: none;
  user-select: none;
  opacity: 0;
  transition: opacity 500ms var(--ease-out);
  filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.55));
}
.tl-tap3d.is-ready{ opacity: 1; }
.tl-tap3d canvas{ display: block; width: 100%; height: 100%; }

/* The real AL FALLO glass — the protagonist of the timeline. It's the actual
   product photo (assets/brand/alfallo-glass-cutout.png), not a 3D object or
   a hand-drawn illustration: its bowl interior was punched fully transparent
   (see the build script notes in CONTEXT.md) so the wine div sitting behind
   it shows through as real liquid, while the rim/walls/stem/foot/branding
   stay crisp on top. Positioned in JS (buildTimelineBeam) to track the tube/
   tap's endpoint; fixed size matching the photo's own aspect ratio. */
.tl-glass2d{
  position: absolute;
  width: 210px;
  height: 261px;
  pointer-events: none;
  z-index: 1;
}
.tl-glass2d-img{
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 2;
  user-select: none;
  filter: drop-shadow(0 10px 18px rgba(0, 0, 0, 0.5));
}
/* Clipped to the same interior polygon traced from the photo (see
   CONTEXT.md) so the wine never spills outside the bowl's real silhouette. */
.tl-glass2d-wine{
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  clip-path: polygon(45.76% 4.44%, 39.08% 5.01%, 37.52% 5.51%, 37.14% 6.01%, 36.94% 6.51%, 36.81% 7.02%, 36.6% 7.52%, 36.48% 8.02%, 36.27% 8.52%, 36.08% 9.09%, 35.88% 9.59%, 35.67% 10.09%, 35.48% 10.59%, 35.28% 11.1%, 35.08% 11.6%, 34.89% 12.1%, 34.62% 12.67%, 34.42% 13.17%, 34.23% 13.67%, 34.04% 14.17%, 33.84% 14.67%, 33.58% 15.18%, 33.37% 15.68%, 33.19% 16.25%, 33% 16.75%, 32.73% 17.25%, 32.54% 17.75%, 32.35% 18.25%, 32.16% 18.75%, 31.96% 19.26%, 31.7% 19.83%, 31.5% 20.33%, 31.31% 20.83%, 31.11% 21.33%, 30.9% 21.83%, 30.72% 22.33%, 30.61% 22.83%, 30.34% 23.41%, 30.21% 23.91%, 30.01% 24.41%, 29.88% 24.91%, 29.77% 25.41%, 29.64% 25.91%, 29.51% 26.41%, 29.38% 26.99%, 29.33% 27.49%, 29.27% 27.99%, 29.2% 28.49%, 29.21% 28.99%, 29.14% 29.49%, 29.14% 29.99%, 29.14% 30.57%, 29.21% 31.07%, 29.21% 31.57%, 29.28% 32.07%, 29.33% 32.57%, 29.47% 33.07%, 29.52% 33.57%, 29.66% 34.07%, 29.78% 34.65%, 29.89% 35.15%, 30.16% 35.65%, 30.35% 36.15%, 30.55% 36.65%, 30.72% 37.15%, 30.98% 37.65%, 31.24% 38.22%, 31.57% 38.73%, 31.81% 39.23%, 32.13% 39.73%, 32.53% 40.23%, 32.82% 40.73%, 33.22% 41.23%, 33.73% 41.8%, 34.1% 42.3%, 34.64% 42.81%, 35.09% 43.31%, 35.67% 43.81%, 36.11% 44.31%, 36.61% 44.81%, 37.17% 45.38%, 37.75% 45.88%, 38.4% 46.39%, 38.96% 46.89%, 39.66% 47.39%, 40.38% 47.89%, 41.07% 48.39%, 41.9% 48.96%, 42.82% 49.46%, 43.6% 49.96%, 44.47% 50.47%, 56.68% 50.47%, 57.29% 49.96%, 57.98% 49.46%, 58.72% 48.96%, 59.55% 48.39%, 60.33% 47.89%, 60.96% 47.39%, 61.66% 46.89%, 62.31% 46.39%, 62.88% 45.88%, 63.45% 45.38%, 64.1% 44.81%, 64.69% 44.31%, 65.21% 43.81%, 65.71% 43.31%, 66.16% 42.81%, 66.52% 42.3%, 66.98% 41.8%, 67.49% 41.23%, 67.8% 40.73%, 68.27% 40.23%, 68.58% 39.73%, 68.9% 39.23%, 69.23% 38.73%, 69.47% 38.22%, 69.73% 37.65%, 69.99% 37.15%, 70.25% 36.65%, 70.45% 36.15%, 70.64% 35.65%, 70.82% 35.15%, 71.02% 34.65%, 71.23% 34.07%, 71.28% 33.57%, 71.42% 33.07%, 71.47% 32.57%, 71.61% 32.07%, 71.67% 31.57%, 71.67% 31.07%, 71.66% 30.57%, 71.66% 29.99%, 71.66% 29.49%, 71.67% 28.99%, 71.6% 28.49%, 71.53% 27.99%, 71.47% 27.49%, 71.33% 26.99%, 71.2% 26.41%, 71.07% 25.91%, 70.94% 25.41%, 70.74% 24.91%, 70.61% 24.41%, 70.5% 23.91%, 70.37% 23.41%, 70.19% 22.83%, 69.99% 22.33%, 69.72% 21.83%, 69.6% 21.33%, 69.49% 20.83%, 69.29% 20.33%, 69.1% 19.83%, 68.92% 19.26%, 68.73% 18.75%, 68.54% 18.25%, 68.35% 17.75%, 68.16% 17.25%, 67.98% 16.75%, 67.79% 16.25%, 67.52% 15.68%, 67.4% 15.18%, 67.22% 14.67%, 67.03% 14.17%, 66.84% 13.67%, 66.65% 13.17%, 66.53% 12.67%, 66.35% 12.1%, 66.16% 11.6%, 65.97% 11.1%, 65.85% 10.59%, 65.66% 10.09%, 65.55% 9.59%, 65.43% 9.09%, 65.24% 8.52%, 65.12% 8.02%, 64.99% 7.52%, 64.88% 7.02%, 64.75% 6.51%, 64.64% 6.01%, 64.25% 5.51%, 62.87% 5.01%, 56.37% 4.44%);
}
/* The liquid level itself: absolutely positioned by `top` (a % of the glass
   box), sliding between GLASS2D_LEVEL_EMPTY_PCT and _FULL_PCT (gallery.js).
   Oversized (well past the wine div's own bounds) since the clip-path above
   is what actually shapes it — this element only needs to supply a moving
   top edge. */
.tl-glass2d-level{
  position: absolute;
  left: -10%;
  right: -10%;
  height: 150%;
  background: linear-gradient(180deg, #a8160f 0%, #630f1f 55%, #1e0509 100%);
  transition: top 420ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
@media (prefers-reduced-motion: reduce){
  .tl-glass2d-level{ transition: top 300ms ease; }
}
.tl-glass2d-surface{
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 5px;
  background: linear-gradient(rgba(255,200,120, 0.55), rgba(255,200,120, 0));
}
.tl-glass2d-ripple{
  position: absolute;
  top: -3px;
  left: 50%;
  width: 16px;
  height: 7px;
  margin-left: -8px;
  border: 1px solid rgba(216,40,30, 0.6);
  border-radius: 50%;
  opacity: 0;
}
.is-splashing .r1{ animation: tl-glass2d-ripple 650ms ease-out; }
.is-splashing .r2{ animation: tl-glass2d-ripple 650ms ease-out 130ms; }
@keyframes tl-glass2d-ripple{
  0%{ transform: scale(0.3); opacity: 0.7; }
  100%{ transform: scale(3.2); opacity: 0; }
}
@media (prefers-reduced-motion: reduce){
  .is-splashing .r1, .is-splashing .r2{ animation: none; }
}
/* The falling drop: a small teardrop animated purely via WAAPI from
   pourWine() (JS), since its fall distance depends on the live fill level —
   never CSS keyframes. `top`/opacity are driven inline; this just gives it
   its shape and color. */
.tl-glass2d-drop{
  position: absolute;
  left: 50%;
  width: 9px;
  height: 13px;
  margin-left: -4.5px;
  border-radius: 50% 50% 50% 50% / 65% 65% 35% 35%;
  background: linear-gradient(180deg, #ff5a3d, #cf2318);
  opacity: 0;
  z-index: 3;
  transform-origin: top center;
}
/* The drain streak (pourWine()'s "going back to an earlier edition" branch):
   sits right at the neck, on top of the (opaque) stem in the photo, and is
   grown/faded purely via WAAPI in JS to match the drain's own duration —
   this just gives it its shape/color. Height is set inline by the
   animation; top/width/etc. stay fixed here. */
.tl-glass2d-drain{
  position: absolute;
  left: 50%;
  top: 50%;
  width: 6px;
  margin-left: -3px;
  height: 0;
  border-radius: 0 0 3px 3px;
  background: linear-gradient(180deg, rgba(168,22,15, 0.92), rgba(30,10,5, 0.65));
  opacity: 0;
  z-index: 3;
  filter: blur(0.3px);
  transform-origin: top center;
}

.tl-node{
  position: relative;
  z-index: 1;
  flex: 0 0 auto;
  min-width: 150px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  cursor: pointer;
  padding: 0 12px;
}
/* The party avatar hangs BELOW the beam, joined to it by a short stem —
   never sitting on top of / overlapping the light itself. */
.tl-junction{
  width: 11px; height: 11px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 28%, #f2a5c0, var(--wine) 60%, var(--wine-deep) 100%);
  box-shadow: 0 0 8px rgba(255,59,48,0.85), 0 0 16px rgba(255,59,48,0.5);
}
.tl-connector{
  width: 2px;
  height: 18px;
  background: linear-gradient(to bottom, rgba(255,255,255,0.8), rgba(255,255,255,0.1));
}
.tl-dot-wrap{
  position: relative;
  width: 58px; height: 58px;
  display: flex; align-items: center; justify-content: center;
  margin-top: 14px;
  margin-bottom: 16px;
}
.tl-avatar{
  width: 48px; height: 48px;
  border-radius: 50%;
  overflow: hidden;
  border: 1.5px solid var(--line-strong);
  background: var(--bg-elev-2);
  transition: transform 300ms var(--ease-out), border-color 300ms var(--ease-out), box-shadow 300ms var(--ease-out);
}
.tl-avatar img, .tl-avatar .fallback{ width: 100%; height: 100%; object-fit: cover; }
.tl-avatar .fallback{
  background: radial-gradient(circle at 30% 20%, rgba(255,59,48,0.6), transparent 55%), linear-gradient(160deg, var(--wine-deep), var(--bg) 80%);
}
.tl-ring{
  position: absolute;
  inset: -5px;
  border-radius: 50%;
  border: 1px solid var(--wine-light);
  opacity: 0;
}
.tl-num{
  font-family: var(--font-display);
  font-size: 1.3rem;
  color: var(--cream-faint);
  opacity: 0.55;
  transition: color 250ms var(--ease-out), opacity 250ms var(--ease-out), text-shadow 250ms var(--ease-out);
}

@media (hover: hover) and (pointer: fine){
  .tl-node:hover .tl-avatar{ border-color: var(--line-strong); transform: scale(1.05); }
}
.tl-node.is-active .tl-avatar{
  border-color: var(--wine-light);
  transform: scale(1.12);
  box-shadow: 0 0 0 4px rgba(255,59,48,0.16);
}
.tl-node.is-active .tl-ring{ opacity: 1; animation: tl-pulse 2.4s ease-out infinite; }
.tl-node.is-active .tl-num{ color: var(--wine-light); opacity: 1; text-shadow: 0 0 16px rgba(255,59,48,0.5); }

@keyframes tl-pulse{
  0%{ transform: scale(1); opacity: 0.9; }
  100%{ transform: scale(1.9); opacity: 0; }
}
@media (prefers-reduced-motion: reduce){
  .tl-node.is-active .tl-ring{ animation: none; opacity: 0.6; }
}

/* ---------- Zoom overlay (the "mini zoom" scene) ---------- */
.gallery-zoom{
  position: fixed;
  inset: 0;
  z-index: 260;
  /* Fully opaque, not a translucent 0.95-alpha black: the page behind
     (nav, timeline, footer) was visibly bleeding through the overlay —
     an opaque backdrop guarantees it never can, regardless of what's
     stacked underneath. */
  background: #06040a;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px var(--gutter);
  opacity: 0;
  pointer-events: none;
  transition: opacity 380ms var(--ease-out);
}
.gallery-zoom.is-open{ opacity: 1; pointer-events: auto; }
.gallery-zoom-close{
  position: absolute;
  top: 24px; right: 24px;
  width: 44px; height: 44px;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  color: var(--cream);
  display: flex; align-items: center; justify-content: center;
  transition: background 250ms var(--ease-out), border-color 250ms var(--ease-out);
}
.gallery-zoom-close:hover{ background: var(--wine); border-color: var(--wine); }

.gallery-detail{
  width: 100%;
  max-width: 1000px;
  display: grid;
  grid-template-columns: minmax(240px, 380px) 1fr;
  gap: clamp(32px, 6vw, 72px);
  align-items: center;
  transform: scale(0.94);
  opacity: 0;
  transition: transform 380ms var(--ease-out), opacity 380ms var(--ease-out);
}
.gallery-zoom.is-open .gallery-detail{ transform: scale(1); opacity: 1; }
.detail-photo{
  position: relative;
  width: 100%;
  aspect-ratio: 4/5;
  border-radius: 14px;
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--bg-elev-2);
  box-shadow: 0 40px 70px -30px rgba(0,0,0,0.7);
  will-change: transform;
}
.detail-photo img, .detail-photo .fallback{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  transition: opacity 420ms var(--ease-out), transform 420ms var(--ease-out), filter 420ms var(--ease-out);
}
.detail-photo .fallback{
  background: radial-gradient(circle at 30% 20%, rgba(255,59,48,0.55), transparent 55%), linear-gradient(160deg, var(--wine-deep), var(--bg) 80%);
}
.detail-photo img.is-swapping{
  opacity: 0;
  transform: scale(0.93);
  filter: blur(14px);
}
.detail-photo .corner{
  position: absolute;
  width: 18px; height: 18px;
  border: 1.5px solid var(--wine-light);
  opacity: 0.85;
  transition: opacity 400ms var(--ease-out);
}
.detail-photo .corner.tl{ top: 10px; left: 10px; border-right: none; border-bottom: none; }
.detail-photo .corner.tr{ top: 10px; right: 10px; border-left: none; border-bottom: none; }
.detail-photo .corner.bl{ bottom: 10px; left: 10px; border-right: none; border-top: none; }
.detail-photo .corner.br{ bottom: 10px; right: 10px; border-left: none; border-top: none; }

.detail-info{ min-width: 0; }
.detail-eyebrow{ display: block; margin-bottom: 8px; }
.detail-date-row{
  display: flex;
  align-items: baseline;
  gap: 18px;
  transition: opacity 300ms var(--ease-out), transform 300ms var(--ease-out);
}
.detail-date-row.is-swapping{ opacity: 0; transform: translateY(8px); }
.detail-day{
  font-family: var(--font-display);
  font-size: clamp(3.4rem, 7vw, 6rem);
  line-height: 0.9;
  color: var(--cream);
}
.detail-daymeta{ display: flex; flex-direction: column; gap: 2px; }
.detail-month{
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.3rem;
  color: var(--wine-light);
}
.detail-year{ font-size: 0.78rem; letter-spacing: 0.1em; color: var(--cream-faint); }
.detail-title-link{
  margin-top: 20px;
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
  border-bottom: 1px solid transparent;
  transition: border-color 250ms var(--ease-out), gap 250ms var(--ease-out);
}
.detail-title-link .detail-title{ margin: 0; transition: color 250ms var(--ease-out); }
.detail-title-link .btn-arrow{ font-size: 0.9rem; color: var(--wine-light); transition: transform 250ms var(--ease-out); }
.detail-title-link:hover{ border-color: var(--wine-light); gap: 14px; }
.detail-title-link:hover .detail-title{ color: var(--wine-light); }
@media (hover: hover) and (pointer: fine){
  .detail-title-link:hover .btn-arrow{ transform: translateX(4px); }
}
.detail-meta{ margin-top: 10px; font-size: 0.9rem; }
.detail-meta:empty{ display: none; }
.detail-cta{ display: flex; width: max-content; margin-top: 24px; }
.detail-cta[hidden]{ display: none; }

@media (max-width: 760px){
  .tl-node{ min-width: 120px; }
  .gallery-detail{ grid-template-columns: 1fr; }
  .detail-photo{ width: min(320px, 100%); margin: 0 auto; }
}

/* ---------- Page hero (calendar/gallery inner pages) ---------- */
.page-hero{
  position: relative;
  overflow: hidden;
  padding: 170px var(--gutter) 60px;
  border-bottom: 1px solid var(--line);
  background: radial-gradient(ellipse 70% 50% at 50% 0%, rgba(216,40,30,0.22), transparent 65%);
}
.page-hero .hero-doodle{ color: var(--wine-light); }
.page-hero h1{ font-size: clamp(2.4rem, 6vw, 4.2rem); text-transform: uppercase; }
.page-hero p{ margin-top: 16px; max-width: 520px; }
.page-hero-compact{ padding-top: 120px; padding-bottom: 50px; }
/* Extra clearance so the (tall, 3/4 ratio) flyer cards don't end up
   scrolling in right behind the fixed nav — at the old spacing the card
   would tuck in under the nav logo after only ~150px of scroll, reading
   as the AL FALLO wordmark sitting drawn on top of the flyer. */
.page-hero-compact + section{ padding-top: clamp(56px, 8vw, 96px); }

/* ---------- 404 page ---------- */
.error-page{
  min-height: 100svh;
  display: flex;
  align-items: center;
  border-bottom: none;
}
.error-inner{ text-align: center; margin: 0 auto; }
.error-inner .eyebrow{ font-size: 1.1rem; letter-spacing: 0.3em; }
.error-inner h1{ margin-top: 14px; }
.error-inner p{ margin: 18px auto 0; }
.error-ctas{
  margin-top: 34px;
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  justify-content: center;
}

/* Smaller cards on the calendar page so the flyer is visible without much scrolling */
.calendar-events .event-card{ flex-basis: clamp(190px, 22vw, 240px); }

/* ---------- Calendar page ---------- */
.event-tags{
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 10px;
}
.event-tag{
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--rose);
  border: 1px solid var(--wine);
  border-radius: 999px;
  padding: 4px 12px;
  display: inline-block;
}

/* Event cards "dealt" into place — a bigger drop with a slight fanned
   tilt (alternating per card) instead of the site-wide generic reveal's
   plain fade-up, plus their tag pills popping in one after another once
   the card itself has landed. Builds on the existing [data-reveal] +
   --reveal-i stagger system (main.js) rather than a separate observer.

   The rotate+translateY lives on the inner .event-card-reveal wrapper,
   never on .event-card itself: .event-card is the flex item inside
   .carousel (overflow-x: auto), and a *rotated* flex item's scrollable
   overflow area is measured including that rotation — so for the ~350ms
   before reveal, the tilted card was wider than the track and briefly
   forced a horizontal scrollbar into existence, which then vanished
   once the card settled back to rotate(0). Animating the inner wrapper
   instead (clipped by .event-card's own overflow:hidden) keeps the
   card's own box perfectly static the whole time. */
.event-card[data-reveal]{ transform: none; }
.event-card[data-reveal].is-visible{ transform: none; }
.event-card-reveal{
  flex: 1;
  display: flex;
  flex-direction: column;
  transform: translateY(38px) rotate(-5deg);
  transition: transform 350ms var(--ease-out);
  transition-delay: calc(var(--reveal-i, 0) * 60ms);
}
.event-card:nth-child(even) .event-card-reveal{ transform: translateY(38px) rotate(5deg); }
.event-card.is-visible .event-card-reveal{ transform: translateY(0) rotate(0deg); }

.event-tag{ opacity: 0; transform: scale(0.4); }
@keyframes tag-pop{
  0%{ transform: scale(0.4); opacity: 0; }
  65%{ transform: scale(1.15); opacity: 1; }
  100%{ transform: scale(1); opacity: 1; }
}
.event-card.is-visible .event-tag{
  animation: tag-pop 420ms var(--ease-out) forwards;
  animation-delay: calc(var(--reveal-i, 0) * 60ms + 300ms);
}
.event-card.is-visible .event-tag:nth-child(2){ animation-delay: calc(var(--reveal-i, 0) * 60ms + 380ms); }
.event-card.is-visible .event-tag:nth-child(3){ animation-delay: calc(var(--reveal-i, 0) * 60ms + 460ms); }
@media (prefers-reduced-motion: reduce){
  .event-card-reveal{ transform: none; transition: none; }
  .event-tag{ opacity: 1; transform: none; animation: none; }
}
.empty-state{
  text-align: center;
  padding: 80px 20px;
  color: var(--cream-faint);
}
.empty-state h3{ color: var(--cream); margin-bottom: 10px; }

/* ---------- Gallery page ---------- */
.album-view{ display: none; }
.album-view.is-active{ display: block; }
.timeline-view{ display: none; }
.timeline-view.is-active{ display: block; }

.back-link{
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.85rem;
  color: var(--cream-dim);
  margin-bottom: 30px;
}
.back-link:hover{ color: var(--cream); }

.photo-grid{
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 16px;
}
.photo-tile{
  position: relative;
  aspect-ratio: 1;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--bg-elev-2);
  cursor: pointer;
}
.photo-tile img{ width: 100%; height: 100%; object-fit: cover; transition: transform .5s var(--ease); }
.photo-tile .dl{
  position: absolute;
  top: 10px; right: 10px;
  width: 34px; height: 34px;
  border-radius: 50%;
  background: rgba(11,6,5,0.72);
  backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center;
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity .3s var(--ease), transform .3s var(--ease), background .3s var(--ease);
}
@media (hover: hover) and (pointer: fine){
  .photo-tile:hover img{ transform: scale(1.08); }
  .photo-tile:hover .dl{ opacity: 1; transform: translateY(0); }
}
/* Touch devices never fire :hover, so the download button needs to be
   reachable without it — show it plainly instead of hiding it forever. */
@media (hover: none), (pointer: coarse){
  .photo-tile .dl{ opacity: 1; transform: none; }
}
.photo-tile .dl:hover{ background: var(--wine); }

.album-actions{
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 16px;
  margin-bottom: 30px;
}

/* Lightbox */
.album-more{ text-align: center; margin-top: 36px; }
.album-more[hidden]{ display: none; }
.album-more p{ color: var(--cream-dim); margin: 0 0 14px; }

.lightbox{
  position: fixed; inset: 0;
  background: rgba(11,6,5,0.96);
  z-index: 300;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 40px;
}
.lightbox.is-open{ display: flex; }
.lightbox img{ max-width: 88vw; max-height: 82vh; object-fit: contain; border-radius: 4px; }
.lightbox-caption{
  position: absolute;
  left: 50%; bottom: 28px;
  transform: translateX(-50%);
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1rem;
  color: var(--cream-dim);
}
.lightbox-caption:empty{ display: none; }
.lightbox-close, .lightbox-prev, .lightbox-next, .lightbox-dl{
  position: absolute;
  color: var(--cream);
  width: 44px; height: 44px;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  display: flex; align-items: center; justify-content: center;
  transition: background .3s var(--ease), border-color .3s var(--ease);
}
.lightbox-close:hover, .lightbox-prev:hover, .lightbox-next:hover, .lightbox-dl:hover{
  background: var(--wine); border-color: var(--wine);
}
.lightbox-close{ top: 24px; right: 24px; }
.lightbox-dl{ top: 24px; right: 80px; }
.lightbox-prev{ left: 24px; top: 50%; transform: translateY(-50%); }
.lightbox-next{ right: 24px; top: 50%; transform: translateY(-50%); }

/* ---------- Responsive ---------- */
@media (max-width: 900px){
  .nav-links, .nav-cta .btn-ghost{ display: none; }
  .nav-burger{ display: flex; }
  .about-grid{ grid-template-columns: 1fr; }
  .about-collage{ order: -1; aspect-ratio: 16/10; }
  .footer-top{ flex-direction: column; }
}
