/* ============================================================
   Newport 2027 — Madison & Charles
   Direction: grand estate at dusk. Candlelight falling off into
   near-black green, inside gilt boiserie panels borrowed from
   Rosecliff's own ballroom walls.
   ============================================================ */

:root{
  /* ground — Wuthering Heights night */
  --ink:        #0C110B;
  --ink-2:      #101711;
  --forest:     #16201A;
  --moss:       #2D3B32;
  /* Spring Court, living */
  --verdant:    #5A7052;
  --verdant-lo: #3D4E39;
  /* light */
  --ivory:      #F1EADC;
  --paper:      #F8F3E6;
  --ivory-dim:  #C3BCAC;
  --ivory-far:  #8A8677;
  /* Gatsby */
  --gilt:       #C9A961;
  --gilt-hi:    #E8D9A8;
  --gilt-lo:    #8A733C;
  /* blood */
  --oxblood:    #5A1A20;

  --f-display: 'Cormorant Garamond', Georgia, serif;
  --f-body:    'EB Garamond', Georgia, serif;
  --f-script:  'Italianno', cursive;

  --frame: clamp(14px, 2.2vw, 60px);
  --gutter: clamp(20px, 4.4vw, 150px);
  --rule: rgba(201,169,97,.30);
  --rule-soft: rgba(201,169,97,.14);

  --ease: cubic-bezier(.22,.61,.36,1);
}

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

/* No scroll-behavior here on purpose: setting it globally animates every
   scroll including the user's own wheel, which reads as the page moving by
   itself. Programmatic slide nav passes behavior:'smooth' per call instead. */

/* One fluid root size drives the whole deck. Everything below is in rem, so a
   bigger window scales type, cards and spacing together instead of leaving a
   fixed-size block of content adrift in a large frame. */
html{ font-size:clamp(15px, calc(0.25vw + 0.6vh + 7.6px), 30px); }
/* svh where supported so a mobile URL bar appearing doesn't resize the type */
@supports (height:1svh){
  html{ font-size:clamp(15px, calc(0.25vw + 0.6svh + 7.6px), 30px); }
}

body{
  margin:0;
  background:var(--ink);
  color:var(--ivory);
  font-family:var(--f-body);
  font-size:1rem;
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}

/* ---------- ambient: grain + candle ---------- */
.grain{
  position:fixed; inset:0; z-index:60; pointer-events:none;
  opacity:.32; mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='.42'/%3E%3C/svg%3E");
}
.vignette{
  position:fixed; inset:0; z-index:2; pointer-events:none;
  background:radial-gradient(120% 90% at 50% 42%, transparent 30%, rgba(4,7,4,.55) 78%, rgba(3,5,3,.9) 100%);
}

/* ---------- slide shell ---------- */
/* Deliberately no scroll-snap. It was previously declared on .deck, which is
   not the scroll container (<html> is), so the viewport got yanked around. */
.slide{
  position:relative;
  min-height:100svh;
  display:flex; align-items:start;
  padding:calc(var(--frame) + var(--gutter)*.55) var(--gutter);
  /* Slides are top-aligned, so this clearance is the only thing above a title.
     (It once also had to clear the variant toggle, removed 2026-08-05 — which
     is why it could drop from 3.5rem to 1.6rem.) The bottom clearance matches
     the top exactly, so content ends the same distance from the frame that
     the title starts from it. */
  padding-block-start:max(calc(var(--frame) + var(--gutter)*.28), calc(var(--frame) + 1.6rem));
  padding-block-end:max(calc(var(--frame) + var(--gutter)*.28), calc(var(--frame) + 1.6rem));
  isolation:isolate;
  overflow:hidden;
}
.slide__glow{
  position:absolute; inset:-20%; z-index:-2; pointer-events:none;
  background:radial-gradient(46% 52% at var(--gx,38%) var(--gy,46%),
              rgba(201,169,97,.20) 0%,
              rgba(90,112,82,.10) 34%,
              transparent 68%);
}
/* Full-bleed: content fills the viewport width, held off the edges by the
   gutter and the boiserie frame rather than by a fixed max-width. */
.slide__wrap{ width:100%; margin-inline:auto; position:relative; z-index:1; }

/* boiserie — the signature. Gilt panel with mitred corners. */
.slide::before{
  content:''; position:absolute; inset:var(--frame); z-index:-1; pointer-events:none;
  border:1px solid var(--rule-soft);
}
.slide::after{
  content:''; position:absolute; z-index:-1; pointer-events:none;
  inset:calc(var(--frame) + 9px);
  border:1px solid transparent;
  border-image:linear-gradient(140deg,
      rgba(201,169,97,.42), rgba(201,169,97,.05) 32%,
      rgba(201,169,97,.05) 68%, rgba(201,169,97,.34)) 1;
}
.corner{
  position:absolute; width:19px; height:19px; z-index:0; pointer-events:none;
  border:0 solid var(--gilt); opacity:.62;
}
.corner--tl{ top:calc(var(--frame) + 9px); left:calc(var(--frame) + 9px); border-top-width:1px; border-left-width:1px; }
.corner--tr{ top:calc(var(--frame) + 9px); right:calc(var(--frame) + 9px); border-top-width:1px; border-right-width:1px; }
.corner--bl{ bottom:calc(var(--frame) + 9px); left:calc(var(--frame) + 9px); border-bottom-width:1px; border-left-width:1px; }
.corner--br{ bottom:calc(var(--frame) + 9px); right:calc(var(--frame) + 9px); border-bottom-width:1px; border-right-width:1px; }

