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

:root {
  --ivory: #FAF7F2;
  --blush: #E8C5B0;
  --gold: #C9A96E;
  --deep: #1A1410;
  --wine: #6B2D3E;
  --card-w: calc(100vw - 48px);
  --card-gap: 16px;
  /* Display type: headings, card titles, values. A rounded sans with a much
     larger x-height than the Cormorant it replaced, which is why the sizes
     set with it run about a fifth smaller. Loaded by the Google Fonts link in
     each page's <head>. */
  --font-display: 'Quicksand', sans-serif;
  /* Names only, always italic 300: Francisco da Ana in Sobre mim
     (.about-name) and the honorees at the top of each client page
     (.header-names). The pages that show neither don't load it. */
  --font-name: 'Cormorant Garamond', serif;
  /* Height of the nav bar every page opens with (see SITE NAV), not counting
     the notch inset — each page's content starts this far down. */
  --nav-h: 64px;
}
@media (min-width: 960px) {
  :root { --nav-h: 76px; }
}

html, body {
  width: 100%; height: 100%;
  overflow: hidden;
  background: var(--deep);
  font-family: 'Josefin Sans', sans-serif;
  color: var(--ivory);
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}

/* ── PAGE TRANSITION ──
   A native cross-document view transition carries every hop between the
   site's pages. The browser holds a snapshot of the
   outgoing page on screen and crossfades it into the new one already
   rendering underneath, so there is no gap for a page to sit blank — which a
   manual fade across two separate navigations can never fully hide.

   Not nested in a @media query: whether a conditional group rule may contain
   this at-rule varies by engine, and a version that drops it at parse time
   would silently leave every browser with no transition at all. Reduced
   motion is handled at the bottom of this block instead, by neutralising the
   animations — which resolves the transition on the next frame, keeping those
   navigations instant rather than merely slower. */
@view-transition {
  navigation: auto;
}

@keyframes fsax-fade-in  { from { opacity: 0; } }
@keyframes fsax-fade-out { to   { opacity: 0; } }

/* The page as a whole: a plain crossfade, quick out and a touch slower in.
   No scale on the incoming root — the mark below is what carries the motion,
   and a simultaneous whole-page zoom just competes with it. */
::view-transition-old(root) { animation: .2s  cubic-bezier(.4, 0, .2, 1) both fsax-fade-out; }
::view-transition-new(root) { animation: .34s cubic-bezier(.4, 0, .2, 1) both fsax-fade-in; }

/* ── THE MARK, ACROSS PAGES ──
   The logo is the one element every page shares, so it is handed a single
   view-transition-name and the browser morphs it: the big hero mark on the
   home glides up and shrinks into the nav, and grows back on the way home.
   Between two pages that both show it in the nav it simply holds still.

   Both snapshots are the same artwork, so the default crossfade between them
   is left alone (its plus-lighter blending is exactly what makes identical
   content crossfade invisibly); only the timing is overridden. Slightly
   outlasting the page fade is what makes the mark read as the anchor the
   page moves around rather than as one more thing fading. */
::view-transition-group(fsax-brand),
::view-transition-old(fsax-brand),
::view-transition-new(fsax-brand) {
  animation-duration: .46s;
  animation-timing-function: cubic-bezier(.32, .72, 0, 1);
}

/* Every element that may claim the name, in one place. Two elements sharing
   a view-transition-name abort the whole transition, silently, so exactly one
   claimant must exist in every state.

   Normally that is the nav's logo — every page has exactly one nav. The home
   is the exception: while its hero is on screen the bar is empty (see SITE
   NAV), and html.nav-over-hero moves the name onto the hero <img> instead.
   site.js keeps that class in step with the scroll; the home's inline <head>
   probe sets it before the first frame, which is the one the incoming half of
   a transition is captured from. An open phone menu shows the nav logo over
   the hero, so while it is open the nav keeps the name. */
.site-nav .site-logo img                                  { view-transition-name: fsax-brand; }
html.nav-over-hero:not(.menu-open) .site-nav .site-logo img { view-transition-name: none; }
html.nav-over-hero:not(.menu-open) .hero-logo             { view-transition-name: fsax-brand; }

/* The bar itself gets a name of its own so it stays put while the page under
   it crossfades — it is the same bar on every page, and fading it out and
   back in would only make it flicker. Its snapshot excludes the logo, which
   is captured separately above. */
.site-nav { view-transition-name: fsax-nav; }

/* Browsers without cross-document view transitions (Firefox, Safari < 18.2)
   get an entrance fade and nothing else. Deliberately no exit fade: holding
   a click to fade the page out means the document has to sit at opacity 0
   waiting for a navigation that may be slow, cancelled, or restored from the
   back/forward cache still faded — a blank page with nothing left to undo it.
   An animation that only ever runs forward cannot strand anything. The class
   comes from the one-line 'onpageswap' probe inlined in each page's <head>. */
