/* The Raven Restaurant.
   Hand-written CSS, no framework and no build step, so the client can host this
   as static files on their own domain.

   Palette sampled from the client's own photographs: the carved crest, the
   wordmark plaque and the facade at night. Warm near-blacks (#181800, #301800),
   aged gold (#906018) lifted for contrast, weathered bone.

   Theme is locked to dark. A brand decision, not a default: the room is
   candlelit, the crest is gold on dark wood, and every photograph in the set is
   a low-light interior. A light mode would misrepresent the place. */

:root {
  --ink:      #0e0c09;
  --ink-2:    #15120d;
  --ink-3:    #241e16;
  --bone:     #f0e9da;
  --bone-dim: #b6ac95;
  --gold:     #d2a44e;
  --gold-dp:  #8a6520;

  --r: 2px;
  --maxw: 1200px;
  --pad: 24px;
  --bar-h: 72px;
  --gutter: max(var(--pad), calc((100vw - var(--maxw)) / 2 + var(--pad)));

  --display: 'Bodoni Moda', 'Times New Roman', serif;
  --sans: 'Geist', system-ui, -apple-system, 'Segoe UI', sans-serif;

  --ease: cubic-bezier(.16, 1, .3, 1);
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--ink);
  color: var(--bone);
  font-family: var(--sans);
  font-weight: 300;
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

/* Deliberately NOT `overflow-x: hidden` on body or html. It turns the element
   into a scroll container, and `animation-timeline: view()` then resolves
   against it instead of the document, so every scroll-driven animation
   silently fails and snaps to its end state. Horizontal overflow is prevented
   structurally instead; tools/check-images.py and the layout audit confirm it. */

img { max-width: 100%; height: auto; display: block; }
a { color: var(--gold); text-underline-offset: 3px; }
a:focus-visible, button:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }

.wrap { max-width: var(--maxw); margin-inline: auto; padding-inline: var(--pad); }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--gold); color: var(--ink); padding: 12px 18px; font-weight: 500;
}
.skip:focus { left: 12px; top: 12px; }

/* ── Type ─────────────────────────────────────────────── */

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

h2 {
  font-size: clamp(2.1rem, 4.4vw, 3.5rem);
  line-height: 1.06;
  letter-spacing: -0.015em;
  margin-bottom: 18px;
}
h3 { font-size: 1.6rem; line-height: 1.15; }

p { margin: 0 0 18px; max-width: 62ch; }
.lead { color: var(--bone-dim); font-size: 1.08rem; }

.note {
  color: var(--bone-dim); font-size: 1rem;
  font-family: var(--display); font-style: italic;
  margin-top: 40px;
}

/* ── Header ───────────────────────────────────────────── */

.bar {
  position: fixed; inset: 0 0 auto 0; z-index: 50;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color .4s var(--ease), border-color .4s var(--ease), backdrop-filter .4s;
}
.bar.is-solid {
  background: rgba(14, 12, 9, .94);
  border-bottom-color: var(--ink-3);
}

.bar__in {
  max-width: var(--maxw); margin-inline: auto; padding-inline: var(--pad);
  height: var(--bar-h);
  display: flex; align-items: center; gap: 30px;
}

.mark { display: flex; align-items: center; gap: 10px; text-decoration: none; color: var(--bone); }
.mark__crest { width: 26px; height: 26px; fill: var(--gold); flex: none; }
.mark__wd { font-family: var(--display); font-size: 1.32rem; line-height: 1; white-space: nowrap; }
.mark__wd i { font-style: italic; font-size: .74em; color: var(--gold); margin-right: 1px; }

.nav { display: flex; gap: 4px; margin-left: auto; }
.nav a {
  position: relative;
  color: var(--bone-dim); text-decoration: none;
  font-size: .92rem; white-space: nowrap;
  padding: 6px 12px;
  transition: color .25s var(--ease);
}
.nav a::after {
  content: ""; position: absolute; left: 12px; right: 12px; bottom: 2px;
  height: 1px; background: var(--gold);
  scale: 0 1; transform-origin: left;
  transition: scale .38s var(--ease);
}
.nav a:hover { color: var(--bone); }
.nav a:hover::after, .nav a.is-active::after { scale: 1 1; }
.nav a.is-active { color: var(--bone); }

/* Specificity raised deliberately: `.btn` is declared after this block, so a
   plain `.bar__cta` loses the padding fight and the header button comes out
   the size of a hero CTA. */