/* ---------- type ---------- */
.eyebrow{
  font-family:var(--f-body);
  font-size:.72rem; letter-spacing:.34em; text-transform:uppercase;
  color:var(--gilt); margin:0 0 1.35rem;
  display:flex; align-items:center; gap:.9rem;
}
.eyebrow::after{ content:''; flex:1; height:1px; background:linear-gradient(90deg,var(--rule),transparent); }
.eyebrow--plain::after{ display:none; }

h1,h2,h3{ font-family:var(--f-display); font-weight:300; margin:0; line-height:1.04; letter-spacing:-.012em; }
h1{ font-size:clamp(2.9rem,7.6vw,7.4rem); }
h2{ font-size:clamp(2.1rem,4.3vw,4.05rem); }
h3{ font-size:clamp(1.15rem,1.55vw,1.5rem); font-weight:400; letter-spacing:0; }

/* Italianno is a connected script: its joins are drawn into the glyph metrics,
   so any tracking at all breaks them — positive opens gaps, negative overlaps.
   Leave it at the font's designed spacing. */
.script{ font-family:var(--f-script); font-weight:400; letter-spacing:normal; line-height:.92; color:var(--gilt-hi); }
.lede{ font-size:clamp(1.05rem,1.45vw,1.42rem); color:var(--ivory-dim); max-width:62ch; line-height:1.62; }
.lede em{ color:var(--ivory); font-style:italic; }
.muted{ color:var(--ivory-far); }
.tiny{ font-size:.76rem; letter-spacing:.15em; text-transform:uppercase; color:var(--ivory-far); }

.rule{ height:1px; background:linear-gradient(90deg,var(--rule),var(--rule-soft) 45%,transparent); border:0; margin:2.2rem 0; }

/* ---------- cover ---------- */
.cover{ text-align:center; align-items:center; }
.cover__names{ font-size:clamp(4.4rem,15vw,15rem); margin:0; }
.cover__amp{ display:block; font-size:.42em; color:var(--gilt); margin:-.16em 0; opacity:.8; }
.cover__meta{
  margin-top:2.4rem; font-size:clamp(.78rem,1.02vw,.95rem);
  letter-spacing:.42em; text-transform:uppercase; color:var(--ivory-dim);
}
.cover__venue{ margin-top:.85rem; font-family:var(--f-display); font-size:clamp(1rem,1.5vw,1.35rem); letter-spacing:.2em; color:var(--gilt); text-transform:uppercase; }
.cover__hint{ position:absolute; bottom:calc(var(--frame) + 34px); left:0; right:0; text-align:center; }

/* ---------- welcome (slide 2) ---------- */
/* Full-bleed image behind a slide: under the glow (-2) and the boiserie frame
   (-1) inside the slide's own stacking context, with a scrim so the script
   stays legible over a busy collage. */
.slide__bg{ position:absolute; inset:0; z-index:-3; }
.slide__bg img{ width:100%; height:100%; object-fit:cover; display:block; }
.slide__bg::after{
  content:''; position:absolute; inset:0;
  background:radial-gradient(72% 62% at 50% 46%, rgba(12,17,11,.30) 0%, rgba(12,17,11,.66) 100%);
}
.welcome__title{ font-size:clamp(3.4rem,11vw,11rem); margin:0; }

/* ---------- generic layout helpers ---------- */
.grid{ display:grid; gap:clamp(1.4rem,2.6vw,3.2rem); }
.grid--2{ grid-template-columns:repeat(2,1fr); }
.grid--3{ grid-template-columns:repeat(3,1fr); }
.grid--split{ grid-template-columns:minmax(0,1.85fr) minmax(0,1fr); align-items:start; }
/* Text column beside a full-height portrait image. */
.grid--tall{ grid-template-columns:minmax(0,1fr) minmax(0,.55fr); align-items:stretch; }
.grid--tall > .stack{ align-self:start; }
/* The weekend slide splits evenly — its timeline is wide and the sea image is
   the point, not a garnish. */
.grid--tall--half{ grid-template-columns:minmax(0,1fr) minmax(0,1fr); }

/* Slightly smaller body type in the left column of a split slide, which buys
   the height the taller images below need. The venue's 50/50 text column runs
   the deck's longest copy beside a full-height carousel, so it compacts a
   step further — the alternative is the hardstop clipping on short windows. */
.grid--split > :first-child .lede{ font-size:clamp(.96rem,1.24vw,1.2rem); line-height:1.56; }
.grid--half > :first-child .lede{ font-size:clamp(.9rem,1.12vw,1.1rem); line-height:1.5; }
.grid--half > :first-child .dist li{ padding:.22rem 0; font-size:.84rem; }
.grid--half > :first-child .hardstop{ margin-top:1.1rem; padding:.75rem 1rem; font-size:.88rem; }
/* Two even halves that stretch to the slide bottom (venue, map). The single
   row is minmax(0,1fr) so a full-height child reads its height from the row
   rather than feeding its content size back into it. */
.grid--half{ grid-template-columns:repeat(2,minmax(0,1fr)); grid-template-rows:minmax(0,1fr); align-items:stretch; }
.grid--map{ grid-template-columns:repeat(2,minmax(0,1fr)); grid-template-rows:minmax(0,1fr); align-items:stretch; gap:clamp(1.4rem,3vw,3.4rem); }
.stack{ display:flex; flex-direction:column; gap:1.1rem; }