@media (prefers-reduced-motion: no-preference) {
  html.no-vt body { animation: .34s cubic-bezier(.4, 0, .2, 1) backwards fsax-fade-in; }
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* ── SITE NAV ──
   The same bar opens every page: three links, the mark, three links — the
   logo stays centred at the top as it always was, the links now flank it.
   Below 960px the links fold into .nav-menu, a full-screen overlay opened by
   the burger (site.js). The markup is repeated in each page's HTML (no build
   step), so a change to the links has to be made in all five.

   backdrop-filter sits on the bar itself, and the overlay is its sibling
   rather than its child, on purpose: backdrop-filter turns an element into
   the containing block of its position:fixed descendants, which would shrink
   a nested full-screen overlay to the height of the bar. */
.site-nav {
  position: fixed; top: 0; left: 0; right: 0;
  z-index: 300;
  padding-top: env(safe-area-inset-top, 0px);
  background-color: rgba(18,15,12,0.82);
  -webkit-backdrop-filter: blur(14px);
          backdrop-filter: blur(14px);
  transition: background-color .45s ease, -webkit-backdrop-filter .45s ease, backdrop-filter .45s ease;
}
.site-nav::after {
  content: '';
  position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: linear-gradient(90deg, transparent, rgba(201,169,110,0.32), transparent);
  transition: opacity .45s ease;
}
/* On the home, over the hero: the bar is empty — no glass, no logo, no links
   — because the hero shows the big mark and the same six links itself.
   site.js lifts the class once the hero's links have scrolled up under the
   bar, which then takes over. */
html.nav-over-hero:not(.menu-open) .site-nav {
  background-color: rgba(18,15,12,0);
  -webkit-backdrop-filter: blur(0px);
          backdrop-filter: blur(0px);
}
html.nav-over-hero:not(.menu-open) .site-nav::after { opacity: 0; }
/* The overlay has its own background, so the bar goes clear over it too. */
html.menu-open .site-nav {
  background-color: rgba(18,15,12,0);
  -webkit-backdrop-filter: blur(0px);
          backdrop-filter: blur(0px);
}
html.menu-open .site-nav::after { opacity: 0; }

.nav-bar {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  height: var(--nav-h);
  max-width: 1200px; margin: 0 auto;
  padding: 0 clamp(16px, 4vw, 40px);
}
.nav-bar > * { grid-row: 1; }
.nav-bar .site-logo { grid-column: 2; margin: 0; }
.site-nav .site-logo img {
  height: 38px;
  transition: opacity .35s ease, transform .5s cubic-bezier(.16, 1, .3, 1);
}
html.nav-over-hero:not(.menu-open) .site-nav .site-logo {
  visibility: hidden; pointer-events: none;
  transition: visibility 0s .35s;
}
html.nav-over-hero:not(.menu-open) .site-nav .site-logo img {
  opacity: 0; transform: translateY(6px);
}
.nav-links,
.nav-burger { transition: opacity .4s ease, visibility 0s; }
html.nav-over-hero:not(.menu-open) .nav-links,
html.nav-over-hero:not(.menu-open) .nav-burger {
  opacity: 0; visibility: hidden;
  transition: opacity .3s ease, visibility 0s .3s;
}

.nav-links { display: none; list-style: none; }
.nav-links a {
  position: relative; display: block;
  padding: 10px 0;
  font-family: 'Josefin Sans', sans-serif;
  font-size: 10.5px; font-weight: 300;
  letter-spacing: 3.5px; text-transform: uppercase;
  color: rgba(250,247,242,0.72); text-decoration: none;
  white-space: nowrap;
  transition: color .3s ease;
}
/* right stops short by one letter-space, so the line ends under the last
   letter rather than under the tracking after it. */
.nav-links a::after {
  content: '';
  position: absolute; left: 0; right: 3.5px; bottom: 3px; height: 1px;
  background: var(--gold);
  transform: scaleX(0);
  transition: transform .35s cubic-bezier(.4, 0, .2, 1);
}
.nav-links a[href]:hover,
.nav-links a[href]:focus-visible { color: var(--ivory); outline: none; }
.nav-links a[href]:hover::after,
.nav-links a[href]:focus-visible::after { transform: scaleX(1); }
/* The page you are on has no href (a reload would drop clientes' link key
   and stop the covers player), and on the home site.js marks the section in
   view as .is-active. */
.nav-links a[aria-current],
.nav-links a.is-active { color: var(--gold); }
.nav-links a:not([href]),
.nav-menu-links a:not([href]) { cursor: default; }
.nav-links a[aria-current]::after,
.nav-links a.is-active::after { transform: scaleX(1); opacity: .6; }

.nav-burger {
  grid-column: 3; justify-self: end;
  width: 44px; height: 44px; margin-right: -11px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 0; padding: 0;
  color: var(--ivory); cursor: pointer;
}
.nav-burger:focus { outline: none; }
.nav-burger:focus-visible { outline: 1px solid rgba(201,169,110,0.55); outline-offset: -4px; border-radius: 50%; }
.nav-burger-lines {
  width: 22px;
  display: flex; flex-direction: column; align-items: flex-end; gap: 7px;
}
.nav-burger-lines span {
  display: block; width: 22px; height: 1px;
  background: currentColor;
  transition: transform .4s cubic-bezier(.4, 0, .2, 1), width .4s cubic-bezier(.4, 0, .2, 1);
}
.nav-burger-lines span:nth-child(2) { width: 14px; }
html.menu-open .nav-burger-lines span:nth-child(1) { transform: translateY(4px) rotate(45deg); }
html.menu-open .nav-burger-lines span:nth-child(2) { width: 22px; transform: translateY(-4px) rotate(-45deg); }

