/* ============================================================
   Seek And Find motion system: "seek, then find".
   The logo is a magnifying glass, so things on this site are FOUND:
   photos open through a lens, scripture is read into the light line
   by line, gold rules draw, today's hours light up.
   Loaded after styles.css on every page. Paired with motion.js.
   Rules: transform / opacity / clip-path / mask only. No-JS and
   reduced-motion both show everything at rest, instantly.
   ============================================================ */

:root {
  --t-fast: 0.25s;
  --t-base: 0.6s;
  --t-slow: 1s;
  --t-hero: 1.4s;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);   /* entrances */
  --ease-in: cubic-bezier(0.55, 0, 1, 0.45);   /* exits, 0.35s max */
  --ease-io: cubic-bezier(0.65, 0, 0.35, 1);   /* the lens, A to B */
  --stagger: 80ms;
}

/* Animatable mask positions. initial-value is the finished state, so a
   browser without @property simply shows the text. */
@property --read { syntax: "<percentage>"; inherits: false; initial-value: 130%; }
@property --sweep { syntax: "<percentage>"; inherits: false; initial-value: 130%; }

/* ---------- reveal tiers (replace the one-size fade-up) ---------- */
/* rise: the default. Shorter travel, longer settle than before. */
html.js .reveal[data-m] { transition: opacity 0.8s var(--ease-out) var(--d, 0s), transform 0.9s var(--ease-out) var(--d, 0s); }
html.js .reveal[data-m="rise"] { opacity: 0; transform: translateY(18px); }
html.js .reveal[data-m="rise"].in { opacity: 1; transform: none; }
/* still: the wrapper stays put, its children carry the motion */
html.js .reveal[data-m="still"] { opacity: 1; transform: none; }

/* lens: photos are found. A circle opens from a point inside the frame
   while the photo settles from 1.08 to 1. */
html.js .reveal [data-lens],
html.js .reveal[data-lens] {
  clip-path: circle(0% at var(--lx, 50%) var(--ly, 50%));
  transform: scale(1.08);
  transition: clip-path 1.15s var(--ease-io) var(--ld, 0s), transform 1.6s var(--ease-out) var(--ld, 0s);
}
html.js .reveal.in [data-lens],
html.js .reveal.in[data-lens] {
  clip-path: circle(150% at var(--lx, 50%) var(--ly, 50%));
  transform: none;
}

/* read: scripture comes into the light from the top, line by line */
html.js .reveal [data-read] {
  --read: -10%;
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(var(--read) - 22%), transparent var(--read));
  mask-image: linear-gradient(to bottom, #000 calc(var(--read) - 22%), transparent var(--read));
  transition: --read 1.8s var(--ease-io) 0.15s;
  /* A mask paints only inside the border box, and the gold quote mark
     rises about 0.6rem above it (line-height 0.6). Pad the box up to hold
     the mark, and pull it back so the layout does not move. */
  padding-top: 0.8rem;
  margin-top: -0.8rem;
}
html.js .reveal.in [data-read] { --read: 130%; }
/* the gold quote mark lands first, the reference arrives last */
html.js .reveal .verse blockquote::before,
html.js .verse.reveal blockquote::before { opacity: 0; transform: translateY(10px) scale(0.6); transition: opacity 0.5s var(--ease-out), transform 0.7s cubic-bezier(0.34, 1.4, 0.64, 1); }
html.js .reveal.in .verse blockquote::before,
html.js .verse.reveal.in blockquote::before { opacity: 1; transform: none; }
html.js .reveal .verse cite,
html.js .verse.reveal cite { display: inline-block; opacity: 0; transform: translateY(6px); transition: opacity 0.6s var(--ease-out) 1.35s, transform 0.6s var(--ease-out) 1.35s; }
html.js .reveal.in .verse cite,
html.js .verse.reveal.in cite { opacity: 1; transform: none; }

/* rule: every eyebrow's gold line draws in from the left */
html.js .overline::before { transform: scaleX(0); transform-origin: left center; transition: transform 0.9s var(--ease-out) 0.1s; }
html.js .overline.in::before { transform: none; }

/* ---------- page heroes: the photo settles, the headline is swept into light ---------- */
.hero { overflow: hidden; }
html.js .hero > img.hero-bg,
html.js .hero picture { animation: sf-settle 2.2s var(--ease-out) both; }
@keyframes sf-settle { from { scale: 1.09; } to { scale: 1; } }