/* ---------- option cards ---------- */
.opts{ display:grid; grid-template-columns:repeat(3,1fr); gap:clamp(.9rem,1.5vw,1.6rem); margin-top:2rem; }
.opt{
  position:relative;
  /* --padx/--padt are read back by .opt__img to bleed the photo to the card edge */
  --padx:1.4rem; --padt:1.5rem;
  padding:var(--padt) var(--padx) 1.6rem;
  background:linear-gradient(168deg, rgba(45,59,50,.42), rgba(12,17,11,.24));
  border:1px solid rgba(201,169,97,.13);
  transition:border-color .45s var(--ease), transform .45s var(--ease), background .45s var(--ease);
}
.opt:hover,.opt:focus-within{
  border-color:rgba(201,169,97,.42);
  background:linear-gradient(168deg, rgba(58,76,63,.5), rgba(12,17,11,.3));
  transform:translateY(-3px);
}
.opt__name{ font-family:var(--f-display); font-size:1.32rem; font-weight:400; color:var(--paper); margin:0 0 .28rem; line-height:1.16; }
.opt__meta{ font-size:.71rem; letter-spacing:.13em; text-transform:uppercase; color:var(--gilt-lo); margin-bottom:.62rem; }
.opt__body{ font-size:.94rem; color:var(--ivory-dim); margin:0; line-height:1.58; }
.opt--pick{ border-color:rgba(201,169,97,.46); background:linear-gradient(168deg, rgba(90,112,82,.3), rgba(12,17,11,.34)); }
.opt--pick .opt__name{ color:var(--gilt-hi); }
.opt__flag{
  position:absolute; top:-1px; right:-1px;
  font-size:.62rem; letter-spacing:.2em; text-transform:uppercase;
  padding:.34rem .6rem; background:var(--gilt); color:var(--ink); font-weight:600;
}

/* ---------- day slides ---------- */
.day__head{ display:flex; align-items:baseline; gap:1.2rem; flex-wrap:wrap; }
.day__name{ font-family:var(--f-display); font-size:clamp(2.2rem,4.4vw,4rem); font-weight:300; line-height:1; }
.day__date{ font-size:.82rem; letter-spacing:.3em; text-transform:uppercase; color:var(--gilt); }
.day__title{ font-family:var(--f-display); font-size:clamp(1.1rem,1.7vw,1.7rem); font-weight:300; color:var(--ivory-dim); font-style:italic; }
/* No max-width: a measure cap just wraps the note into two or three short
   lines and crowds the header — let it run the slide. Sized to match the
   .lede on the accommodations slide, with a clear buffer before the columns
   start; the flexible card images pay for the extra height. */
.day__note{
  margin:.6rem 0 1.4rem; color:var(--ivory-dim);
  font-size:clamp(1.05rem,1.45vw,1.42rem); line-height:1.62;
}
.day--wedding .day__name{ color:var(--gilt-hi); }

/* three time columns, two options each */
/* Columns stretch and the cards share the height between them, so a day slide
   fills its frame instead of stacking content at the top. */
.parts{ display:grid; grid-template-columns:repeat(3,1fr); gap:clamp(.8rem,1.6vw,1.9rem); margin-top:1.1rem; align-items:stretch; }
.part{ display:flex; flex-direction:column; gap:.6rem; min-width:0; }
.part .opt{ flex:1 1 0; }
.part__h{
  font-family:var(--f-body); font-size:.7rem; letter-spacing:.28em; text-transform:uppercase;
  color:var(--gilt); margin:0 0 .1rem; padding-bottom:.6rem;
  border-bottom:1px solid var(--rule-soft);
}
.parts--2{ grid-template-columns:repeat(2,1fr); }
.parts .opt{ --padx:.9rem; --padt:.9rem; padding:var(--padt) var(--padx) .92rem; }
.parts .opt__name{ font-size:1.06rem; }
.parts .opt__body{ font-size:.82rem; line-height:1.46; }

.hardstop{
  margin-top:1.5rem; padding:1rem 1.2rem;
  border-left:2px solid var(--oxblood);
  background:linear-gradient(90deg, rgba(90,26,32,.24), transparent 80%);
  font-size:.94rem; color:var(--ivory-dim);
}
.hardstop strong{ color:var(--paper); font-weight:500; }

/* ---------- timeline ---------- */
.tl{ display:grid; gap:0; margin-top:1.9rem; border-top:1px solid var(--rule-soft); }
.tl__row{
  display:grid; grid-template-columns:clamp(96px,11vw,160px) 1fr auto;
  gap:clamp(1rem,2.4vw,2.6rem); align-items:baseline;
  padding:1.05rem 0; border-bottom:1px solid var(--rule-soft);
  transition:background .4s var(--ease), padding-left .4s var(--ease);
}
.tl__row:hover{ background:linear-gradient(90deg, rgba(90,112,82,.15), transparent 70%); padding-left:.7rem; }
.tl__day{ font-family:var(--f-display); font-size:1.28rem; color:var(--paper); }
.tl__what{ color:var(--ivory-dim); }
.tl__tag{ font-size:.68rem; letter-spacing:.2em; text-transform:uppercase; color:var(--gilt-lo); white-space:nowrap; }
/* The wedding row is marked by gilt text alone — a permanent background here
   read as "Friday is always highlighted" against the cycling row bar. */
.tl__row--wedding .tl__day,.tl__row--wedding .tl__what{ color:var(--gilt-hi); }
/* the row whose photo is currently showing — driven by the auto-cycle */
.tl__row.is-on{ background:linear-gradient(90deg, rgba(201,169,97,.18), transparent 72%); padding-left:.7rem; }

/* ---------- map ---------- */
/* The map panel fills its half to the slide bottom. The SVG keeps its own
   aspect and sits centred; the panel extends the same water behind it, so the
   whole column reads as one full-height chart rather than a letterboxed image.
   The gilt frame moved here from the SVG's own <rect> for the same reason. */
.mapwrap{
  position:relative; height:100%; min-height:0;
  display:flex; align-items:center;
  background:#03080A; border:1px solid rgba(201,169,97,.32);
}
.map{ width:100%; height:auto; display:block; overflow:visible; }
/* Water sits clearly darker than the page ground; land clearly lighter, with a
   gilt hairline at the shore so the edge reads at any size. */