/* ── The phone menu ── */
.nav-menu {
  position: fixed; inset: 0;
  z-index: 290;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 40px;
  padding: calc(var(--nav-h) + env(safe-area-inset-top, 0px) + 12px) 24px calc(32px + env(safe-area-inset-bottom, 0px));
  overflow-y: auto;
  background:
    radial-gradient(ellipse 80% 40% at 50% 100%, rgba(107,45,62,0.3) 0%, transparent 70%),
    radial-gradient(ellipse 60% 40% at 10% 20%, rgba(201,169,110,0.1) 0%, transparent 60%),
    linear-gradient(180deg, #1A1410 0%, #120F0C 100%);
  opacity: 0; visibility: hidden;
  transition: opacity .35s ease, visibility 0s linear .35s;
}
html.menu-open .nav-menu {
  opacity: 1; visibility: visible;
  transition: opacity .35s ease, visibility 0s;
}
.nav-menu-links { list-style: none; text-align: center; }
.nav-menu-links a {
  display: block; padding: 9px 24px;
  font-family: var(--font-display);
  font-size: clamp(23px, 6.6vw, 30px); font-weight: 300;
  letter-spacing: 1px; line-height: 1.2;
  color: var(--ivory); text-decoration: none;
  transition: color .3s ease;
}
.nav-menu-links a[href]:hover,
.nav-menu-links a[href]:focus-visible { color: var(--gold); outline: none; }
.nav-menu-links a[aria-current] { color: var(--gold); }
.nav-menu-social { display: flex; gap: 12px; justify-content: center; }

@media (prefers-reduced-motion: no-preference) {
  .nav-menu-links li,
  .nav-menu-social {
    opacity: 0; transform: translateY(10px);
    transition: opacity .3s ease, transform .3s ease;
  }
  html.menu-open .nav-menu-links li,
  html.menu-open .nav-menu-social {
    opacity: 1; transform: none;
    transition: opacity .5s ease, transform .6s cubic-bezier(.16, 1, .3, 1);
    transition-delay: calc(var(--i, 0) * 45ms + 90ms);
  }
}
.nav-menu-social { --i: 8; }

@media (min-width: 960px) {
  .nav-links {
    display: flex; align-items: center;
    gap: clamp(24px, 2.6vw, 40px);
  }
  .nav-left  { grid-column: 1; justify-self: end;   padding-right: clamp(32px, 3.6vw, 56px); }
  .nav-right { grid-column: 3; justify-self: start; padding-left:  clamp(32px, 3.6vw, 56px); }
  .site-nav .site-logo img { height: 44px; }
  .nav-burger,
  .nav-menu { display: none; }
}

/* ── ICONS ── One sprite (images/icons.svg) serves every page; these set the
   line style its symbols inherit through <use>. */
.icon {
  width: 18px; height: 18px; flex-shrink: 0;
  fill: none; stroke: currentColor;
  stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round;
}

/* Round social buttons: the phone menu. */
.social-btn {
  width: 42px; height: 42px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid rgba(201,169,110,0.28); border-radius: 50%;
  background: rgba(201,169,110,0.05);
  color: var(--gold);
  transition: background .3s, color .3s, border-color .3s;
}
.social-btn:hover,
.social-btn:focus-visible { background: var(--gold); color: var(--deep); border-color: var(--gold); outline: none; }

/* ── LOCK SCREEN ──
   Same shell as the covers page: the nav holds its spot at the top while a
   single 720px column scrolls beneath it, opened by an artwork header. The
   measurements below deliberately mirror .cv-* in covers/covers.css. */
#lock {
  position: fixed; inset: 0;
  display: flex; flex-direction: column;
  padding-top: calc(var(--nav-h) + env(safe-area-inset-top, 0px));
  background: var(--deep);
  z-index: 100;
  transition: opacity .8s ease, transform .8s ease;
}
#lock.hide { opacity: 0; pointer-events: none; transform: translateY(-40px); }

