/* chefrosiesanchez.com
   Art direction: CRS-Website-Design-Doc.md
   Technical design: docs/superpowers/specs/2026-09-03-brand-site-design.md

   Two ideas carry this file.

   1. --ground and --rule are a matched pair, flipped together by site.js as
      sections pass. The frame, the page background, the body colour and the
      monogram all read from them. Never hard-code charcoal or beige anywhere a
      section boundary can reach.

   2. Sections are opaque and sit above the interstitials, so an interstitial's
      pinned photograph is genuinely occluded by the section arriving over it.
      That occlusion IS the depth cue in art doc 2.5. Without it the pin is
      just a stall. */

/* ---------- Tokens ---------- */

:root {
  --charcoal: #34312B;
  --beige: #E5DFD3;
  --butter: #F1E5AC;

  /* Charcoal is the opening ground (hero). site.js takes over from here. */
  --ground: #34312B;
  --rule: #F1E5AC;
  --body: rgb(229 223 211 / 0.85);

  /* Spacing scale. Seeded from the frame inset so 24 and 40 are members of
     the scale rather than exceptions to it. Viewport-relative padding is
     deliberately NOT used for page rhythm: the frame inset is fixed, so vh
     padding makes the ratio between page margin and text margin drift with
     window height. A booklet's margins do not resize with the reader. */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 16px;
  --s-4: 24px;   /* == mobile frame inset */
  --s-5: 40px;   /* == desktop frame inset */
  --s-6: 64px;
  --s-7: 104px;
  --s-8: 168px;
  --s-9: 272px;

  --spread-y: var(--s-7);
  --spread-y-wide: var(--s-8);

  --frame-inset: var(--s-4);
  --gutter: var(--s-4);
  --ground-fade: 700ms;

  --nav-gap: 90px;   /* width of the break the nav makes in the top rule */
  --mono-gap: 34px;   /* half-width of the break the monogram makes */
}

@media (min-width: 900px) {
  :root {
    --frame-inset: var(--s-5);
    --gutter: var(--s-5);
    --spread-y: var(--s-8);
    --spread-y-wide: var(--s-9);
    --nav-gap: 190px;
  }
}

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

html {
  background-color: var(--ground);
  transition: background-color var(--ground-fade) ease;
  -webkit-text-size-adjust: 100%;

  /* On html, NOT on body. The bleeds deliberately run past the viewport edge,
     so something has to clip them — but `overflow-x: hidden` on body makes
     body a scroll container, which is the classic cause of position:sticky
     failing to pin on iOS Safari. The interstitials depend entirely on sticky,
     so that must not be load-bearing. On html the viewport propagation rule
     applies and sticky keeps resolving against the viewport. */
  overflow-x: hidden;
}

img, svg, picture { display: block; max-width: 100%; }

/* ---------- Type ---------- */

@font-face {
  font-family: 'Work Sans';
  src: url('/fonts/work-sans-var.woff2') format('woff2-variations');
  font-weight: 300 400;
  font-style: normal;
  font-display: swap;
}

body {
  font-family: 'Work Sans', system-ui, -apple-system, sans-serif;
  font-weight: 300;
  font-size: 17px;
  line-height: 1.6;
  color: var(--body);
  transition: color var(--ground-fade) ease;
}

@media (min-width: 900px) { body { font-size: 18px; } }