.map__water{ fill:#03080A; }
.map__land path{
  fill:url(#landglow);
  stroke:rgba(201,169,97,.38); stroke-width:1;
  stroke-linejoin:round; vector-effect:non-scaling-stroke;
}
.map__ring{ fill:none; stroke:rgba(201,169,97,.24); stroke-dasharray:3 7; }
.pin__leader{ stroke:rgba(201,169,97,.42); stroke-width:.8; fill:none; }
.map__ringlabel{ font-family:var(--f-body); font-size:12px; fill:var(--gilt-lo); letter-spacing:.16em; text-transform:uppercase; }
.pin{ cursor:pointer; }
.pin__dot{ fill:var(--ivory-dim); transition:r .3s var(--ease), fill .3s var(--ease); }
.pin__halo{ fill:none; stroke:var(--gilt); stroke-width:1; opacity:0; transition:opacity .3s var(--ease), r .3s var(--ease); }
.pin__lbl{ font-family:var(--f-body); font-size:13px; fill:var(--ivory-dim); letter-spacing:.04em; transition:fill .3s var(--ease); }
.pin--host .pin__dot{ fill:var(--gilt); r:6; }
.pin--host .pin__lbl{ fill:var(--paper); }
.pin--wedding .pin__dot{ fill:var(--gilt-hi); }
.pin--stay .pin__dot{ fill:var(--verdant); }
.pin:hover .pin__dot,.pin.is-on .pin__dot{ fill:var(--gilt-hi); r:8; }
.pin:hover .pin__halo,.pin.is-on .pin__halo{ opacity:1; r:15; }
.pin:hover .pin__lbl,.pin.is-on .pin__lbl{ fill:var(--paper); }
.pin.is-dim{ opacity:.2; }
.beyond{ font-family:var(--f-body); font-size:12.5px; fill:var(--ivory-far); }
.beyond__n{ fill:var(--ivory-dim); }

.maplegend{ display:flex; gap:1.6rem; flex-wrap:wrap; margin-top:1.4rem; }
.legkey{ display:flex; align-items:center; gap:.5rem; font-size:.78rem; letter-spacing:.13em; text-transform:uppercase; color:var(--ivory-far); background:none; border:0; cursor:pointer; padding:.3rem 0; font-family:var(--f-body); }
.legkey::before{ content:''; width:9px; height:9px; border-radius:50%; background:var(--c,var(--ivory-dim)); }
.legkey[aria-pressed="true"]{ color:var(--paper); }
.legkey[aria-pressed="false"]{ opacity:.42; }

.mapinfo{ min-height:4.2rem; padding-top:.4rem; }
.mapinfo__n{ font-family:var(--f-display); font-size:1.5rem; color:var(--gilt-hi); }
.mapinfo__m{ color:var(--ivory-dim); font-size:.95rem; }

.dist{ list-style:none; margin:1.6rem 0 0; padding:0; }
.dist li{ display:flex; align-items:baseline; gap:.8rem; padding:.3rem 0; border-bottom:1px solid var(--rule-soft); font-size:.88rem; }
.dist span:first-child{ color:var(--ivory-dim); }
.dist .d{ margin-left:auto; color:var(--gilt); font-variant-numeric:tabular-nums; white-space:nowrap; }
.dist .w{ color:var(--ivory-far); font-size:.82rem; white-space:nowrap; }

/* ---------- Newport chronology ---------- */
/* Four eras read left to right like the day slides read morning to evening.
   The columns stretch and the fact lists are pushed to a common baseline with
   margin-top:auto, so unequal prose lengths don't leave the row ragged. */
.eras{
  display:grid; grid-template-columns:repeat(4,1fr);
  gap:clamp(1rem,2.2vw,2.4rem); margin-top:1.2rem; align-items:stretch;
}
.era{ display:flex; flex-direction:column; min-width:0; padding-top:.9rem; border-top:1px solid var(--rule); }
.era__yr{
  font-family:var(--f-display); font-weight:300; line-height:1;
  font-size:clamp(1.7rem,2.5vw,2.5rem); color:var(--gilt);
  font-variant-numeric:tabular-nums;
}
.era__n{
  font-family:var(--f-display); font-size:clamp(1rem,1.3vw,1.28rem);
  font-weight:400; font-style:italic; color:var(--ivory-dim);
  margin:.22rem 0 .75rem; letter-spacing:0;
}
.era__b{ font-size:.89rem; line-height:1.5; color:var(--ivory-dim); margin:0; }
/* Overrides .dist's top margin — this block must stay after .dist in the file. */
.era__f{ margin-top:auto; padding-top:.8rem; }
.era__tie{ margin:.65rem 0 0; font-size:.8rem; line-height:1.48; color:var(--gilt-lo); }


/* ---------- lodging ---------- */
/* Each tier is a bordered card like the day-slide options — the gilt frame
   lives on the card, where no image mask can fade it out. --padx/--padt feed
   the shared .tier__img bleed math. */
.tier{
  --padx:1.05rem; --padt:0px;
  padding:0 var(--padx) 1.05rem;
  border:1px solid rgba(201,169,97,.32);
  background:linear-gradient(168deg, rgba(45,59,50,.32), rgba(12,17,11,.2));
}
.tier__h{ display:flex; align-items:baseline; gap:1rem; margin-bottom:.9rem; }
.tier__l{ font-family:var(--f-display); font-size:1.6rem; color:var(--gilt-hi); white-space:nowrap; }
.tier__n{ font-size:.74rem; letter-spacing:.16em; text-transform:uppercase; color:var(--ivory-far); }
.place{ margin-bottom:1rem; }
.place__n{ font-family:var(--f-display); font-size:1.16rem; color:var(--paper); }
.place__m{ font-size:.72rem; letter-spacing:.13em; text-transform:uppercase; color:var(--gilt-lo); margin:.15rem 0 .45rem; }
.place__b{ font-size:.84rem; color:var(--ivory-dim); margin:0; line-height:1.46; }

/* ---------- asks ---------- */
.ask{ display:grid; grid-template-columns:auto 1fr; gap:1.4rem; align-items:start; padding:1.5rem 0; border-bottom:1px solid var(--rule-soft); }
.ask__i{ font-family:var(--f-display); font-size:2.4rem; color:var(--gilt); line-height:.9; opacity:.55; }
.ask__n{ font-family:var(--f-display); font-size:1.55rem; color:var(--paper); }
.ask__b{ color:var(--ivory-dim); margin:.3rem 0 0; }

/* ---------- guest menu ---------- */
.menu{ columns:3; column-gap:clamp(1.6rem,3vw,3.4rem); margin-top:1.8rem; }
.menu__g{ break-inside:avoid; margin-bottom:1.6rem; }
.menu__h{ font-size:.72rem; letter-spacing:.22em; text-transform:uppercase; color:var(--gilt); margin-bottom:.55rem; }
.menu ul{ list-style:none; margin:0; padding:0; }
.menu li{ color:var(--ivory-dim); padding:.2rem 0; font-size:.94rem; }
.menu li b{ color:var(--paper); font-weight:400; }

/* ---------- chrome: nav, toggle, progress ---------- */
.chrome{ position:fixed; z-index:50; }
.rail{ right:calc(var(--frame) + 12px); top:50%; transform:translateY(-50%); display:flex; flex-direction:column; gap:.62rem; }
.rail button{
  width:8px; height:8px; padding:0; border-radius:50%; cursor:pointer;
  background:transparent; border:1px solid rgba(201,169,97,.42);
  transition:background .3s var(--ease), transform .3s var(--ease), border-color .3s;
}
.rail button:hover{ transform:scale(1.5); border-color:var(--gilt-hi); }
.rail button[aria-current="true"]{ background:var(--gilt); border-color:var(--gilt); transform:scale(1.32); }

.pagenav{ display:none; }

.progress{ left:0; right:0; top:0; height:2px; background:transparent; }
.progress__b{ height:100%; width:0; background:linear-gradient(90deg,var(--gilt-lo),var(--gilt-hi)); transition:width .25s linear; }

.counter{ right:calc(var(--frame) + 14px); bottom:calc(var(--frame) + 14px); font-size:.7rem; letter-spacing:.2em; color:var(--ivory-far); font-variant-numeric:tabular-nums; }
.counter b{ color:var(--gilt); font-weight:400; }

.keyhint{ font-size:.68rem; letter-spacing:.24em; text-transform:uppercase; color:var(--ivory-far); }
.keyhint kbd{ font-family:var(--f-body); border:1px solid var(--rule); padding:.1rem .38rem; margin:0 .1rem; color:var(--gilt); }

/* ---------- reveal ---------- */
.rv{ opacity:0; transform:translateY(18px); transition:opacity .85s var(--ease), transform .85s var(--ease); }
.slide.is-in .rv{ opacity:1; transform:none; }
.slide.is-in .rv:nth-child(2){ transition-delay:.07s; }
.slide.is-in .rv:nth-child(3){ transition-delay:.14s; }
.slide.is-in .rv:nth-child(4){ transition-delay:.21s; }
.slide.is-in .rv:nth-child(5){ transition-delay:.28s; }

@keyframes flicker{
  0%,100%{ opacity:1 }
  42%{ opacity:.93 }
  47%{ opacity:1.0 }
  63%{ opacity:.9 }
  68%{ opacity:.99 }
}
.slide__glow{ animation:flicker 7.5s ease-in-out infinite; }

/* ---------- responsive ---------- */
@media (max-width:1080px){
  .opts,.parts,.parts--2{ grid-template-columns:1fr; }
  .part{ margin-bottom:1.4rem; }
  .grid--3,.grid--2,.grid--split,.grid--half,.grid--map{ grid-template-columns:1fr; grid-template-rows:auto; }
  .carousel{ height:clamp(300px, 60vw, 520px); }
  .mapwrap{ height:auto; }
  .eras{ grid-template-columns:repeat(2,1fr); row-gap:2.2rem; }
  .era__f{ margin-top:1rem; }
  .menu{ columns:2; }
  .slide{ min-height:auto; padding-block:clamp(4rem,13vw,7rem); }
  .rail{ display:none; }
}
@media (max-width:620px){
  .menu{ columns:1; }
  .eras{ grid-template-columns:1fr; }
  /* portrait-beside-text goes text-first, image below, at phone widths */
  .grid--tall{ grid-template-columns:1fr; }
  .tl__row{ grid-template-columns:1fr; gap:.25rem; }
  .tl__tag{ justify-self:start; }
  .counter{ display:none; }
}

@media (prefers-reduced-motion:reduce){
  .rv{ opacity:1; transform:none; transition:none; }
  .slide__glow{ animation:none; }
  .opt,.tl__row{ transition:none; }
}

/* ---------------------------------------------------------------
   Photography.

   Every file in img/ is pre-graded (crop + warm split-tone + auto exposure to a
   common mean luminance) so 44 photos from 44 photographers sit in one world.
   CSS only handles placement and the dissolve, never the colour — keeping the
   grade in the files means it looks the same before the CSS loads.

   Heights are aspect ratios so a band tracks its column width, then capped in
   svh: slides are a fixed 100svh, so an un-capped band would push a tall day
   slide into overflow on a short window.
   --------------------------------------------------------------- */
.imgbox{
  display:block; overflow:hidden; position:relative;
}
.imgbox img{
  display:block; width:100%; height:100%; object-fit:cover;
  transition:transform 1.2s var(--ease);
  will-change:transform;
}
/* Slow, small push-in on hover — the deck is candlelit and still, so the motion
   has to read as breathing rather than a UI effect. The wrapper clips it. */
.imgbox:hover img{ transform:scale(1.06); }
.opt:hover .imgbox img,
.opt:focus-within .imgbox img{ transform:scale(1.06); }

.opt__img,
.tier__img{
  width:calc(100% + var(--padx, 0px) * 2);
  margin:calc(var(--padt, 0px) * -1) calc(var(--padx, 0px) * -1) .85rem;
  aspect-ratio:16 / 9;
  max-height:clamp(110px, 24svh, 340px);
  opacity:.84;
  transition:opacity .45s var(--ease), filter .45s var(--ease);
  /* dissolve the lower edge into the card rather than ending on a hard line */
  -webkit-mask-image:linear-gradient(to bottom, #000 56%, rgba(0,0,0,.4) 86%, transparent 100%);
  mask-image:linear-gradient(to bottom, #000 56%, rgba(0,0,0,.4) 86%, transparent 100%);
}
.opt--pick .opt__img{ opacity:.95; }
.opt:hover .opt__img,
.opt:focus-within .opt__img{ opacity:1; filter:saturate(1.06); }

/* Lodging tier bands: no dissolve — the bottom fade was erasing the card
   border along with the image, which read as "no gold borders" (2026-08-05).
   Doubled height ("2x as tall"); the text flows below the band. */
.tier__img{
  margin-bottom:1.1rem;
  -webkit-mask-image:none; mask-image:none;
  border-bottom:1px solid var(--rule-soft);
  opacity:.9;
}
.tier__img{ aspect-ratio:21 / 16; max-height:clamp(160px, 28svh, 380px); }

/* Slide-level band, roughly double its old height now that slides are
   top-aligned and the space is there. */
.hero{
  width:100%;
  aspect-ratio:21 / 7;
  max-height:clamp(170px, 31svh, 470px);
  margin-top:clamp(1.1rem, 2.6vh, 2.2rem);
  opacity:.9;
  border-top:1px solid var(--rule-soft);
  -webkit-mask-image:linear-gradient(to bottom, #000 74%, transparent 100%);
  mask-image:linear-gradient(to bottom, #000 74%, transparent 100%);
}
.grid > .hero{ grid-column:1 / -1; }

/* Portrait image beside a column of text. The height must be definite: with
   height:100% on a stretched grid item the row height and the image height
   define each other, and the image's intrinsic 2:3 wins — it blew the slide out
   by 1500px. Size it against the viewport instead. */
.imgbox.tall{
  height:clamp(300px, 77svh, 1050px);
  align-self:start;
  opacity:.92;
  border:1px solid var(--rule-soft);
}

/* Mid-height windows (a 1512x900 laptop is the common case). Everything above
   was tuned at ~1118px tall; between 800 and 1000 the type only shrinks ~12%
   while the height drops 20%, so the taller images and the timeline need
   trimming or most slides spill. */
@media (max-height:1000px){
  .imgbox.tall{ height:clamp(280px, 66svh, 640px); }
  .opt__img{ max-height:clamp(90px, 18svh, 240px); }
  .hero{ max-height:clamp(120px, 24svh, 280px); }
  .tier__img{ max-height:clamp(132px, 23svh, 260px); }
  .tl{ margin-top:1.3rem; }
  .tl__row{ padding:.72rem 0; }
  .parts{ margin-top:.9rem; }
  h2{ font-size:clamp(1.9rem,3.5vw,3.1rem); }
  .eras{ margin-top:.9rem; }
  .era__b{ font-size:.84rem; }
  .ask{ padding:1.05rem 0; }
  .place{ margin-bottom:.75rem; }
}

/* Short windows. The deck already ran past 100svh here before the photography
   went in — at 1280x633 the cover overflows by 82px and the map by 184px, and
   neither carries an image. The bands can't fix that, but they must not make it
   worse, so they shrink and then drop out entirely. */
@media (max-height:800px){
  .opt__img{ max-height:clamp(30px, 6svh, 66px); margin-bottom:.55rem; }
  .hero{ max-height:clamp(70px, 13svh, 150px); margin-top:clamp(.7rem, 1.6vh, 1.2rem); }
  .imgbox.tall{ min-height:clamp(200px, 40svh, 420px); }
}
@media (max-height:640px){
  .opt__img{ display:none; }
}

/* ---------- venue links ----------
   The anchor sits on the name but stretches over the whole card, so the card is
   a single click target while the markup stays a heading containing a link
   rather than a link wrapping block content. */
.opt__link{ color:inherit; text-decoration:none; transition:color .3s var(--ease); }
.opt__link::after{ content:''; position:absolute; inset:0; z-index:3; }
.opt:has(.opt__link){ cursor:pointer; }
.opt:hover .opt__link,
.opt__link:focus-visible{ color:var(--gilt-hi); }

/* A quiet mark that this one goes somewhere — only on hover, so 40 cards don't
   sprout 40 icons at rest. */
.opt__link::before{
  content:'↗'; position:absolute; top:.5rem; right:.6rem; z-index:4;
  font-size:.72rem; color:var(--gilt); opacity:0; transform:translateY(2px);
  transition:opacity .3s var(--ease), transform .3s var(--ease);
}
.opt:hover .opt__link::before,
.opt__link:focus-visible::before{ opacity:.9; transform:none; }
/* the pick flag already occupies that corner */
.opt--pick .opt__link::before{ top:1.9rem; }

/* Lodging names are links too, but inline — no stretched hit area, because the
   tier column holds several places and one target would be ambiguous. */
.place__n .opt__link::after{ position:static; }
.place__n .opt__link::before{ display:none; }
.place__n .opt__link{ border-bottom:1px solid transparent; }
.place__n .opt__link:hover{ color:var(--gilt-hi); border-bottom-color:var(--rule); }

@media (prefers-reduced-motion:reduce){
  .opt__img{ transition:none; }
  .opt__link,.opt__link::before{ transition:none; }
  /* no push-in on hover for anyone who has asked motion to stop */
  .imgbox img{ transition:none; }
  .imgbox:hover img,
  .opt:hover .imgbox img,
  .opt:focus-within .imgbox img{ transform:none; }
}

:focus-visible{ outline:2px solid var(--gilt-hi); outline-offset:3px; }

/* ---------------------------------------------------------------
   Fill slides — 2026-08-05. Every slide with imagery across the
   bottom lets that imagery claim ALL the remaining height: the
   slide is 100svh, the wrap becomes a flex column, and the .fill
   child takes the leftover. min-height:0 below the fill point so
   an image's intrinsic size never feeds back into the layout (the
   trap that once blew a slide out by 1500px — see .imgbox.tall).
   Kept at the end of the file so these win the media-query caps.
   --------------------------------------------------------------- */
/* height, not min-height: a fill slide is a fixed 100svh box and its content
   is clamped inside — images flex to absorb the slack, and the .fill child
   clips as a last resort rather than painting past the boiserie frame. */
.slide--fill{ align-items:stretch; height:100svh; }
.slide--fill .slide__wrap{ display:flex; flex-direction:column; min-height:0; }
.slide--fill .slide__wrap > .fill{ flex:1 1 0; min-height:0; overflow:hidden; }

/* bottom band: grows to the frame, whatever the window height */
.slide--fill .imgbox.hero{
  flex:1 1 0; min-height:clamp(110px, 16svh, 999px);
  aspect-ratio:auto; max-height:none;
}

/* day slides: the columns grid is wrapped in the .fill reveal div */
.slide--fill .slide__wrap > .fill > .parts{ height:100%; margin-top:0; }
.slide--fill .parts{ min-height:0; }

/* Inside a clamped slide the card photo is the flexible part: it absorbs
   whatever height the copy doesn't need and shrinks first when space runs
   out. A single-card column therefore reads photo-first with its text
   settled at the bottom — the hosted-moment treatment — for free. */
.slide--fill .parts .opt{ display:flex; flex-direction:column; min-height:0; overflow:hidden; }
.slide--fill .parts .opt__img{
  flex:1 1 auto; height:auto; aspect-ratio:auto; max-height:none;
  min-height:52px;
}
.slide--fill .parts .opt__name{ margin-top:auto; }

/* split grids that stretch (airports): columns share the full height */
.slide--fill .grid--split.fill{ align-items:stretch; grid-template-rows:minmax(0,1fr); }

/* 50/50 halves: whichever column runs long clips at the padding edge
   instead of escaping the frame */
.slide--fill .grid--half > *,
.slide--fill .grid--map > *{ min-height:0; overflow:hidden; }

/* lodging tiers inside the clamped slide: the band is tall by default and
   gives height back to the copy when the window is short */
.slide--fill .grid--3.fill{ grid-template-rows:minmax(0,1fr); align-items:stretch; }
.slide--fill .tier{ display:flex; flex-direction:column; min-height:0; overflow:hidden; }
.slide--fill .tier__img{
  flex:0 1 auto; height:clamp(160px, 30svh, 420px); min-height:90px;
  aspect-ratio:auto; max-height:none;
}

/* portrait beside text at 50/50: the image reads its height from the row */
.slide--fill .grid--tall{ grid-template-rows:minmax(0,1fr); flex:1 1 0; }
.imgbox.tall.tall--fill{ height:auto; align-self:stretch; min-height:0; }

/* a stage slide's image pair: two frames sharing the columns area 50/50 */
.slide--fill .grid--duo{ grid-template-columns:repeat(2,minmax(0,1fr)); grid-template-rows:minmax(0,1fr); align-items:stretch; margin-top:1.1rem; }
.grid--duo .imgbox{ min-height:0; overflow:hidden; border:1px solid var(--rule-soft); opacity:.92; }

/* the weekend portrait is a stack — one image per day, hover/tap to switch */
.imgbox.tall .wk__im{
  position:absolute; inset:0; opacity:0;
  transition:opacity .8s var(--ease);
}
.imgbox.tall .wk__im.is-on{ opacity:1; }

/* Place links on the guest menu, and the venue title. Quiet at rest, gilt on
   hover — same voice as the option-card links. */
.menu a{
  color:inherit; text-decoration:none; border-bottom:1px solid transparent;
  transition:color .3s var(--ease), border-color .3s var(--ease);
}
.menu a:hover{ color:var(--gilt-hi); border-bottom-color:var(--rule); }
.h2link{ color:inherit; text-decoration:none; transition:color .3s var(--ease); }
.h2link:hover{ color:var(--gilt-hi); }

/* ---------- venue carousel ---------- */
.carousel{
  position:relative; height:100%; min-height:clamp(320px, 50svh, 2000px);
  overflow:hidden; border:1px solid var(--rule-soft); cursor:pointer;
}
.car__frame{
  position:absolute; inset:0; opacity:0;
  transition:opacity 1.1s var(--ease);
}
.car__frame.is-on{ opacity:1; }
.car__frame img{ width:100%; height:100%; object-fit:cover; display:block; }
.car__dots{
  position:absolute; left:50%; bottom:14px; transform:translateX(-50%);
  display:flex; gap:.55rem; z-index:2;
}
.car__dots button{
  width:8px; height:8px; padding:0; border-radius:50%; cursor:pointer;
  background:rgba(12,17,11,.55); border:1px solid rgba(201,169,97,.55);
}
.car__dots button[aria-current="true"]{ background:var(--gilt); }
@media (prefers-reduced-motion:reduce){ .car__frame,.wk__im{ transition:none; } }

/* Below the deck breakpoint slides scroll naturally again — the entire fill
   machinery switches off. The wrap returns to block flow (a flex column with
   basis-0 children and an auto height collapses them to nothing), every
   min-height:0 / overflow guard is lifted, and the flexible images return to
   honest aspect ratios. */
@media (max-width:1080px){
  .slide--fill{ height:auto; }
  .slide--fill .slide__wrap{ display:block; }
  .slide--fill .slide__wrap > .fill{ min-height:auto; overflow:visible; }
  .slide--fill .grid--tall{ grid-template-rows:auto; }
  /* the airports slide: this fill-grid kept its minmax(0,1fr) row template
     after stacking to one column, which piled its two halves onto each
     other on phones */
  .slide--fill .grid--split.fill{ grid-template-rows:auto; align-items:start; }
  /* flex:none is the load-bearing part: cards keep their desktop flex:1 1 0
     otherwise, and a basis-0 card in an auto-height column collapses to zero
     height — images and text then paint on top of each other. */
  .part .opt{ flex:none; }
  .slide--fill .parts .opt{ display:block; flex:none; min-height:auto; overflow:visible; }
  .slide--fill .parts .opt__img{ flex:none; aspect-ratio:16/9; height:auto; max-height:clamp(110px, 24svh, 340px); }
  .slide--fill .grid--3.fill{ grid-template-rows:auto; }
  .slide--fill .tier{ display:block; overflow:visible; }
  .slide--fill .tier__img{ height:auto; aspect-ratio:21/16; max-height:clamp(160px, 28svh, 380px); }
  .slide--fill .grid--half > *,
  .slide--fill .grid--map > *{ overflow:visible; }
  /* the desktop guard above also sets min-height:0 on the carousel, which
     zeroes it out once the grid rows go auto — every frame inside it is
     absolutely positioned, so the box collapsed and the photos "cycled"
     invisibly. Give it a real box again. */
  .slide--fill .grid--half > .carousel{
    height:clamp(300px, 70vw, 560px);
    min-height:clamp(300px, 70vw, 560px);
  }
  .slide--fill .imgbox.hero{ flex:none; height:clamp(160px, 30vw, 320px); }
  /* a stage slide IS its image — give it real presence once the fill flex
     is off, unlike the garnish bands above */
  .slide--fill .imgbox.hero--stage{ height:clamp(260px, 60vw, 520px); }
  .imgbox.tall.tall--fill{ height:clamp(300px, 66svh, 640px); }
  /* the stage-slide pair: auto rows again, so each frame needs a real height
     (a minmax(0,1fr) row in an auto-height wrap collapses to nothing) */
  .slide--fill .grid--duo{ grid-template-rows:auto; }
  .grid--duo .imgbox{ height:clamp(220px, 44vw, 420px); }
}
@media (max-width:620px){
  /* side-by-side halves go postage-stamp at phone widths — stack them */
  .slide--fill .grid--duo{ grid-template-columns:1fr; }
}

/* ---------------------------------------------------------------
   Phones are a deck, not a PDF (2026-08-05). Each slide is exactly
   one screen and a swipe snaps to the next; a slide whose content
   cannot fit one phone screen scrolls INSIDE its gilt frame first,
   and the following swipe turns the page. Snap lives on html — the
   real scroll container (the 2026-08-04 snap disaster was snap on
   a NON-scroll container) — and only at phone widths.
   --------------------------------------------------------------- */
@media (max-width:820px){
  /* The page itself does not scroll. Scroll-snap was tried first and lost:
     iOS re-snaps programmatic jumps (the pager "flashed" and went nowhere)
     and edge swipes by the clock / URL bar still turned pages. With no root
     scroller there is nothing for the URL bar or a swipe to grab — app.js
     toggles .is-page and only the active slide exists. */
  html, body{ height:100%; overflow:hidden; }
  .slide{
    display:none;
    height:100svh; min-height:100svh;
    padding:calc(var(--frame) + 1.1rem);
  }
  .slide.is-page{ display:flex; }
  /* no corner brackets on phones — scrolling content ran over them */
  .corner{ display:none; }
  .slide__wrap{
    height:100%; overflow-y:auto;
    -webkit-overflow-scrolling:touch;
    overscroll-behavior:contain;
    /* the last line has to clear the page buttons */
    padding-bottom:3.2rem;
  }
  .cover .slide__wrap{ height:auto; overflow:visible; }
  /* "↓ or scroll" is false here — there is no scroller, and the real ↓
     button sits right below it */
  .cover__hint{ display:none; }

  /* the page-turn buttons: bottom centre, above the home indicator */
  .pagenav{
    display:flex; gap:.55rem;
    left:50%; transform:translateX(-50%);
    bottom:max(10px, env(safe-area-inset-bottom));
  }
  .pagenav button{
    width:52px; height:36px; padding:0; cursor:pointer;
    font-family:var(--f-body); font-size:1.05rem; line-height:1;
    color:var(--gilt); background:rgba(10,15,10,.82);
    border:1px solid rgba(201,169,97,.42);
    backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
  }
  .pagenav button:active{ background:var(--gilt); color:var(--ink); }
  /* nothing further in that direction */
  .pagenav button:disabled{ opacity:.32; }
}

@media print{
  .chrome,.grain{ display:none; }
  .slide{ min-height:auto; page-break-after:always; }
}