.lock-scroll {
  position: relative; z-index: 2;
  flex: 1; min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  width: 100%;
  scrollbar-width: thin;
  scrollbar-color: rgba(201,169,110,0.35) transparent;
}
.lock-scroll::-webkit-scrollbar { width: 8px; }
.lock-scroll::-webkit-scrollbar-track { background: transparent; }
.lock-scroll::-webkit-scrollbar-thumb {
  background: rgba(201,169,110,0.35);
  border-radius: 4px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
.lock-scroll::-webkit-scrollbar-thumb:hover {
  background: rgba(201,169,110,0.55);
  background-clip: padding-box;
}
.lock-scroll-inner {
  width: 100%; max-width: 720px;
  margin: 0 auto;
  padding: 0 22px calc(48px + env(safe-area-inset-bottom, 0px));
  /* On tall viewports, push the content down so it sits nearer the centre
     of the screen instead of hugging the top. The clamp prevents it from
     pushing too far on very tall displays. */
  display: flex; flex-direction: column;
  min-height: 100%;
}

/* Reuses .app-bg — the same gradient as every other page — so the lock
   screen and the deck behind it share a single atmospheric layer. */
.lock-bg {
  position: absolute; inset: 0;
  background:
    radial-gradient(ellipse 80% 40% at 50% 100%, rgba(107,45,62,0.25) 0%, transparent 70%),
    radial-gradient(ellipse 60% 40% at 10% 20%, rgba(201,169,110,0.1) 0%, transparent 60%),
    linear-gradient(180deg, #1A1410 0%, #120F0C 100%);
  pointer-events: none;
}

/* ── Header: centred title block ── */
.lock-header {
  text-align: center;
  padding: 32px 0 24px;
  margin-top: auto;
}
@media (max-width: 640px) {
  .lock-header {
    padding: 18px 0 14px;
  }
}
.lock-title {
  font-family: var(--font-display);
  font-size: clamp(27px, 7vw, 43px);
  font-weight: 300;
  color: var(--ivory); letter-spacing: 2px; line-height: 1.05;
  /* Nothing follows it inside the header — the header's own padding does the
     spacing to the state block below. */
  margin-bottom: 0;
}

/* One of these two is shown, depending on whether the address bar carries a
   link key. The [hidden] rule is not redundant: the UA's own hidden{display:none}
   would lose to the author display:flex above it. */
.lock-state {
  display: flex; flex-direction: column; align-items: center;
  text-align: center;
}
.lock-state[hidden] { display: none; }

/* boot() reveals whichever of the two it needs only after a round trip to
   Postgres, so it lands a beat behind the page transition. Give it the same
   entrance the page had, so it reads as the last step of one arrival instead
   of a block appearing out of nowhere. */
@keyframes fsax-rise-in { from { opacity: 0; transform: translateY(8px); } }

@media (prefers-reduced-motion: no-preference) {
  .lock-state:not([hidden]) { animation: .42s cubic-bezier(.16, 1, .3, 1) backwards fsax-rise-in; }
}

.lock-lead {
  max-width: 620px;
  font-size: 15.5px; font-weight: 300;
  line-height: 1.8; letter-spacing: 0.4px;
  color: var(--ivory); opacity: 0.5;
  margin-bottom: 26px;
}

/* ── No-link state: the pitch ── */
/* Wide enough for the sentence to hold one line on a desktop window, and the
   same 620px as the card grid below it so the two edges agree. */
.lock-intro {
  max-width: 620px;
  font-size: 16.5px; font-weight: 300;
  line-height: 1.6; letter-spacing: 0.3px;
  color: var(--ivory); opacity: 0.85;
  margin-bottom: 24px;
  text-align: center;
}

.lock-features {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  width: 100%;
  max-width: 620px;
  margin: 0 auto 24px;
}

.lock-feature-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 18px 12px 16px;
  background: rgba(255, 255, 255, 0.025);
  border: 1px solid rgba(201, 169, 110, 0.16);
  border-radius: 6px;
}

.lock-feature-icon {
  width: 36px; height: 36px;
  border-radius: 50%;
  background: rgba(201, 169, 110, 0.08);
  border: 1px solid rgba(201, 169, 110, 0.22);
  display: flex; align-items: center; justify-content: center;
  color: var(--gold);
  margin-bottom: 10px;
  flex-shrink: 0;
}

/* The treatment the header subtitle used to carry: blush, wide uppercase
   tracking. Kept brighter than that subtitle's 0.5 — here the line sits above a
   description, so at 0.5 it read fainter than its own body text. 2px is also as
   wide as the tracking can go: "REPERTÓRIO MUSICAL" needs 163 of the card's
   171px, and 3px wraps it to two lines. */
.lock-feature-content h3 {
  font-family: 'Josefin Sans', sans-serif;
  font-size: 12.5px; font-weight: 300;
  letter-spacing: 2px; text-transform: uppercase;
  color: var(--blush); opacity: 0.8;
  margin: 0 0 5px;
}

.lock-feature-content p {
  font-size: 13.5px; font-weight: 300;
  line-height: 1.45; color: rgba(250, 247, 242, 0.55);
  margin: 0;
}

@media (max-width: 640px) {
  .lock-intro {
    font-size: 16px;
    margin-bottom: 14px;
    padding: 0 8px;
  }
  /* Tightened to buy back the height the larger type costs, so the whole
     pitch still lands inside one phone screen. */
  .lock-features {
    grid-template-columns: 1fr;
    gap: 8px;
    margin-bottom: 16px;
  }
  .lock-feature-card {
    flex-direction: row;
    align-items: center;
    text-align: left;
    padding: 12px 16px;
    gap: 14px;
    border-radius: 8px;
  }
  .lock-feature-icon {
    margin-bottom: 0;
  }
  .lock-feature-content {
    text-align: left;
  }
  /* Tracking stays at the base 2px — a full-width row has the space for it. */
  .lock-feature-content h3 {
    margin: 0 0 3px;
    font-size: 12px;
  }
  .lock-feature-content p {
    font-size: 13px;
    line-height: 1.4;
  }
}

#noLinkBlock .lock-lead {
  margin-bottom: 0;
  font-size: 14.5px;
  opacity: 0.45;
  line-height: 1.6;
}