.bar .bar__cta { padding: 10px 20px; font-size: .87rem; }

/* ── Buttons ──────────────────────────────────────────── */

.btn {
  display: inline-block; padding: 15px 32px; border-radius: var(--r);
  font-family: var(--sans); font-size: .95rem; font-weight: 500;
  letter-spacing: .005em;
  text-decoration: none; white-space: nowrap;
  transition: transform .2s var(--ease), background-color .25s var(--ease),
              border-color .25s var(--ease), color .25s var(--ease);
}
.btn:active { transform: translateY(1px); }
/* gold #d2a44e on ink -> 8.5:1, ink on gold -> the same. Both pass AA. */
.btn--gold { background: var(--gold); color: var(--ink); border: 1px solid var(--gold); }
.btn--gold:hover { background: #e4b96e; border-color: #e4b96e; }
.btn--line { background: transparent; color: var(--bone); border: 1px solid #453b2c; }
.btn--line:hover { border-color: var(--gold); color: var(--gold); }
.btn--block { display: block; text-align: center; }

/* Two document links. A stack of identical filled rectangles read as a form,
   not as a menu, so these are a typographic list with the format set apart. */
.links { list-style: none; margin: 26px 0 0; padding: 0; border-top: 1px solid var(--ink-3); }
.links a {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
  padding: 16px 2px;
  border-bottom: 1px solid var(--ink-3);
  color: var(--bone); text-decoration: none;
  font-family: var(--display); font-size: 1.24rem;
  transition: color .25s var(--ease), padding-left .3s var(--ease);
}
.links a span {
  font-family: var(--sans); font-size: .68rem; font-weight: 500;
  letter-spacing: .14em; color: var(--bone-dim);
  transition: color .25s var(--ease);
}
.links a:hover { color: var(--gold); padding-left: 9px; }
.links a:hover span { color: var(--gold); }

.row { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 34px; }

/* ── Sections ─────────────────────────────────────────── */

.sec { padding: clamp(84px, 10vw, 148px) 0; scroll-margin-top: var(--bar-h); }
#topo { scroll-margin-top: 0; }

/* ── Hero. The photograph runs off the right edge. ────── */

.hero {
  padding-top: calc(var(--bar-h) + clamp(28px, 5vw, 64px));
  padding-bottom: clamp(64px, 8vw, 110px);
  overflow: hidden;
}

.hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: clamp(32px, 5vw, 76px);
  align-items: center;
}

.hero__text { padding-left: var(--gutter); padding-right: 8px; }

.hero__h1 {
  font-size: clamp(3.6rem, 9.2vw, 7.4rem);
  line-height: .92;
  letter-spacing: -0.03em;
  margin-bottom: 26px;
}
.hero__h1 i {
  font-style: italic;
  font-size: .3em;
  color: var(--gold);
  letter-spacing: .04em;
  display: block;
  line-height: 1.25;      /* descender clearance for the italic 'the' */
  padding-bottom: 3px;
}

.hero__sub { color: var(--bone-dim); font-size: 1.14rem; max-width: 40ch; }

.hero__fig { margin: 0; }

/* The facade dissolves into the page instead of sitting in a frame.
   No border-radius here on purpose: a rounded corner and a feathered edge
   fight each other, and you end up seeing the corner precisely because the
   edge beside it has gone soft.

   The left edge fades into the dark half behind the headline, and the top and
   bottom fade so the photograph has no visible horizontal edge either.
   The right edge is left hard because it bleeds past
   the viewport and is never seen.

   The stops are an eased ramp, not a plain two-stop gradient. A linear alpha
   ramp over ~200px bands visibly on a dark photograph; weighting the stops
   Each element carries exactly ONE mask layer, deliberately. Stacking three
   gradients on one element and intersecting them makes the photograph vanish
   outright: the first layer composites against an initially transparent
   backdrop, so `intersect` yields nothing. Splitting the axes across the two
   elements sidesteps `mask-composite` entirely and behaves the same in every
   engine.

   The stops are an eased ramp, not a plain two-stop gradient. A linear alpha
   ramp over ~200px bands visibly on a dark photograph; weighting the stops
   toward the opaque end hides the transition. */
.hero__fig {
  /* Vertical feather, top and bottom, in a single gradient. */
  --fy: clamp(40px, 7vw, 120px);
  -webkit-mask-image: linear-gradient(to bottom,
    transparent 0,
    rgba(0,0,0,.32) calc(var(--fy) * .45),
    rgba(0,0,0,.80) calc(var(--fy) * .80),
    #000 var(--fy),
    #000 calc(100% - var(--fy)),
    rgba(0,0,0,.80) calc(100% - var(--fy) * .80),
    rgba(0,0,0,.32) calc(100% - var(--fy) * .45),
    transparent 100%);
  mask-image: linear-gradient(to bottom,
    transparent 0,
    rgba(0,0,0,.32) calc(var(--fy) * .45),
    rgba(0,0,0,.80) calc(var(--fy) * .80),
    #000 var(--fy),
    #000 calc(100% - var(--fy)),
    rgba(0,0,0,.80) calc(100% - var(--fy) * .80),
    rgba(0,0,0,.32) calc(100% - var(--fy) * .45),
    transparent 100%);
}

.hero__fig img {
  /* Horizontal feather into the text side. The right edge stays hard because
     it bleeds past the viewport and is never seen. */
  --fx: clamp(70px, 13vw, 210px);
  -webkit-mask-image: linear-gradient(to right,
    transparent 0,
    rgba(0,0,0,.10) calc(var(--fx) * .22),
    rgba(0,0,0,.34) calc(var(--fx) * .44),
    rgba(0,0,0,.68) calc(var(--fx) * .68),
    rgba(0,0,0,.90) calc(var(--fx) * .86),
    #000 var(--fx));
  mask-image: linear-gradient(to right,
    transparent 0,
    rgba(0,0,0,.10) calc(var(--fx) * .22),
    rgba(0,0,0,.34) calc(var(--fx) * .44),
    rgba(0,0,0,.68) calc(var(--fx) * .68),
    rgba(0,0,0,.90) calc(var(--fx) * .86),
    #000 var(--fx));
}

/* ── O nome. Full bleed, the stanza is the section. ───── */

.poem {
  position: relative;
  padding: clamp(96px, 11vw, 168px) 0 clamp(120px, 13vw, 200px);
  background: var(--ink-2);
  border-block: 1px solid var(--ink-3);
  scroll-margin-top: var(--bar-h);
  overflow: hidden;
}

.poem__bg {
  position: absolute; inset: 0 0 0 auto;
  width: 62%; height: 100%;
  object-fit: cover; object-position: 48% 46%;
  opacity: .64;
  -webkit-mask-image: linear-gradient(100deg, transparent 6%, #000 46%);
          mask-image: linear-gradient(100deg, transparent 6%, #000 46%);
}

.poem__in {
  position: relative; z-index: 2;
  padding-left: var(--gutter); padding-right: var(--pad);
  /* gutter + a fixed measure. Capping the whole box instead would make the text
     column narrower the wider the window got, because the gutter grows with it. */
  max-width: calc(var(--gutter) + 620px);
}

.poem__h2 { margin-bottom: 16px; }
.poem__lead { color: var(--bone-dim); max-width: 42ch; }

.stanza {
  margin: 34px 0 16px;
  font-family: var(--display);
  /* capped so every verse line fits on one line: the stagger only reads as
     verse if the browser is not also wrapping mid-line */
  font-size: clamp(1.35rem, 2.6vw, 2.15rem);
  font-style: italic;
  line-height: 1.38;
  letter-spacing: -0.012em;
  color: var(--bone);
}
.ln--em { color: var(--gold); }
.stanza__cite { display: block; font-size: .84rem; color: var(--bone-dim); font-style: normal; }
.poem__tail { margin-top: 30px; color: var(--bone-dim); max-width: 44ch; }

/* ── The flock ────────────────────────────────────────────
   Four birds crossing the section, each on its own view timeline so the drift
   is scrubbed by scroll rather than played on a loop. The wings are the only
   thing running on a clock, and only while the section is on screen. */

.flock {
  position: absolute; inset: 0;
  pointer-events: none; overflow: hidden; z-index: 1;
}

.bird {
  position: absolute;
  fill: none;
  stroke: var(--bone);
  stroke-width: 1.6;
  stroke-linecap: round;
  will-change: translate;
}
.bird__wings { transform-origin: 50% 60%; }

.bird--1 { width: 26px; top: 16%; opacity: .30; }
.bird--2 { width: 17px; top: 27%; opacity: .22; }
.bird--3 { width: 32px; top: 9%;  opacity: .34; }
.bird--4 { width: 13px; top: 36%; opacity: .16; }

@keyframes flyAcross {
  from { translate: -14vw 46px; }
  to   { translate: 108vw -54px; }
}
@keyframes flapWings {
  0%, 100% { scale: 1 1; }
  50%      { scale: 1 .42; }
}

@media (prefers-reduced-motion: no-preference) {
  .bird__wings { animation: flapWings 1.15s ease-in-out infinite; }
  .bird--2 .bird__wings { animation-duration: .82s; animation-delay: -.3s; }
  .bird--3 .bird__wings { animation-duration: 1.5s; animation-delay: -.6s; }
  .bird--4 .bird__wings { animation-duration: .68s; animation-delay: -.15s; }

  @supports (animation-timeline: view()) {
    .bird { animation: flyAcross linear both; animation-timeline: view(); }
    .bird--1 { animation-range: cover 0% cover 100%; }
    .bird--2 { animation-range: cover 8% cover 92%; }
    .bird--3 { animation-range: cover 18% cover 100%; }
    .bird--4 { animation-range: cover 0% cover 78%; }
  }
}

/* Without scroll-driven animation the flock would sit frozen in a corner,
   so it simply does not appear. */
.no-sdt .flock { display: none; }

.poem__inset {
  position: absolute; right: clamp(24px, 6vw, 96px); bottom: -52px;
  width: clamp(150px, 26vw, 300px);
  margin: 0; z-index: 2;
  border: 1px solid var(--ink-3);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .55);
}
.poem__inset img { border-radius: var(--r); }

/* ── Cardápio ─────────────────────────────────────────── */

.menu__head { margin-bottom: 40px; }

.cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px; margin-bottom: 76px; }

.card {
  display: flex; flex-direction: column;
  padding: 28px 30px 26px;
  background: var(--ink-2); border: 1px solid var(--ink-3); border-radius: var(--r);
  text-decoration: none; color: var(--bone);
  transition: border-color .3s var(--ease), background-color .3s var(--ease), translate .3s var(--ease);
}
.card:hover { border-color: var(--gold-dp); background: #1b1710; translate: 0 -3px; }
.card__k { font-family: var(--display); font-size: 1.7rem; line-height: 1.1; }
.card__d { color: var(--bone-dim); font-size: .95rem; margin-top: 10px; }
.card__go { color: var(--gold); font-size: .87rem; font-weight: 500; margin-top: 20px; }

/* One dish gets to be large. */
.feature {
  display: grid;
  grid-template-columns: 1.2fr 1fr;    /* 604 / 504 of 1152 with a 44 gap */
  gap: 44px;
  align-items: end;
  margin: 0 0 84px;
}
.feature__img { overflow: hidden; border-radius: var(--r); }
.feature__cap { padding: 0 8px 42px 0; }
.feature__cap::before {
  content: ""; display: block; width: 46px; height: 1px;
  background: var(--gold-dp); margin-bottom: 22px;
}
.feature__name { font-size: clamp(1.9rem, 3.2vw, 2.7rem); margin-bottom: 14px; }
.feature__cap p { color: var(--bone-dim); font-size: 1rem; margin: 0; }

.course {
  margin: 0 0 22px; padding-bottom: 12px;
  border-bottom: 1px solid var(--ink-3);
  font-size: 1.45rem;
}

/* A fixed grid, three cells per course for exactly three dishes.
   Column masonry was wrong here: with four items it balanced them 1/1/2 and
   left a dead column half the section tall. A uniform 4:3 crop costs a little
   of each frame but gives the section a rhythm, and object-position is set per
   image below wherever the subject would otherwise drift out. */
.dishes {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px 20px;                              /* (1152 - 40) / 3 = 371px */
  margin-bottom: 62px;
}

.dish { margin: 0; }
.dish img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  object-position: 50% 50%;
  border-radius: var(--r);
}
.dish .pos-upper { object-position: 50% 32%; }  /* tall glass, keep the top */
.dish .pos-lower { object-position: 50% 62%; }  /* plate sits low in frame */

.dish figcaption {
  font-family: var(--display); font-size: 1.16rem; color: var(--bone);
  padding-top: 11px;
}

/* ── Vinhos. Full bleed band. ─────────────────────────── */

.band {
  position: relative;
  padding: clamp(96px, 11vw, 164px) 0;
  border-block: 1px solid var(--ink-3);
  scroll-margin-top: var(--bar-h);
  overflow: hidden;
}
.band__bg {
  position: absolute; inset: 0 0 0 auto;
  width: 55%; height: 100%;
  object-fit: cover; object-position: 34% 42%;
  opacity: .55;
  -webkit-mask-image: linear-gradient(100deg, transparent 4%, #000 44%);
          mask-image: linear-gradient(100deg, transparent 4%, #000 44%);
}
.band__in {
  position: relative; z-index: 1;
  padding-left: var(--gutter); padding-right: var(--pad);
  max-width: calc(var(--gutter) + 600px);
}
.band__in .lead { max-width: 40ch; }

.award {
  font-family: var(--display); font-style: italic;
  font-size: 1.16rem; color: var(--gold);
  border-top: 1px solid var(--ink-3);
  padding-top: 18px; margin-top: 26px; max-width: 46ch;
}

/* ── A casa ───────────────────────────────────────────── */

.casa__head { margin-bottom: 38px; }

/* Asymmetric grid, deliberately a different layout family from the masonry.
   Exactly three cells for exactly three photographs. */
.casa__grid { display: grid; grid-template-columns: 1.4fr 1fr; gap: 24px; }   /* 658 / 470 */
.casa__grid figure { margin: 0; overflow: hidden; border-radius: var(--r); }
.casa__grid img { width: 100%; }
.casa__tall { grid-column: 2; grid-row: 1 / span 2; }
.casa__tall img { height: 100%; object-fit: cover; }

/* ── Visita ───────────────────────────────────────────── */

.visita__grid { display: grid; grid-template-columns: 1.2fr 1fr; gap: 56px; align-items: start; }  /* 624 / 498 */

.facts { margin: 0; }
.facts dt { font-family: var(--display); font-size: 1.25rem; color: var(--gold); margin-top: 28px; }
.facts dd { margin: 7px 0 0; color: var(--bone-dim); }
.facts dd a { color: var(--bone); }
.facts dd a:hover { color: var(--gold); }

.hrs { display: block; }
.hrs b { color: var(--bone); font-weight: 500; }
.hrs--nb { margin-top: 13px; font-family: var(--display); font-style: italic; color: var(--bone-dim); }

.visita__fig { margin: 0 0 24px; overflow: hidden; border-radius: var(--r); }

/* ── Footer ───────────────────────────────────────────── */

.foot { border-top: 1px solid var(--ink-3); padding: 36px 0 46px; }
.foot__in { display: flex; flex-wrap: wrap; align-items: center; gap: 14px 26px; }
.mark--foot .mark__wd { font-size: 1.18rem; }
.foot__addr { color: var(--bone-dim); font-size: .9rem; margin-left: auto; }
.foot__ig { font-size: .9rem; text-decoration: none; }
.foot__ig:hover { text-decoration: underline; }

/* ══ Motion ═══════════════════════════════════════════════
   Every animation below answers one question: what does it
   communicate? Draw-on = the mark is carved by hand. Sway =
   the sign hangs on chains. Wipe = the photograph arrives.
   Rise = reading order. Nothing here is decoration. */

/* ── The hero, on load ────────────────────────────────────
   A sequence, not a simultaneous fade: the crest is drawn, the name rises out
   of its own mask a word at a time, then the facade is uncovered from the edge
   it bleeds off. */

.mask { display: block; overflow: hidden; }
/* Scoped to the mask. A bare `.up { display: block }` here sits later in the
   file than `.row { display: flex }` and silently flattened the hero buttons
   into inline-blocks, which then baseline-aligned at different heights. */
.mask .up { display: block; }
.js .up { opacity: 0; translate: 0 110%; }
.is-ready .up {
  opacity: 1; translate: 0 0;
  transition: translate 1.15s cubic-bezier(.19, 1, .22, 1),
              opacity .8s var(--ease);
}
.is-ready .up--1 { transition-delay: .34s; }
.is-ready .up--2 { transition-delay: .44s; }
.is-ready .up--3 { transition-delay: .70s; }
.is-ready .up--4 { transition-delay: .82s; }

.js .hero__fig { clip-path: inset(0 0 0 100%); }
.is-ready .hero__fig {
  clip-path: inset(0 0 0 0);
  transition: clip-path 1.5s cubic-bezier(.65, 0, .18, 1) .12s;
}
.js .hero__fig img { scale: 1.18; }
.is-ready .hero__fig img { scale: 1; transition: scale 2.2s cubic-bezier(.19, 1, .22, 1) .12s; }

/* ── On scroll ────────────────────────────────────────────
   Where the browser supports it, these run on the element's own view timeline,
   so they are scrubbed by scroll position rather than fired once on entry. The
   difference is the point: content tracks the scroll instead of playing at it.
   `.no-sdt` is set by app.js for browsers without scroll-driven animation, and
   falls back to the same choreography as a one-shot transition. */

/* No hidden state by default. Where scroll-driven animation runs, the keyframes
   supply the starting state through `both`. Where it does not, `.no-sdt` (set by
   app.js) supplies it, so a browser without either still shows the content. */
.wipe { overflow: hidden; }
.ln { display: block; }

.no-sdt .rise { opacity: 0; translate: 0 26px; }
.no-sdt .wipe img, .no-sdt .wipe > img { opacity: 0; scale: 1.14; }
.no-sdt .ln { opacity: 0; translate: 0 108%; }

@keyframes riseIn {
  from { opacity: 0; translate: 0 26px; }
  to   { opacity: 1; translate: 0 0; }
}
@keyframes settleIn {
  from { opacity: 0; scale: 1.14; }
  to   { opacity: 1; scale: 1; }
}
@keyframes lineIn {
  from { opacity: 0; translate: 0 108%; }
  to   { opacity: 1; translate: 0 0; }
}

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    /* Ranges stay inside the `entry` phase, which is the well-defined one: 0%
       is the element touching the bottom of the viewport, 100% is it fully in. */
    .rise {
      animation: riseIn linear both;
      animation-timeline: view();
      animation-range: entry 10% cover 22%;
    }
    .wipe img, .wipe > img {
      animation: settleIn linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 26%;
    }
    /* Each tile runs on its own timeline, so the stagger falls out of the
       layout rather than out of hand-tuned delays. */
    .dish:nth-child(2) img { animation-range: entry 0% cover 31%; }
    .dish:nth-child(3) img { animation-range: entry 0% cover 36%; }

    .ln { animation: lineIn linear both; animation-timeline: view(); }
    .stanza .mask:nth-child(1) .ln { animation-range: entry 30% cover 20%; }
    .stanza .mask:nth-child(2) .ln { animation-range: entry 45% cover 25%; }
    .stanza .mask:nth-child(3) .ln { animation-range: entry 60% cover 30%; }
  }
}

/* Fallback: one-shot on entry, driven by IntersectionObserver. */
.no-sdt .rise.is-in {
  opacity: 1; translate: 0 0;
  transition: opacity .8s var(--ease), translate .8s var(--ease);
}
.no-sdt .wipe.is-in img, .no-sdt .wipe.is-in > img {
  opacity: 1; scale: 1;
  transition: opacity .9s var(--ease), scale 1.5s var(--ease);
}
.no-sdt .stanza.is-in .ln {
  opacity: 1; translate: 0 0;
  transition: translate 1s cubic-bezier(.19, 1, .22, 1), opacity .7s var(--ease);
}
.no-sdt .stanza.is-in .mask:nth-child(2) .ln { transition-delay: .12s; }
.no-sdt .stanza.is-in .mask:nth-child(3) .ln { transition-delay: .26s; }

/* The client's real crest. A filled trace cannot be drawn on with
   stroke-dashoffset, so the same path is used twice: once stroked, which draws
   its own outline, and once filled, which inks in behind it as the line closes. */
.crest { display: block; overflow: visible; }

.crest__line {
  fill: none;
  stroke: var(--gold);
  stroke-width: .9;
  stroke-linejoin: round;
}
.js .crest__line { stroke-dasharray: 1; stroke-dashoffset: 1; }
.crest.is-drawn .crest__line {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 2.1s cubic-bezier(.5, 0, .2, 1);
}

/* A slow band of paler gold crosses the mark every nine seconds, the way light
   moves across the carved sign at the entrance. The sweep lives in the SVG
   sprite as an <animateTransform> on the gradient, so it costs no JavaScript
   and no main-thread work.

   The gradient's stops are all the same gold except the narrow bright band, so
   if the gradient fails to resolve for any reason the mark is simply solid
   gold: the fallback and the resting state are the same colour. */
.crest__ink { fill: var(--gold); fill: url(#crestSheen); }
.js .crest__ink { opacity: 0; }
.crest.is-drawn .crest__ink { opacity: 1; transition: opacity 1s var(--ease) 1.1s; }

.crest--hero { width: 104px; height: auto; margin-bottom: 28px; transform-origin: 50% 0%; }
/* It hangs on chains at the door, so once drawn it settles like one. */
.crest--hero.is-swaying { animation: sway 6.5s var(--ease) 0s infinite alternate; }
@keyframes sway {
  from { rotate: -1.9deg; }
  to   { rotate: 1.9deg; }
}

/* Depth. The photograph shifts inside its frame under the pointer.
   Written straight to style.transform, never through a custom property,
   which would not composite. `translate`/`scale` stay free for the
   wipe and the parallax, so the two never fight. */
.tilt { perspective: 1200px; }
.tilt img { transition: transform .6s var(--ease); }

.dish img { transition: transform .7s var(--ease); }
.dish:hover img { transform: scale(1.05); }
.dish figcaption { position: relative; display: inline-block; }
.dish figcaption::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px;
  height: 1px; background: var(--gold);
  scale: 0 1; transform-origin: left;
  transition: scale .5s var(--ease);
}
.dish:hover figcaption::after { scale: 1 1; }

/* A slow shine across the primary action, once, on hover. The room is lit by
   candles and the crest is brass; the light moving over it is the reference. */
.btn--gold { position: relative; overflow: hidden; isolation: isolate; }
.btn--gold::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(100deg, transparent 34%, rgba(255, 252, 242, .5) 50%, transparent 66%);
  translate: -130% 0;
}
.btn--gold:hover::after { translate: 130% 0; transition: translate .9s cubic-bezier(.3, 0, .2, 1); }

/* Parallax, where the browser can drive it off the scroll timeline itself.
   No scroll listener, no main-thread work. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .poem__bg, .band__bg {
      animation: drift linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
    @keyframes drift {
      from { translate: 0 -3.5%; }
      to   { translate: 0 3.5%; }
    }
  }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .up, .is-ready .up { opacity: 1; translate: 0; transition: none; }
  .rise, .rise.is-in, .no-sdt .rise.is-in { opacity: 1; translate: 0; transition: none; animation: none; }
  .ln, .stanza.is-in .ln, .no-sdt .stanza.is-in .ln { opacity: 1; translate: 0; transition: none; animation: none; }
  .hero__fig, .is-ready .hero__fig { clip-path: none; transition: none; }
  .hero__fig img, .is-ready .hero__fig img { scale: 1; transition: none; }
  .wipe img, .wipe > img, .wipe.is-in img, .wipe.is-in > img,
  .no-sdt .wipe.is-in img, .no-sdt .wipe.is-in > img { opacity: 1; scale: 1; transition: none; animation: none; }
  .btn--gold::after { display: none; }
  .dish figcaption::after { display: none; }
  .flock { display: none; }
  .js .bar .bar__cta,
  .js .bar.is-solid .bar__cta { translate: 0; transition: opacity .2s linear, visibility 0s; }
  .crest__line, .crest.is-drawn .crest__line { stroke-dashoffset: 0; transition: none; }
  /* Flat gold, no travelling sheen. SMIL cannot be stopped from CSS, so the
     gradient is taken out of the fill entirely rather than paused. */
  .crest__ink, .crest.is-drawn .crest__ink { opacity: 1; transition: none; fill: var(--gold); }
  .crest--hero.is-swaying { animation: none; }
  .tilt img, .dish img { transition: none; }
  .dish:hover img { transform: none; }
  .btn, .card { transition: none; }
  .btn:active { transform: none; }
}

/* ── Responsive. Declared after the base rules so they win. ── */

@media (max-width: 1080px) {
  .nav { gap: 0; }
  .nav a { padding: 6px 9px; font-size: .88rem; }
}

@media (max-width: 900px) {
  body { font-size: 16px; }

  .hero { padding-top: calc(var(--bar-h) + 26px); }
  .hero__grid { grid-template-columns: 1fr; gap: 36px; }
  .hero__text { padding-inline: var(--pad); }
  .hero__fig { padding-left: var(--pad); }

  /* Stacked layout: the photograph is now full width under the text, so the
     feather has far less room. Keeping the desktop values would eat most of
     the picture. Note --fy belongs to the figure and --fx to the img; they are
     set on different elements on purpose (see the mask block above). */
  .hero__fig     { --fy: clamp(24px, 6vw, 54px); }
  .hero__fig img { --fx: clamp(28px, 9vw, 60px); }

  .poem__bg, .band__bg {
    width: 100%;
    opacity: .3;
    -webkit-mask-image: linear-gradient(180deg, transparent 2%, #000 60%);
            mask-image: linear-gradient(180deg, transparent 2%, #000 60%);
  }
  .poem__in, .band__in { padding-inline: var(--pad); max-width: none; }
  .poem__inset { position: static; width: clamp(150px, 42vw, 240px); margin: 34px var(--pad) 0 auto; }
  .poem { padding-bottom: clamp(72px, 9vw, 110px); }

  .feature { grid-template-columns: 1fr; gap: 22px; }
  .casa__grid { grid-template-columns: 1fr; }
  .casa__tall { grid-column: 1; grid-row: auto; }
  .casa__tall img { height: auto; }
  .visita__grid { grid-template-columns: 1fr; gap: 40px; }
}

@media (max-width: 760px) {
  /* The nav stays reachable on phones. It moves to its own scrollable row rather
     than disappearing: five short labels do not justify a menu button, and hiding
     it entirely would leave no way back to a section once you scroll past it. */
  :root { --bar-h: 108px; }

  .bar { position: sticky; background: rgba(14, 12, 9, .96); border-bottom-color: var(--ink-3); }
  .bar__in { flex-wrap: wrap; height: auto; padding-block: 10px; row-gap: 8px; column-gap: 14px; }
  .mark__wd { font-size: 1.18rem; }
  .mark__crest { width: 22px; height: 22px; }
  .bar .bar__cta { margin-left: auto; padding: 9px 17px; font-size: .84rem; }

  /* On a phone the sticky header sits directly on top of the hero, so its
     Reservar and the hero's Reservar appear in the same viewport and read as a
     duplicate. The header one waits until the hero has scrolled away, which is
     also when a persistent booking action starts being useful. Its space stays
     reserved (visibility, not display) so the header does not shift, and
     visibility removes it from the tab order while hidden. */
  .js .bar .bar__cta {
    opacity: 0;
    visibility: hidden;
    translate: 0 -7px;
    transition: opacity .35s var(--ease), translate .35s var(--ease), visibility 0s linear .35s;
  }
  .js .bar.is-solid .bar__cta {
    opacity: 1;
    visibility: visible;
    translate: 0 0;
    transition: opacity .35s var(--ease), translate .35s var(--ease), visibility 0s;
  }
  .nav {
    order: 3; width: 100%; margin-left: 0; gap: 2px;
    overflow-x: auto; overscroll-behavior-x: contain;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .nav::-webkit-scrollbar { display: none; }
  .nav a { padding: 6px 8px; font-size: .85rem; }

  .hero { padding-top: 30px; }
  .cards { grid-template-columns: 1fr; }
  .dishes { grid-template-columns: 1fr 1fr; gap: 12px; }
  .dish figcaption { font-size: 1rem; }
  .hero__h1 { font-size: clamp(3.2rem, 15vw, 4.8rem); }

  /* Keep the pair on one line down to a 360px phone: a primary and a secondary
     action stacked full-width read as a form, which is the thing being avoided
     in the Visita block too. */
  .hero .row { gap: 10px; }
  .hero .row .btn { padding: 13px 18px; font-size: .9rem; }
  .foot__addr { margin-left: 0; width: 100%; }
}

/* ── Hero ember field (three.js, optional) ──────────────────────────────────
   The canvas is painted behind the hero grid, so the facade photograph covers
   it and the motes only ever appear in the dark half. It stays at opacity 0
   until embers.js has a first frame, otherwise a blank canvas would flash a
   hard rectangle over the gradient on slow connections. */
.hero { position: relative; }

.embers {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 0;
  pointer-events: none;      /* the pointer belongs to the buttons under it */
  opacity: 0;
  transition: opacity 1.8s var(--ease);
}

.embers.is-lit { opacity: 1; }

/* Without this the grid shares the canvas's stacking level and the motes can
   paint over the headline. */
.hero__grid { position: relative; z-index: 1; }

/* Belt and braces: the gate in index.html already refuses to load three.js
   under reduced motion, but if it is toggled after load the canvas goes. */
@media (prefers-reduced-motion: reduce) {
  .embers { display: none; }
}