p { margin: 0 0 1.35em; text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

.statement {
  font-size: 22px; font-weight: 300; line-height: 1.45;
  max-width: 24em; margin: 0 0 1.2em;
  color: var(--rule);
  text-wrap: balance;
  transition: color var(--ground-fade) ease;
}

/* Section heads. Tracked caps are the display tier the art doc's 2.3 defines;
   without them the page has body and statements and nothing else. */
.label {
  font-size: 12px; font-weight: 400;
  text-transform: uppercase; letter-spacing: 0.14em;
}

.section-head {
  font-size: 12px; font-weight: 400;
  text-transform: uppercase; letter-spacing: 0.14em;
  margin: 0 0 var(--s-4);
  color: var(--rule);
  transition: color var(--ground-fade) ease;
}

.measure { max-width: 34em; }

@media (min-width: 900px) {
  .statement { font-size: 30px; }
  .label, .section-head { font-size: 13px; }
}

/* Inline links. A resting hairline at 45% gives the affordance the art doc's
   3.4 asks for ("charcoal, underlined"); 2.5's left-to-right draw belongs to
   the hover state, not the resting one. */
.link {
  color: inherit; text-decoration: none; font-weight: 400;
  position: relative; padding-bottom: 2px;
}
.link::after {
  content: ''; position: absolute; left: 0; bottom: 0;
  width: 100%; height: 1px; background: currentColor;
  transform: scaleX(1); transform-origin: left;
  opacity: 0.45;
  transition: opacity 300ms ease;
}
.link:hover::after { opacity: 1; }
.link:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ---------- The frame ---------- */

/* The rule is BROKEN by masking, never by painting a ground-coloured chip
   over it. Over photography — more than half this page — a chip stamps an
   opaque rectangle onto the image and announces that the frame is a separate
   layer. Masking cuts a real gap, which is what a rule printed on a page and
   interrupted by a mark actually looks like. */
.frame {
  position: fixed;
  inset: var(--frame-inset);
  border: 1px solid var(--rule);
  pointer-events: none;
  z-index: 100;
  transition: border-color var(--ground-fade) ease;

  --mask-top: linear-gradient(to right, #000 0 calc(100% - 20px - var(--nav-gap)),
              transparent calc(100% - 20px - var(--nav-gap)) calc(100% - 20px),
              #000 calc(100% - 20px) 100%);
  --mask-side: linear-gradient(#000, #000);
  --mask-bottom: linear-gradient(to right, #000 0 calc(50% - var(--mono-gap)),
                 transparent calc(50% - var(--mono-gap)) calc(50% + var(--mono-gap)),
                 #000 calc(50% + var(--mono-gap)) 100%);

  -webkit-mask-image: var(--mask-top), var(--mask-side), var(--mask-side), var(--mask-bottom);
          mask-image: var(--mask-top), var(--mask-side), var(--mask-side), var(--mask-bottom);
  -webkit-mask-size: 100% 1px, 1px 100%, 1px 100%, 100% 1px;
          mask-size: 100% 1px, 1px 100%, 1px 100%, 100% 1px;
  -webkit-mask-position: top, left, right, bottom;
          mask-position: top, left, right, bottom;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}

/* Keep the frame opening continuous behind the navigation links. */
.frame__nav {
  position: fixed;
  top: var(--frame-inset);
  right: calc(var(--frame-inset) + 20px);
  transform: translateY(-50%);
  display: flex; gap: 0;
  margin-right: -0.14em;        /* optical: trailing letter-space */
  z-index: 101;
  pointer-events: auto;
  transition: opacity 300ms ease;
}

.frame__nav a {
  color: var(--rule);
  text-decoration: none;
  font-size: 12px; font-weight: 400;
  text-transform: uppercase; letter-spacing: 0.14em;
  padding: var(--s-3) 14px;     /* yields a 48px touch target */
  transition: opacity 200ms ease, color var(--ground-fade) ease;
}
.frame__nav a:hover { opacity: 0.7; }
.frame__nav a:focus-visible { outline: 1px solid currentColor; outline-offset: 2px; }

html.scrolling .frame__nav { opacity: 0.4; }

@media (max-width: 899px) { .nav-home, .nav-contact { display: none; } }

.frame__monogram {
  position: fixed;
  bottom: var(--frame-inset);
  left: 50%;
  transform: translate(-50%, 50%);
  background: none;
  border: 0;
  padding: var(--s-2) 14px;
  min-width: 48px; min-height: 48px;
  display: grid; place-items: center;
  color: var(--rule);
  cursor: pointer;
  z-index: 101;
  pointer-events: auto;
  transition: color var(--ground-fade) ease;
}
.frame__monogram svg { height: 28px; width: auto; fill: currentColor; }
.frame__monogram:focus-visible { outline: 1px solid currentColor; outline-offset: 2px; }

/* ---------- Sections ---------- */

/* Opaque and above the interstitials, so they occlude the pinned photo.
   Each carries its literal ground, so a section's colour is correct the
   instant it appears — the html-level --ground only has to drive the frame
   and the overscroll area. */
main > section {
  position: relative;
  z-index: 1;
  padding: var(--spread-y) calc(var(--frame-inset) + var(--gutter));
}
main > section[data-ground="charcoal"] { background-color: var(--charcoal); }
main > section[data-ground="beige"]    { background-color: var(--beige); }

/* ---------- Interstitials ---------- */

/* 200vh container pulled back 100vh, so the next section rides over the
   pinned photograph for its whole entrance. Net scroll cost 100vh, all of it
   occupied by an arriving section rather than by a freeze. lvh (not svh) so
   the photo overfills rather than letterboxes when iOS chrome retracts. */
.interstitial {
  height: 200lvh;
  margin-bottom: -100lvh;
  padding: 0;
  background: none;
  z-index: 0;
}
.interstitial__inner {
  position: sticky; top: 0;
  height: 100lvh; overflow: hidden;
}
.interstitial__inner picture,
.interstitial__inner img {
  width: 100%; height: 100%; object-fit: cover;
}

/* ---------- Bleeds ---------- */

/* Bleed to exactly the viewport edge — not past it. The image starts at x=0,
   overshooting the frame rule by the full frame inset, which is unambiguous.
   Running further costs nothing visually (you cannot see past the viewport)
   and buys a horizontal scrollbar you then have to clip.
   min-width:0 because a grid item's auto minimum is its min-content size,
   which a <picture> inflates past the track and forces an overflow. */
.bleed, .bleed-left {
  min-width: 0;
  margin-inline: calc(-1 * (var(--frame-inset) + var(--gutter)));
}
.bleed img, .bleed-left img { width: 100%; height: auto; }

/* Contained images on beige read as mounted prints (art doc 2.4). This is
   mutually exclusive with a bleed: a rule at inset 0 whose left and right
   edges are off-screen renders as two stray hairlines. */
.mounted img { border: 1px solid var(--charcoal); }

/* Sized images: the attributes reserve the box, CSS sizes it. */
.how__plates img, .service__media img, .bio__portrait img, .subpage__media img {
  width: 100%; height: auto;
}

/* ---------- Hero (3.1) ---------- */

.hero { padding: 0; height: 100lvh; min-height: 520px; }
.hero__media { position: absolute; inset: 0; }
.hero__media picture, .hero__media img { width: 100%; height: 100%; object-fit: cover; }

.hero__mark {
  position: absolute;
  top: 12%; left: 50%; transform: translateX(-50%);
  width: min(78%, 660px);
  text-align: center;
  color: var(--butter);
}
.hero__mark .wordmark { margin: 0; }
.hero__mark svg { width: 100%; height: auto; }
.hero__place {
  margin: var(--s-3) 0 0;
  letter-spacing: 0.3em;
  text-indent: 0.3em;            /* optical: centre tracked caps */
  color: var(--butter);
}

@media (min-width: 900px) { .hero__mark { top: 14%; width: min(62%, 620px); } }

/* ---------- Statement (3.2) ---------- */

.statement-section { padding-block: var(--spread-y-wide); }

/* A stack, not four paragraphs that happen to be short. */
.statement--indent {
  margin-left: var(--s-6);
  margin-bottom: var(--s-4);
  max-width: calc(24em - var(--s-6));
}
.statement--indent:first-of-type { margin-top: var(--s-6); }

@media (min-width: 900px) {
  .statement--indent { margin-left: var(--s-7); max-width: calc(24em - var(--s-7)); }
}

/* ---------- How it starts (3.4) ---------- */

/* Plates bleed LEFT and the text sits right. This overrides art doc 3.4,
   which specifies a right bleed. Deliberate: with both 3.4 and 3.7 as written
   the alternation can only ever be L,L,R,R — two pairs, not the rhythm 2.3
   calls for. Bleeding the plates left yields L→R→L→R. The plate pair is
   symmetrical and reads identically either way; 3.7's portrait bleed is a
   specific composition from booklet p.9 and is preserved. */
.how { display: grid; gap: var(--s-7); }
.how__plates { display: grid; gap: var(--s-4); }

@media (min-width: 900px) {
  .how {
    grid-template-columns: minmax(0, 1fr) minmax(0, 34em);
    align-items: center; column-gap: var(--s-7);
  }
  /* grid-row is explicit because how__text precedes how__plates in source but
     occupies the later column; without it auto-placement cannot move backwards
     and drops the plates into a second row. */
  .how__plates {
    grid-column: 1; grid-row: 1;
    grid-template-columns: 1fr 1fr;
    margin-left: calc(-1 * (var(--frame-inset) + var(--gutter)));
  }
  .how__text { grid-column: 2; grid-row: 1; max-width: none; }
}

/* ---------- The service (3.5) ---------- */

/* One text position per section, per 2.3. Both blocks sit left. */
.service { display: grid; gap: var(--s-7); }
.service__media { margin-block: var(--s-3); }

/* ---------- Bio (3.7) ---------- */

.bio { display: grid; gap: var(--s-7); }

@media (min-width: 900px) {
  .bio {
    grid-template-columns: minmax(0, 1fr) minmax(0, 34em);
    align-items: center; column-gap: var(--s-7);
  }
  .bio__portrait {
    margin-left: calc(-1 * (var(--frame-inset) + var(--gutter)));
  }
  .bio__text { max-width: none; }
}

/* ---------- Contact (3.8) ---------- */

/* A colophon sits low, not centred in a void. Explicit margins per child
   rather than a blanket gap competing with each element's own margin. */
.contact {
  min-height: 100lvh;
  display: grid; align-content: end; justify-items: center;
  text-align: center;
  padding-bottom: var(--spread-y);
}
.contact__inner { display: grid; justify-items: center; gap: 0; max-width: 100%; }
.contact__mark { width: 320px; max-width: 70%; color: var(--butter); }
.contact__mark svg { width: 100%; height: auto; }
.contact__cta { margin: var(--s-7) 0 0; }
.contact__line { margin: var(--s-6) 0 0; }

.contact__details {
  font-style: normal;
  display: grid; gap: var(--s-2);
  margin-top: var(--s-4);
  line-height: 1.6;
}
.contact__details span { font-weight: 400; }

/* ---------- The one button on the site ---------- */

/* Long and shallow, like a letterpress plate. 16px/32px is the framework
   default shape and reads as one. */
.cta {
  display: inline-block;
  border: 1px solid var(--butter);
  color: var(--butter);
  background: transparent;
  padding: var(--s-4) var(--s-6);
  min-width: 260px;
  text-transform: uppercase; letter-spacing: 0.14em;
  text-indent: 0.14em;           /* optical: centre tracked caps */
  font-size: 13px; font-weight: 400;
  text-decoration: none;
  transition: background-color 250ms ease, color 250ms ease;
}
.cta:hover { background-color: var(--butter); color: var(--charcoal); }
.cta:focus-visible { outline: 2px solid var(--butter); outline-offset: 3px; }

@media (max-width: 899px) { .cta { display: block; width: 100%; min-width: 0; } }

/* ---------- Subpages (/events, /preferences) ---------- */

.subpage { min-height: 100lvh; display: grid; align-items: center; }
.subpage__inner { display: grid; gap: var(--s-7); }
.subpage__inner--centred {
  justify-items: center; text-align: center;
  max-width: 34em; margin-inline: auto;   /* centre to the FRAME, not to a slab */
  gap: var(--s-4);
}
.subpage__label { margin: 0 0 var(--s-4); color: var(--rule); }
.subpage__inner--centred .subpage__label { text-indent: 0.14em; }
.subpage__mark { width: 320px; max-width: 70%; color: var(--butter); }
.subpage__mark svg { width: 100%; height: auto; }
.subpage__details {
  font-style: normal; display: grid; gap: var(--s-2);
  margin-top: var(--s-4); line-height: 1.6;
}

@media (min-width: 900px) {
  .subpage__inner { grid-template-columns: minmax(0, 1fr) minmax(0, 34em); column-gap: var(--s-7); align-items: center; }
  .subpage__label { grid-column: 1 / -1; }
  .subpage__media {
    grid-column: 1; grid-row: 2;
    margin-left: calc(-1 * (var(--frame-inset) + var(--gutter)));
  }
  .subpage__text { grid-column: 2; grid-row: 2; }
  .subpage__inner--centred { grid-template-columns: 1fr; }
}

/* ---------- Motion ----------

   Gated on `scripting: enabled` rather than a JS-set class: an inline
   class-setting script in <head> violates our own CSP (script-src 'self').

   Where the browser supports scroll-driven animation, opacity is a pure
   function of scroll POSITION and therefore cannot be outrun. The previous
   fixed-duration fade lost a race against fast scrolling — at 1000px/s a
   short block reached full opacity after it had already left the screen.
   Browsers without view() fall through to the IntersectionObserver path. */

@media (scripting: enabled) {
  .reveal { opacity: 0; transition: opacity 900ms ease-out; }
  .reveal.in { opacity: 1; }

  .reveal-img {
    opacity: 0; transform: scale(1.03);
    transition: opacity 1200ms ease-out, transform 1200ms ease-out;
  }
  .reveal-img.in { opacity: 1; transform: scale(1); }

  .reveal-stagger > * { opacity: 0; transition: opacity 900ms ease-out; }
  .reveal-stagger.in > * { opacity: 1; }
  .reveal-stagger.in > *:nth-child(2) { transition-delay: 120ms; }
  .reveal-stagger.in > *:nth-child(3) { transition-delay: 240ms; }
  .reveal-stagger.in > *:nth-child(4) { transition-delay: 360ms; }
  .reveal-stagger.in > *:nth-child(5) { transition-delay: 480ms; }

  /* Frame draws once on load. 600ms so it overlaps the hero fade rather than
     leaving the wordmark absent for 1.9s to anyone who scrolls immediately. */
  @keyframes frame-draw { from { opacity: 0; } to { opacity: 1; } }
  .frame { animation: frame-draw 1000ms ease-out both; }
  .hero__mark { transition-delay: 600ms; }
}

@supports (animation-timeline: view()) {
  @media (scripting: enabled) and (prefers-reduced-motion: no-preference) {
    .reveal, .reveal-stagger > * {
      opacity: 0;
      transition: none;
      animation: crs-reveal linear both;
      animation-timeline: view();
      animation-range: entry 10% entry 70%;
    }
    .reveal-img {
      opacity: 0;
      transition: none;
      transform: none;
      animation: crs-reveal-img linear both;
      animation-timeline: view();
      animation-range: entry 10% entry 70%;
    }
    /* Stagger becomes a range offset, since a view timeline has no clock. */
    .reveal-stagger > *:nth-child(2) { animation-range: entry 16% entry 76%; }
    .reveal-stagger > *:nth-child(3) { animation-range: entry 22% entry 82%; }
    .reveal-stagger > *:nth-child(4) { animation-range: entry 28% entry 88%; }
    .reveal-stagger > *:nth-child(5) { animation-range: entry 34% entry 94%; }

    /* Set by site.js when the document cannot scroll far enough for a view()
       range to complete. Without this a block below the fold on a short page
       would never reach opacity 1. */
    html.static .reveal,
    html.static .reveal-img,
    html.static .reveal-stagger > * {
      animation: none;
      opacity: 1;
      scale: 1;
      transform: none;
    }

    @keyframes crs-reveal { from { opacity: 0; } to { opacity: 1; } }
    @keyframes crs-reveal-img {
      from { opacity: 0; scale: 1.03; }
      to   { opacity: 1; scale: 1; }
    }
  }
}

/* ---------- Reduced motion ---------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 300ms !important;
    transition-delay: 0ms !important;
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
  .reveal-img { transform: none !important; }
  .interstitial { height: auto; margin-bottom: 0; }
  .interstitial__inner { position: static; height: auto; }
  .interstitial__inner picture, .interstitial__inner img { height: auto; }
}

/* /events place line — sits between the copy and the address block. */
.subpage__place { margin-top: var(--s-6); }

/* Identity mark on /events. This page is the one most likely to be forwarded
   on its own or found cold, and without the mark a stranger gets a first name,
   a photograph and a phone number. */
.subpage__id { display: block; width: 200px; max-width: 50%; color: var(--rule); margin-bottom: var(--s-6); }
.subpage__id svg { width: 100%; height: auto; }
@media (min-width: 900px) { .subpage__id { grid-column: 1 / -1; } }

.contact__survey{margin:32px 0;font-size:16px;line-height:1.6}.contact__survey span{display:block;font-size:13px;margin-top:8px;opacity:.75}