.lock-form {
  display: flex; flex-direction: column;
  align-items: stretch; gap: 16px; width: 260px; max-width: 100%;
  margin: 0 auto;
}
.lock-hint {
  font-size: 11px; letter-spacing: 4px;
  color: var(--blush); opacity: 0.6;
  text-transform: uppercase; margin-bottom: -10px;
  text-align: center;
}
.lock-input {
  width: 100%;
  background: rgba(255,255,255,0.05);
  border: 1px solid rgba(201,169,110,0.3);
  border-radius: 2px; padding: 14px 18px;
  font-family: 'Josefin Sans', sans-serif;
  font-size: 15px; letter-spacing: 3px;
  color: var(--ivory); text-align: center;
  outline: none; transition: border-color .3s;
}
.lock-input::placeholder { color: rgba(250,247,242,0.2); letter-spacing: 2px; font-size: 13px; }
.lock-input:focus { border-color: var(--gold); }
/* The 4-digit field. text-indent cancels the trailing letter-space so the
   digits stay optically centred instead of drifting left — the usual PIN-field
   correction. Not a password field: four digits are friendlier read back on a
   phone than four dots. */
.lock-pin {
  font-size: 28px; letter-spacing: 16px; text-indent: 16px;
  padding: 16px 18px; caret-color: var(--gold);
}
.lock-pin::placeholder { letter-spacing: 16px; font-size: 28px; }
.lock-pin.is-error { border-color: var(--wine); animation: pin-shake .3s ease; }
@keyframes pin-shake {
  25% { transform: translateX(-5px); }
  75% { transform: translateX(5px); }
}
@media (prefers-reduced-motion: reduce) {
  .lock-pin.is-error { animation: none; }
}
/* While the RPC is in flight — stops a double submit burning two rate-limit
   slots on one impatient tap. */
.lock-form.is-busy { opacity: .5; pointer-events: none; }

.lock-btn {
  width: 100%; padding: 14px;
  background: transparent; border: 1px solid var(--gold); border-radius: 2px;
  font-family: 'Josefin Sans', sans-serif;
  font-size: 11px; letter-spacing: 5px; text-transform: uppercase;
  color: var(--gold); cursor: pointer; transition: background .3s, color .3s;
}
.lock-btn:active { background: var(--gold); color: var(--deep); }
.lock-error {
  font-size: 11px; letter-spacing: 3px;
  color: var(--wine); opacity: 0; transition: opacity .3s; text-transform: uppercase;
}
.lock-error.show { opacity: 1; }

/* Shortcut into the public demo event. While the PIN pad is up it is a bare
   icon parked at the bottom of the screen, so it never competes with the form
   — absolute against .lock-scroll, which fills what is left of the viewport. */
.lock-demo[hidden] { display: none; }
.lock-demo {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: calc(26px + env(safe-area-inset-bottom, 0px));
  width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  background: transparent;
  border: 1px solid rgba(201,169,110,0.25); border-radius: 50%;
  font-family: 'Josefin Sans', sans-serif;
  color: var(--gold); opacity: 0.45;
  cursor: pointer;
  transition: opacity .3s, border-color .3s, background .3s, color .3s;
}
.lock-demo:hover,
.lock-demo:focus { opacity: 1; border-color: var(--gold); outline: none; }
.lock-demo:disabled { opacity: 0.2; cursor: default; }
.lock-demo-label { display: none; }

/* Revealed by the same script.js call as the .lock-state above it, a hair
   later so the two don't arrive as one lump. Opacity only, no transform: the
   corner-icon shape centres itself with translateX(-50%), which a translate
   keyframe here would quietly overwrite. */
@media (prefers-reduced-motion: no-preference) {
  .lock-demo:not([hidden]) {
    animation: .42s cubic-bezier(.4, 0, .2, 1) .08s backwards fsax-fade-in;
  }
}

/* With no form to compete with, it comes back into the column under the copy
   and unfolds into a compact labelled pill button. Class set by showNoLink() in script.js. */
#lock.is-nolink .lock-demo {
  position: static; transform: none;
  align-self: center;
  width: fit-content; max-width: max-content;
  margin: 20px auto 0; padding: 7px 16px;
  border-radius: 20px; gap: 7px;
  background: rgba(201, 169, 110, 0.06);
  border: 1px solid rgba(201, 169, 110, 0.35);
  opacity: 0.85;
  transition: all .25s ease;
}
#lock.is-nolink .lock-demo:hover,
#lock.is-nolink .lock-demo:focus {
  opacity: 1;
  background: rgba(201, 169, 110, 0.16);
  border-color: var(--gold);
  transform: translateY(-1px);
}
#lock.is-nolink .lock-demo-label {
  display: inline;
  font-size: 10.5px; letter-spacing: 2.5px; text-transform: uppercase;
}

/* Bottom spacer that balances the margin-top:auto on the header, so the
   content group as a whole gravitates toward the centre. */
.lock-state { margin-bottom: auto; }

/* ── SITE LOGO → HOME ──
   The way back home on every page: the mark in the middle of the nav bar. */
.site-logo {
  display: block; width: fit-content;
  margin: 0 auto; flex-shrink: 0;
}
/* Opacity sits on the img, not on the <a>: the img is what carries
   view-transition-name (see THE MARK, ACROSS PAGES), and an ancestor's
   opacity is not captured into a snapshot — from the <a> the mark would fly
   at full strength and snap dimmer on landing. */