html.js .hero h1 {
  -webkit-mask-image: linear-gradient(100deg, #000 calc(var(--sweep) - 30%), transparent var(--sweep));
  mask-image: linear-gradient(100deg, #000 calc(var(--sweep) - 30%), transparent var(--sweep));
  animation: sf-sweep 1.3s cubic-bezier(0.45, 0, 0.2, 1) 0.1s both, sf-lift 1.2s var(--ease-out) 0.1s both;
}
@keyframes sf-sweep { from { --sweep: 0%; } to { --sweep: 130%; } }
@keyframes sf-lift { from { transform: translateY(14px); } to { transform: none; } }
html.js .hero .overline { animation: sf-fade 0.8s var(--ease-out) both; }
html.js .hero .overline::before { transform: none; animation: sf-draw 1s var(--ease-out) 0.1s both; }
@keyframes sf-draw { from { transform: scaleX(0); } to { transform: none; } }
html.js .hero .lede { animation: sf-rise 1s var(--ease-out) 0.75s both; }
html.js .hero .btn-row > * { animation: sf-rise 0.9s var(--ease-out) calc(0.95s + var(--i, 0) * 70ms) both; }
@keyframes sf-fade { from { opacity: 0; } }
@keyframes sf-rise { from { opacity: 0; transform: translateY(14px); } }

/* Desktop only: the hero photo drifts slower than the page (zero JS). */
@supports (animation-timeline: scroll()) {
  @media (min-width: 900px) and (prefers-reduced-motion: no-preference) {
    /* 14% of extra photo above the frame, so the drift never opens a gap */
    html.js .hero > img.hero-bg,
    html.js .hero picture {
      top: -14%; bottom: auto; height: 114%;
      animation: sf-settle 2.2s var(--ease-out) both, sf-drift linear both;
      animation-timeline: auto, scroll(root);
      animation-range: normal, 0 90vh;
      transform-origin: center top;
    }
    @keyframes sf-drift { from { translate: 0 0; } to { translate: 0 12%; } }
  }
}

/* ---------- live storefront: open-now chip + today's hours ---------- */
.open-chip {
  display: inline-flex; align-items: center; gap: 0.45rem;
  font-weight: 700; white-space: nowrap;
}
.open-chip::before {
  content: ""; width: 0.55rem; height: 0.55rem; border-radius: 50%;
  background: #b9a89a; flex: none;
}
.open-chip[data-state="open"]::before { background: #7bd389; box-shadow: 0 0 0 0 rgba(123, 211, 137, 0.6); animation: sf-pulse 2.4s var(--ease-out) infinite; }
.open-chip[data-state="soon"]::before { background: var(--gold); }
@keyframes sf-pulse { 0% { box-shadow: 0 0 0 0 rgba(123, 211, 137, 0.55); } 70%, 100% { box-shadow: 0 0 0 0.45rem rgba(123, 211, 137, 0); } }
.topbar .open-chip .open-detail { font-weight: 400; opacity: 0.85; }
@media (max-width: 560px) {
  .topbar .open-chip .open-detail { display: none; }
}

/* Any hours row tagged data-days lights up on its own day. */
.is-today {
  position: relative;
  font-weight: 700;
  color: var(--maroon);
}
.is-today::after {
  content: attr(data-today-label);
  display: inline-block; margin-left: 0.5rem; padding: 0.05rem 0.5rem;
  border-radius: 999px; background: var(--gold-soft); color: var(--maroon-ink);
  font-size: 0.72em; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  vertical-align: 0.12em;
}
tr.is-today::after { content: none; }
tr.is-today > * { background: #fbf3df; }
tr.is-today > :first-child { box-shadow: inset 4px 0 0 var(--gold); }
.section--maroon .is-today, .site-footer .is-today { color: var(--white); }

/* ---------- touch and hover ---------- */
.site-header { transition: box-shadow 0.35s var(--ease-out); }
.site-header.is-scrolled { box-shadow: 0 6px 22px -14px rgba(63, 15, 23, 0.45); }

.btn:active { scale: 0.97; }

@media (hover: hover) and (pointer: fine) {
  /* only cards you can act on lift */
  /* Lift and zoom use the independent translate / scale properties so
     they never fight the reveal's transform. The transition lists repeat
     the reveal's entries because these selectors must outrank it. */
  .card:has(a),
  html.js .reveal[data-m].card:has(a) {
    transition: opacity 0.8s var(--ease-out) var(--d, 0s), transform 0.9s var(--ease-out) var(--d, 0s),
      translate 0.45s var(--ease-out), box-shadow 0.45s var(--ease-out);
  }
  .card:has(a):hover { translate: 0 -4px; box-shadow: 0 22px 40px -18px rgba(63, 15, 23, 0.32); }
  .card--photo img,
  html.js .reveal .card--photo img[data-lens],
  html.js .reveal.card--photo img[data-lens] {
    transition: clip-path 1.15s var(--ease-io) var(--ld, 0s), transform 1.6s var(--ease-out) var(--ld, 0s), scale 0.7s var(--ease-out);
  }
  .card--photo:hover img { scale: 1.04; }

  /* links grow a gold underline instead of snapping one on */
  .card-link {
    background: linear-gradient(var(--gold), var(--gold)) 0 100% / 0 2px no-repeat;
    padding-bottom: 2px; transition: background-size 0.4s var(--ease-out);
  }
  .card-link:hover { text-decoration: none; background-size: 100% 2px; }

  @media (min-width: 901px) {
    .site-nav > ul > li > a:not(.btn):not([aria-current]) {
      background: linear-gradient(var(--gold), var(--gold)) 50% 100% / 0 2px no-repeat;
      transition: background-size 0.35s var(--ease-out);
    }
    .site-nav > ul > li > a:not(.btn):not([aria-current]):hover { background-size: 70% 2px; }
  }
}
.card--photo { isolation: isolate; }

/* ---------- video posters get a real play button ---------- */
.sf-video__frame { position: relative; }
.sf-video__frame--tall { display: none; }
@media (max-width: 700px) {
  .sf-video__frame--wide { display: none; }
  .sf-video__frame--tall { display: block; }
}
/* A pill in the lower-left corner, above the native control bar, so it
   never covers the poster's own words. Mouse devices only: phones
   already draw their own play button. */
.sf-play { display: none; }
@media (hover: hover) and (pointer: fine) {
  .sf-play {
    position: absolute; left: 1.1rem; bottom: 3.4rem;
    display: inline-flex; align-items: center; gap: 0.6rem;
    padding: 0.55rem 1.1rem 0.55rem 0.8rem; border-radius: 999px;
    border: 0; cursor: pointer; color: #fff;
    font: 700 0.95rem/1 var(--font-body);
    background: rgba(63, 15, 23, 0.82);
    box-shadow: 0 10px 24px -10px rgba(0, 0, 0, 0.55);
    transition: translate 0.35s var(--ease-out), background 0.25s var(--ease-out), opacity 0.3s var(--ease-in);
  }
  .sf-play::before {
    content: ""; width: 1.7rem; height: 1.7rem; border-radius: 50%; flex: none;
    background: var(--gold) no-repeat 58% 50% / 0.6rem
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 12'%3E%3Cpath d='M0 0l10 6-10 6z' fill='%233f0f17'/%3E%3C/svg%3E");
  }
  .sf-play:hover { translate: 0 -2px; background: var(--maroon); }
}
.sf-play:focus-visible { outline: 3px solid var(--gold); outline-offset: 4px; }
.sf-video__frame.is-playing .sf-play { opacity: 0; pointer-events: none; }

/* ---------- page to page: the header stays, the page slides in ---------- */
@view-transition { navigation: auto; }
.topbar { view-transition-name: sf-topbar; }
.site-header { view-transition-name: sf-header; }
::view-transition-old(root) { animation: sf-vt-out 0.22s var(--ease-in) both; }
::view-transition-new(root) { animation: sf-vt-in 0.5s var(--ease-out) both; }
@keyframes sf-vt-out { to { opacity: 0; transform: translateY(-6px); } }
@keyframes sf-vt-in { from { opacity: 0; transform: translateY(14px); } }

/* ---------- non-negotiable: reduced motion gets the finished page ---------- */
@media (prefers-reduced-motion: reduce) {
  html.js .reveal[data-m], html.js .reveal [data-lens], html.js .reveal[data-lens],
  html.js .reveal [data-read], html.js .overline::before,
  html.js .reveal .verse blockquote::before, html.js .verse.reveal blockquote::before,
  html.js .reveal .verse cite, html.js .verse.reveal cite {
    opacity: 1 !important; transform: none !important; clip-path: none !important;
    -webkit-mask-image: none !important; mask-image: none !important; transition: none !important;
  }
  html.js .hero > img.hero-bg, html.js .hero picture, html.js .hero h1, html.js .hero .overline,
  html.js .hero .overline::before, html.js .hero .lede, html.js .hero .btn-row > * {
    animation: none !important; -webkit-mask-image: none !important; mask-image: none !important;
  }
  .open-chip::before, .sf-play::after { animation: none !important; }
  .card:has(a), .card:has(a):hover, .card--photo img { transition: none !important; transform: none !important; translate: none !important; scale: none !important; }
  @view-transition { navigation: none; }
}

/* topbar: the live chip leads, the posted hours follow it */
.topbar .open-chip + .hours-note { margin-right: auto; }
@media (max-width: 560px) {
  .topbar { font-size: 0.875rem; }
  .topbar .container { gap: 0.7rem; flex-wrap: nowrap; }
  .topbar-right { gap: 0.7rem; }
  .topbar .lang-toggle { font-size: 0.875rem; padding: 0.1rem 0.6rem; }
}

/* ============================================================
   Page-specific rules (merged from the page reviews)
   ============================================================ */

/* thrift-furniture: proposed rules. Every selector is a class added in
   thrift-store.html / furniture.html only, so no other page changes. */

/* Defect: furniture "large empty span" by the video, and the same stacked gap
   between the thrift video and its scripture band. Two plain-cream sections in a
   row stack two section paddings (224px at 1440, 112px at 390) into a dead band.
   A section marked joined continues the one above it, so it drops its top padding. */
.section.section--joined { padding-top: 0; }

/* Defect: thrift "Come see us" blank cell. The grid is complete (7 photos, 8 cells);
   the hole is a 1600px photo still downloading (1.2 MB for the set). Tint each cell
   so an unloaded photo reads as a tile, not a missing one. The lens clips the tint
   too, so nothing shows before the find. */
.gallery--store img { background: var(--cream); }

/* Moment: donation checklists read as a wall of small text. A hairline between
   items and a little more air, the same rhythm as the numbered donation steps,
   so each line reads as one rule. Copy and size untouched. */
.give-cols--ruled .check-list li { padding-top: 0.5rem; padding-bottom: 0.5rem; }
.give-cols--ruled .check-list li + li { border-top: 1px solid var(--line); }
.give-cols--ruled .check-list li::before { top: 0.5rem; }
/* Phone: the two lists stack; give the "do not donate" list its own breath. */
@media (max-width: 720px) { .give-cols--ruled { row-gap: 2rem; } }

/* chapel-cafe: proposed CSS from the "cafe" page agent. All rules are
   scoped to .cafe-gallery, a class added only on chapel-cafe.html. */

/* Defect: on phone the three gallery photos shrink to 109x82 thumbnails
   (the markup's inline repeat(3,1fr) beats the base 820px rule), so they
   read as orphaned scraps. Phone/tablet becomes a deliberate set: the
   portrait stained-glass photo stands tall on the left, kitchen and
   serving counter stack on the right. No single dangling tile.
   !important is needed only to beat the existing inline grid style. */
@media (max-width: 820px) {
  .cafe-gallery { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
  .cafe-gallery img:nth-child(1) { grid-column: 2; grid-row: 1; }
  .cafe-gallery img:nth-child(2) { grid-column: 2; grid-row: 2; }
  .cafe-gallery img:nth-child(3) { grid-column: 1; grid-row: 1 / span 2; aspect-ratio: auto; height: 100%; min-height: 0; }
}

/* Moment: the three photos are found one after another instead of all
   at once (motion.js spaces gallery lenses 90ms apart, which reads as
   simultaneous). 220ms apart, 0.44s total span, inside the stagger budget.
   !important beats the per-image --ld that motion.js sets inline. */
html.js .cafe-gallery img:nth-child(1) { --ld: 0s !important; }
html.js .cafe-gallery img:nth-child(2) { --ld: 0.22s !important; }
html.js .cafe-gallery img:nth-child(3) { --ld: 0.44s !important; }
/* Phone order follows the new layout: tall photo first, then right column. */
@media (max-width: 820px) {
  html.js .cafe-gallery img:nth-child(3) { --ld: 0s !important; }
  html.js .cafe-gallery img:nth-child(1) { --ld: 0.22s !important; }
  html.js .cafe-gallery img:nth-child(2) { --ld: 0.44s !important; }
}

/* ============================================================
   /ministry page: proposed CSS (agent "ministry").
   Hooks added in ministry.html: .ministry-grid on the two course
   grids and the Grow in Faith grid; .ministry-courses on the two
   course grids only.
   ============================================================ */

/* Defect: letterbox bars on all 12 course videos. The video box takes
   its 480x360 attributes (4:3) and with width:100% renders 269x360 on
   desktop and 310x360 on phone, while every poster in
   assets/img/posters/ is 480x320 (3:2). Matching the box to the poster
   shows each poster edge to edge, and the card gets ~180px shorter. */
.ministry-courses .video-card video { height: auto; aspect-ratio: 3 / 2; }

/* Moment: the grids reveal as a diagonal wave, row by row, 80ms apart
   (max 4 steps, 0 to .24s), instead of one long index stagger. motion.js
   writes --d inline from sibling index, so these need !important.
   Phone (one column): every card arrives on its own as you scroll, no lag. */
.ministry-grid > .reveal { --d: 0s !important; }
@media (min-width: 621px) and (max-width: 900px) {
  .ministry-grid > .reveal:nth-child(2),
  .ministry-grid > .reveal:nth-child(3) { --d: 0.08s !important; }
  .ministry-grid > .reveal:nth-child(4),
  .ministry-grid > .reveal:nth-child(5) { --d: 0.16s !important; }
  .ministry-grid > .reveal:nth-child(6) { --d: 0.24s !important; }
}
@media (min-width: 901px) {
  .ministry-grid > .reveal:nth-child(2),
  .ministry-grid > .reveal:nth-child(4) { --d: 0.08s !important; }
  .ministry-grid > .reveal:nth-child(3),
  .ministry-grid > .reveal:nth-child(5) { --d: 0.16s !important; }
  .ministry-grid > .reveal:nth-child(6) { --d: 0.24s !important; }
}

/* Moment: video cards are actionable (they play) but hold no <a>, so the
   global .card:has(a) lift skips them. Same lift as the global rule, plus
   keyboard focus. The transition list repeats the reveal's own entries so
   the entrance is unchanged. Resource cards use the global rule. */
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  html.js .ministry-courses > .video-card.reveal[data-m] {
    transition: opacity 0.8s var(--ease-out) var(--d, 0s), transform 0.9s var(--ease-out) var(--d, 0s),
      translate 0.45s var(--ease-out), box-shadow 0.45s var(--ease-out);
  }
  .ministry-courses > .video-card:hover,
  .ministry-courses > .video-card:focus-within {
    translate: 0 -4px;
    box-shadow: 0 22px 40px -18px rgba(63, 15, 23, 0.32);
  }
}

/* ---------- about-contact agent: proposed rules ---------- */

/* About, mission statement band: the statement is a mission statement, not
   scripture, so it gets the plain block rise only. The markup sets
   data-m="rise" (no line read-in); these two rules stop the quote mark pop
   and the late cite so nothing moves beyond the single rise. */
html.js .verse--statement.reveal blockquote::before,
html.js .verse--statement.reveal cite {
  opacity: 1 !important; transform: none !important; transition: none !important;
}

/* About, Our Team on phone: 9 people in 2 columns left one tile alone on
   the last row. Three columns give a balanced 3 x 3 at phone widths. */
@media (max-width: 560px) {
  .people-grid.people-grid--team { gap: 1.1rem 0.7rem; }
  .people-grid.people-grid--team .person-card { width: calc((100% - 1.4rem) / 3); }
  .people-grid--team .person-card .person-name { font-size: 0.95rem; }
  .people-grid--team .person-card .person-role { font-size: 0.8rem; }
  .people-grid--team .person-card .initials-tile { font-size: 1.8rem; }
}

/* Contact, map beside the address column: the map was a fixed 4:3 box at
   the top of a tall column, leaving a large empty area under it. On
   two-column widths the map now runs the full height of the contact info
   (the inline align-items: start on .split needs !important to override). */
@media (min-width: 821px) {
  .contact-split { align-items: stretch !important; }
  .contact-split > .reveal:last-child { display: flex; flex-direction: column; }
  .contact-split .map-wrap { aspect-ratio: auto; flex: 1; min-height: 360px; }
}

/* ============================================================
   Proposed CSS: get-involved.html + newsletter.html (agent "involved").
   Every rule is scoped to a hook that exists only on these two pages:
   .volunteer-badge, .gi-video, #newsletter (get-involved) and .nl-page
   (newsletter). Motion is transform / opacity only and sits inside
   prefers-reduced-motion: no-preference + html.js, so no-JS and
   reduced-motion both get the finished page.
   ============================================================ */

/* Defect: large empty span on desktop under the volunteer badge.
   The volunteer split and the video were two sections, so 2 x 7rem of
   section padding (224px at 1440) stood empty between them. The video
   is the same story as the badge, so it follows on directly. */
.gi-video { padding-top: 0; }

@media (prefers-reduced-motion: no-preference) {
  /* Moment: "We love our volunteers" badge settles in. The disc arrives
     from 0.92 and overshoots to 1.025 (2.5%) before resting; the gold
     ring then closes in from 1.12 like a lens finding focus, and the
     heart lands last. Keyed to the wrapper's .in from site.js; the
     wrapper is data-m="still" so only the badge moves. */
  html.js .reveal[data-m="still"] > .volunteer-badge {
    opacity: 0; transform: scale(0.92);
  }
  html.js .reveal.in > .volunteer-badge {
    animation: gi-badge 1.1s var(--ease-out) var(--d, 0s) both;
  }
  @keyframes gi-badge {
    0% { opacity: 0; transform: scale(0.92); }
    55% { opacity: 1; transform: scale(1.025); }
    100% { opacity: 1; transform: none; }
  }
  html.js .reveal[data-m="still"] > .volunteer-badge::before { opacity: 0; transform: scale(1.12); }
  html.js .reveal.in > .volunteer-badge::before {
    animation: gi-ring 1s var(--ease-io) calc(var(--d, 0s) + 0.35s) both;
  }
  @keyframes gi-ring {
    from { opacity: 0; transform: scale(1.12); }
    to { opacity: 1; transform: none; }
  }
  html.js .reveal[data-m="still"] > .volunteer-badge .vb-heart { opacity: 0; }
  html.js .reveal.in > .volunteer-badge .vb-heart {
    animation: gi-heart 0.6s var(--ease-out) calc(var(--d, 0s) + 0.95s) both;
  }
  /* explicit end value: the resting style above is opacity 0 */
  @keyframes gi-heart { from { opacity: 0; } to { opacity: 1; } }

  /* Newsletter page: the headline is swept into light like every page
     hero (reuses motion.css sf-sweep / sf-lift), and the form card waits
     for it before rising, so the page reads heading first, then card. */
  html.js .nl-page h1 {
    -webkit-mask-image: linear-gradient(100deg, #000 calc(var(--sweep) - 30%), transparent var(--sweep));
    mask-image: linear-gradient(100deg, #000 calc(var(--sweep) - 30%), transparent var(--sweep));
    animation: sf-sweep 1.3s cubic-bezier(0.45, 0, 0.2, 1) 0.15s both, sf-lift 1.2s var(--ease-out) 0.15s both;
  }
  /* motion.js writes --d inline, so delay the transition itself */
  html.js .nl-page .form-grid.reveal { transition-delay: 0.45s; }

  /* Signup result (both pages): the status line arrives instead of
     popping in. An animation applied by :not(:empty) runs each time the
     text appears; form behavior is untouched. */
  .nl-page [data-newsletter-status]:not(:empty),
  #newsletter [data-newsletter-status]:not(:empty) {
    animation: sf-rise 0.6s var(--ease-out) both;
  }
}

/* Newsletter page presence: the bare form on cream becomes a white card
   with depth on the deeper cream, using the site's own card vocabulary
   (white, --radius, --shadow, gold top edge as on the home facts card). */
.nl-page { background: var(--cream-deep); }
.nl-page .form-grid {
  background: var(--white);
  border-radius: var(--radius);
  border-top: 4px solid var(--gold);
  box-shadow: 0 22px 44px -22px rgba(63, 15, 23, 0.28);
  padding: clamp(1.3rem, 4vw, 2.4rem);
}
/* Status line reads as a confirmation note when it has text, on both pages */
.nl-page [data-newsletter-status]:not(:empty),
#newsletter [data-newsletter-status]:not(:empty) {
  font-weight: 700; color: var(--maroon-ink);
  padding-left: 0.8rem; box-shadow: inset 3px 0 0 var(--gold);
}