.site-logo img {
  display: block;
  height: 44px; width: auto;
  opacity: 0.8; transition: opacity .3s;
  filter: drop-shadow(0 0 20px rgba(201,169,110,0.22));
}
.site-logo:hover img,
.site-logo:focus img { opacity: 1; }
.site-logo:focus { outline: none; }

/* ── WELCOME CARD (inside deck) ── */
.card.card-welcome {
  display: flex; flex-direction: column; align-items: center;
  height: 100%; overflow-y: auto;
  background: transparent;
  border: none;
  backdrop-filter: none;
}
.card.card-welcome::before { display: none; }

.welcome-card-inner {
  display: flex; flex-direction: column;
  align-items: center; text-align: center;
  padding: 20px; max-width: 480px;
  margin: auto 0;
}

.welcome-card-divider {
  width: 50px; height: 1px;
  background: rgba(201,169,110,0.4);
  margin: 24px auto 32px;
}
.welcome-card-greeting {
  font-family: 'Josefin Sans', sans-serif;
  font-size: 14px; letter-spacing: 6px;
  text-transform: uppercase;
  color: var(--gold); opacity: 0.8;
  margin-bottom: 28px;
}
.welcome-card-body {
  font-family: var(--font-display);
  font-size: 17px; font-weight: 300;
  color: var(--ivory); opacity: 0.7;
  line-height: 1.7; letter-spacing: 0.5px;
  margin-bottom: 20px;
}


/* ── MAIN APP ── */
#app {
  position: fixed; inset: 0;
  display: flex; flex-direction: column;
  padding-top: calc(var(--nav-h) + env(safe-area-inset-top, 0px));
  opacity: 0; pointer-events: none;
  transition: opacity .8s ease .3s;
}
#app.show { opacity: 1; pointer-events: all; }

.app-bg {
  position: absolute; inset: 0;
  background:
    radial-gradient(ellipse 80% 40% at 50% 100%, rgba(107,45,62,0.25) 0%, transparent 70%),
    radial-gradient(ellipse 60% 40% at 10% 20%, rgba(201,169,110,0.1) 0%, transparent 60%),
    linear-gradient(180deg, #1A1410 0%, #120F0C 100%);
  pointer-events: none;
}

/* ── HEADER ── */
/* The nav bar above it already provides the top breathing space, so the
   header's own padding is tighter here than the deck-only layout needed. */
.header {
  position: relative; z-index: 2;
  text-align: center; padding: 20px 20px 0; flex-shrink: 0;
  will-change: transform;
  transition: padding .5s cubic-bezier(.4,0,.2,1);
}
/* Intro state (welcome + summary): a little extra top space. Kept modest since
   the ornament it used to make room for is gone — every pixel spent here is one
   the deck below loses, which pushes the welcome text further down the screen.
   In-flow only — growing the header shrinks the deck, so text moves down with
   it and can never ride up into the header. */
.header.intro { padding-top: clamp(16px, 3vh, 48px); }
/* Only shown on the welcome and summary cards: on a moment card the card's own
   title says where you are, so the subtitle collapses out of the way instead of
   spending height on it. JS controls max-height, opacity, margin for animation. */
.header-sub {
  font-family: var(--font-display);
  font-size: 11px; letter-spacing: 6px;
  color: var(--gold); opacity: 0.7; text-transform: uppercase;
  margin-bottom: 6px;
  overflow: hidden;
}
.header-names {
  font-family: var(--font-name);
  font-size: 34px; font-weight: 300; font-style: italic;
  color: var(--ivory); letter-spacing: 3px; line-height: 1;
  margin-bottom: 8px;
}
.header-date {
  font-size: 9.5px; letter-spacing: 6px;
  color: var(--blush); opacity: 0.45;
  text-transform: uppercase;
}

/* ── DOTS ── */
.dots {
  display: flex; justify-content: center; gap: 8px;
  padding: 16px 0 8px; flex-shrink: 0; position: relative; z-index: 2;
}
.dot {
  width: 5px; height: 5px; border-radius: 50%;
  background: rgba(201,169,110,0.25);
  transition: background .4s, transform .4s; cursor: pointer;
}
.dot.active { background: var(--gold); transform: scale(1.3); }


/* ── DECK ── */
.deck-wrapper {
  flex: 1; position: relative; z-index: 2; overflow: hidden;
  min-height: 0; /* critical: lets flex child shrink */
}
.deck {
  position: absolute; inset: 0;
  display: flex; align-items: center;
  padding: 8px calc((100vw - var(--card-w)) / 2) 16px; gap: var(--card-gap);
  will-change: transform;
}

/* ── CARD ── */
.card {
  flex-shrink: 0;
  width: var(--card-w);
  background: rgba(250,247,242,0.04);
  border: 1px solid rgba(201,169,110,0.15);
  border-radius: 16px;
  padding: 24px 22px 18px;
  backdrop-filter: blur(10px);
  position: relative; overflow: hidden;
  transform: scale(0.85); opacity: 0.45;
  transition: transform .4s cubic-bezier(.4,0,.2,1), opacity .4s ease;
}
.card::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px;
  background: linear-gradient(90deg, transparent, rgba(201,169,110,0.4), transparent);
}

/* moment label — big & elegant like the original titles */
.card-moment-label {
  font-size: 9.5px; letter-spacing: 4px;
  text-transform: uppercase; color: var(--gold); opacity: 0.7;
  margin-bottom: 5px; flex-shrink: 0;
}
.card-header-row {
  display: flex; align-items: baseline;
  justify-content: space-between; gap: 8px;
  margin-bottom: 4px;
}

.card-moment-title {
  font-family: var(--font-display);
  font-size: clamp(21px, 5.6vw, 27px);
  font-weight: 300;
  color: var(--ivory); line-height: 1.1;
  flex-shrink: 1;
}

.emoji-normal {
  font-style: normal;
}
 
.card-time {
  font-family: 'Cinzel', serif;
  font-size: clamp(15px, 4.2vw, 21px);
  font-weight: 300; letter-spacing: 2px;
  color: var(--gold);
  white-space: nowrap; flex-shrink: 0;
}
.card-description {
  font-family: 'Josefin Sans', sans-serif;
  font-size: 15.5px;
  line-height: 1.4;
  color: var(--ivory);
  opacity: 0.75;
  margin-bottom: 18px;
  font-weight: 300;
}

.card-emoji {
  position: absolute; top: 18px; right: 18px;
  font-size: 22px; opacity: 0.45;
}

/* iframe fixed height — compact, no empty space */
.card-iframe-wrap {
  border-radius: 10px; overflow: hidden;
  height: 352px;
}
.card-iframe-wrap iframe {
  display: block; width: 100%; height: 100%;
  border: none; border-radius: 10px;
}


.card.active { transform: scale(1); opacity: 1; }

/* ── HAS-EXTRA: slot becomes a transparent wrapper ── */
.card.has-extra {
  background: transparent;
  border: none;
  padding: 0;
  backdrop-filter: none;
  overflow: visible;
  /* Permanent soft fade band sitting just ABOVE the top edge: anything that
     slides or bounces up dissolves through it instead of being hard-cut, while
     content resting at the edge (front, or the flipped back) stays crisp. The
     mask also hides the card-back parked 56px below the front. */
  -webkit-mask-repeat: no-repeat;            mask-repeat: no-repeat;
  -webkit-mask-position: 0 -28px;            mask-position: 0 -28px;
  -webkit-mask-size: 100% calc(100% + 78px); mask-size: 100% calc(100% + 78px);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 28px, #000 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 28px, #000 100%);
}
.card.has-extra::before { display: none; }

.card-track {
  position: relative;
  width: 100%;
  transition: transform .55s cubic-bezier(.34, 1.45, .64, 1);
  will-change: transform;
}

.card-face {
  width: 100%;
  background: rgba(250,247,242,0.04);
  border: 1px solid rgba(201,169,110,0.15);
  border-radius: 16px;
  padding: 24px 22px 18px;
  backdrop-filter: blur(10px);
  position: relative;
  overflow: hidden;
}
.card-face::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px;
  background: linear-gradient(90deg, transparent, rgba(201,169,110,0.4), transparent);
}

.card-track .card-front { position: relative; }

/* Soft gold glow that breathes along the bottom edge — hints at swipeability */
.card.has-extra .card-track .card-front::after {
  content: '';
  position: absolute;
  bottom: 0; left: 0; right: 0;
  height: 52px;
  background: radial-gradient(
    72% 130% at 50% 112%,
    rgba(228,201,146,0.55) 0%,
    rgba(201,169,110,0.30) 32%,
    rgba(201,169,110,0.10) 56%,
    transparent 78%
  );
  pointer-events: none;
  opacity: 0;
  transition: opacity .35s ease;
}
.card.has-extra.active .card-track .card-front::after {
  animation: bottomGlowPulse 4.5s ease-in-out 1s infinite;
}
.card.has-extra.flipped .card-track .card-front::after {
  opacity: 0;
  animation: none;
}
/* Mirrors the cardBounce keyframes so the glow swells exactly as the card
   lifts: brightest at the top of the rise (7%), dimming as it settles, with a
   matching secondary bump at 21%. */
@keyframes bottomGlowPulse {
  0%   { opacity: 0.25; animation-timing-function: ease-out; }
  7%   { opacity: 1;    animation-timing-function: ease-in; }
  16%  { opacity: 0.4;  animation-timing-function: ease-out; }
  21%  { opacity: 0.6;  animation-timing-function: ease-in; }
  26%  { opacity: 0.25; }
  100% { opacity: 0.25; }
}

/* Whole front face gives a little bounce up, synced with the glow */
.card.has-extra.active .card-track .card-front {
  animation: cardBounce 4.5s ease-in-out 1s infinite;
}
.card.has-extra.flipped .card-track .card-front {
  animation: none;
}
@keyframes cardBounce {
  0%   { transform: translateY(0);    animation-timing-function: ease-out; }
  7%   { transform: translateY(-7px); animation-timing-function: ease-in; }
  16%  { transform: translateY(0);    animation-timing-function: ease-out; }
  21%  { transform: translateY(-2px); animation-timing-function: ease-in; }
  26%  { transform: translateY(0); }
  100% { transform: translateY(0); }
}

/* Once a card has been slid up, retire that card's bounce hint. The glow keeps
   breathing, but drops its secondary bump (which only existed to mirror the
   bounce) — a single peak now. */
.card.has-extra.has-swiped.active .card-track .card-front {
  animation: none;
}
.card.has-extra.has-swiped.active .card-track .card-front::after {
  animation: bottomGlowPulseSingle 4.5s ease-in-out 1s infinite;
}
@keyframes bottomGlowPulseSingle {
  0%   { opacity: 0.25; animation-timing-function: ease-out; }
  7%   { opacity: 1;    animation-timing-function: ease-in-out; }
  45%  { opacity: 0.25; }
  100% { opacity: 0.25; }
}

.card-track .card-back {
  position: absolute;
  top: 100%; left: 0; right: 0;
  margin-top: 56px; /* MUST match FLIP_GAP in script.js */
  min-height: 100%;
  background: linear-gradient(180deg, rgba(26,20,16,0.96) 0%, rgba(18,15,12,0.96) 100%);
  display: flex; flex-direction: column;
}

.card.has-extra.flipped .card-track {
  transform: translateY(calc(-100% - 56px));
}

.card-back-header {
  display: flex; align-items: baseline; justify-content: space-between;
  margin-bottom: 18px;
}
.card-back-title {
  font-family: var(--font-display);
  font-size: clamp(21px, 5.6vw, 27px);
  font-weight: 300;
  color: var(--ivory); line-height: 1.1;
}
.extra-intro {
  font-family: 'Josefin Sans', sans-serif;
  font-size: 15.5px; font-weight: 300;
  line-height: 1.5; letter-spacing: 0.3px;
  color: var(--ivory); opacity: 0.78;
  margin: 0 0 18px;
}
/* When an intro is the only content (no rows), let it fill the back. */
.extra-intro:last-child { margin-bottom: 0; }
.card-back-rows {
  flex: 1; overflow-y: auto;
  display: flex; flex-direction: column;
}
.extra-row {
  padding: 12px 2px;
  border-bottom: 1px solid rgba(201,169,110,0.12);
}
.extra-row:last-child { border-bottom: none; }
.extra-row .row-label {
  display: block;
  font-family: 'Josefin Sans', sans-serif;
  font-size: 10px; font-weight: 300;
  letter-spacing: 3px; text-transform: uppercase;
  color: var(--gold); opacity: 0.8;
  margin-bottom: 2px;
}
.extra-row .row-value {
  display: block;
  font-family: var(--font-display);
  font-size: 16px; font-weight: 300;
  color: var(--ivory); opacity: 0.85;
  line-height: 1.4; letter-spacing: 0.3px;
}

/* ── SUMMARY CARD ── */
.card.card-summary {
  display: flex;
  flex-direction: column;
  align-items: center;
  height: 100%;
  overflow-y: auto;
  background: transparent;
  border: none;
  backdrop-filter: none;
  padding: 0;
  min-height: unset;
}
.card.card-summary::before { display: none; }

.summary-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 20px;
  max-width: 480px;
  margin: auto 0;
  width: 100%;
}

.summary-divider {
  width: 50px; height: 1px;
  background: rgba(201,169,110,0.4);
  margin: 24px auto 28px;
}

.summary-greeting {
  font-family: 'Josefin Sans', sans-serif;
  font-size: 14px; letter-spacing: 6px;
  text-transform: uppercase;
  color: var(--gold); opacity: 0.8;
  margin-bottom: 32px;
}

.summary-detail-row {
  width: 100%;
  padding: 14px 0;
  border-bottom: 1px solid rgba(201,169,110,0.12);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
.summary-detail-row:last-child { border-bottom: none; }

.summary-label {
  font-family: 'Josefin Sans', sans-serif;
  font-size: 10px; font-weight: 300;
  letter-spacing: 4px; text-transform: uppercase;
  color: var(--gold); opacity: 0.7;
}

.summary-value {
  font-family: var(--font-display);
  font-size: 17px; font-weight: 300;
  color: var(--ivory); opacity: 0.8;
  line-height: 1.5; letter-spacing: 0.5px;
}

.summary-link {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: rgba(201,169,110,0.5);
  text-underline-offset: 3px;
  cursor: pointer;
}
.summary-paid {
  margin-left: 4px;
  opacity: 0.7;
}
/* A detail that exists but has not been agreed yet — quieter than a real value,
   so the eye passes over it. */
.summary-pending {
  opacity: 0.55;
}

@media (min-width: 768px) {
  :root { --card-w: 480px; --card-gap: 64px; }

  .lock-header { padding: 42px 0 32px; }
}

/* The welcome and summary groups are auto-centred in the deck, but the deck
   starts below the header, so dead centre of the deck reads as low on screen.
   Padding their bottom lifts them half as far up, landing them near the middle
   of the window instead.
   The lift is whatever the window has to spare: the taller group needs ~600px
   of deck, so anything past that is free height. That self-limits — a short
   screen gets none and can never be made to scroll — where a fixed vh fraction
   would overshoot. The svh repeat matters on mobile, where 100vh optimistically
   assumes the browser toolbar is hidden. */
.welcome-card-inner,
.summary-inner {
  padding-bottom: clamp(20px, calc(100vh - 660px), 320px);
  padding-bottom: clamp(20px, calc(100svh - 660px), 320px);
}