/* ============================================================================
   LOVE SUNDAY — the design system.
   site/love-sunday.css

   Monday is the day you work. Sunday is the day you love.
   The mark is the sun. Light is the organising system — DAYLIGHT, never dusk.
   The page begins in first light and ends in golden hour, and it never goes
   dark, because Sunday evening is the hour this brand exists to abolish.

   This file is the foundation the page is built on: reset, tokens, the light,
   type, frames, the mark, motion. Section layout is NOT here — build it
   against these tokens and classes and the page will hold together.

   THE DOCTRINE (inherited from the gallery next door, tools/check.mjs enforces
   it): a palette is honest only if every piece of type clears WCAG AA against
   the surface it truly sits on. So every text-on-surface combination used
   anywhere on the page is declared beside its tokens as a comment of the
   form  "pair" colon, the text token, "on", the surface token, and an
   optional "@ minimum" (3 for display type) — see section 2 for the shape —
   and the checker measures it in EVERY light scope that declares both tokens
   — the house light and each keyframe of the day. The numbers written beside
   each pairing below were computed, not eyeballed (scratch script, WCAG 2.1
   relative luminance). An undeclared pairing is an unchecked claim: if you
   put text on a surface, declare it here first.

   FONTS — Google Fonts only. Put this in <head>, before this stylesheet:
     <link rel="preconnect" href="https://fonts.googleapis.com">
     <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
     <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT,WONK@0,9..144,400..500,100,1;1,9..144,400,100,1&family=Figtree:ital,wght@0,400..700;1,400..700&family=DM+Mono:wght@400&display=swap">
   The Fraunces axes are PINNED to what this file uses — SOFT 100, WONK 1,
   roman weights 400–500, italic weight 400 only — which halves the font
   payload (measured: 321KB → 150KB latin) with no rendering change. opsz stays
   live across 9..144 because optical sizing is in use from 12px to 108px.
   Consequence: italic exists ONLY at weight 400. Do not italicise a 500-weight
   heading or the browser will synthesise the slant. DM Mono 500 is not used
   anywhere and is not requested.>

   CONTENTS
     0  registered properties (so the light interpolates)
     1  reset and base
     2  tokens — colour, measured
     3  the light — one --sun value, keyframed across the scroll
     4  type — faces, scale, roles
     5  spacing, radius, layout primitives
     6  frames — prints, reels, the contact sheet, the browser
     7  the mark — sizes, the sun, bullets, colour rules
     8  buttons, links, focus
     9  motion tokens, reveals, reduced motion
    10  page layout
    11  the reel — the aerial film, and how it loads
   ========================================================================= */


/* ---------------------------------------------------------------------------
   0  REGISTERED PROPERTIES
   Unregistered custom properties animate discretely (they flip at the
   midpoint of each keyframe pair). Registering the ones the light moves makes
   them interpolate smoothly. Where @property is unsupported the light still
   works — it steps between states instead of gliding, and every state is
   AA-safe on its own, so nothing is ever unreadable.
   ------------------------------------------------------------------------ */
@property --sun        { syntax: '<number>'; inherits: true; initial-value: 0.2; }
@property --paper      { syntax: '<color>';  inherits: true; initial-value: #FEDEED; }
@property --blush      { syntax: '<color>';  inherits: true; initial-value: #FBCFE1; }
@property --mat        { syntax: '<color>';  inherits: true; initial-value: #FFFDFB; }
@property --sky        { syntax: '<color>';  inherits: true; initial-value: #FFF1F6; }
@property --glow       { syntax: '<color>';  inherits: true; initial-value: #FFD9C2; }
@property --shadow-ink { syntax: '<color>';  inherits: true; initial-value: rgb(74 30 40 / 0.20); }


/* ---------------------------------------------------------------------------
   1  RESET AND BASE
   ------------------------------------------------------------------------ */
*, *::before, *::after { box-sizing: border-box; }

html {
  /* The page is light, always. Form controls, scrollbars and UA styles are
     told so explicitly, and there is deliberately no dark-scheme block. */
  color-scheme: light;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 5rem;
  hanging-punctuation: first last;
}

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-text);
  font-size: var(--text-base);
  line-height: 1.55;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: clip;
}

h1, h2, h3, h4, p, figure, blockquote, ul, ol, dl, dd { margin: 0; }
ul[class], ol[class] { list-style: none; padding: 0; }
img, svg, video, picture { display: block; max-width: 100%; }
img { height: auto; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; background: none; border: 0; padding: 0; }
a { color: var(--red-ink); text-decoration-thickness: .08em; text-underline-offset: .14em; text-decoration-skip-ink: auto; }
a:hover { color: var(--ink); }
strong, b { font-weight: 600; }
small { font-size: var(--text-sm); }
hr { border: 0; border-top: 1px solid var(--line); margin: 0; }
::selection { background: var(--red-ink); color: var(--mat); }
:target { scroll-margin-top: 5rem; }


/* ---------------------------------------------------------------------------
   2  TOKENS — COLOUR
   Two are fixed by the identity. Everything else is derived from them, and
   every derivation that carries or backs text is measured.

   THE MEASUREMENT THAT SHAPED THIS PALETTE:
     #FE1717 on #FEDEED = 3.16:1.  That fails AA for body text (4.5) and passes
     only the large-text rule (3.0, for type >= 24px or >= 18.66px bold).
   So the bright red is a MARK AND ACCENT colour that may carry DISPLAY type
   only, and --red-ink — the brightest red that still clears 4.6:1 on the
   darkest surface in the whole day — is what red uses to carry words.

   Ratios below are measured in the house light (this :root). The keyframes
   in section 3 redeclare every text token so the checker re-measures the
   same pairings at each stop of the day; the worst value across the entire
   scroll, including interpolated midpoints, is noted as "worst".
   ------------------------------------------------------------------------ */
:root {
  /* — fixed by the identity — */
  --red:       #FE1717;  /* the mark, the accent, the call to action. Display type only. */
  --paper:     #FEDEED;  /* the ground. Pale pink; the brand colour verbatim at first light. */

  /* — derived: type — */
  --ink:       #3B1C24;  /* body copy. Warm plum-brown, red-leaning so it belongs to the pink. */
  --ink-soft:  #7D4B53;  /* captions, meta, secondary copy. Still AA on every surface. */
  --red-ink:   #BD1212;  /* red that carries words: links, labels, small CTAs, outlined buttons. */

  /* — derived: surfaces — */
  --mat:       #FFFDFB;  /* warm white: the mat of a framed print, cards, the browser frame. */
  --blush:     #FBCFE1;  /* a deeper pink: chips, insets, the empty-frame ground, hover fills. */
  --sky:       #FFF1F6;  /* the top of the sky; a lift behind the hero. Not a text surface. */
  --glow:      #FFD9C2;  /* the sun's halo and warm highlights. Not a text surface. */

  /* — text on the ground — */
  /* pair: --ink on --paper */               /*  12.26:1  (worst 12.26) */
  /* pair: --ink-soft on --paper */          /*   5.62:1  (worst 5.61)  */
  /* pair: --red-ink on --paper */           /*   5.18:1  (worst 5.17)  */
  /* pair: --red on --paper @ 3 */           /*   3.16:1  (worst 3.15) — LARGE TYPE ONLY */

  /* — text on blush — */
  /* pair: --ink on --blush */               /*  10.97:1  (worst 10.95) */
  /* pair: --ink-soft on --blush */          /*   5.03:1  (worst 4.95)  */
  /* pair: --red-ink on --blush */           /*   4.64:1  (worst 4.57)  */
  /* Bright --red never carries words on blush; it is not declared here. */

  /* — text on the mat — */
  /* pair: --ink on --mat */                 /*  15.02:1  (worst 14.46) */
  /* pair: --ink-soft on --mat */            /*   6.89:1  (worst 6.63)  */
  /* pair: --red-ink on --mat */             /*   6.35:1  (worst 6.11)  */
  /* pair: --red on --mat @ 3 */             /*   3.87:1  (worst 3.72) — LARGE TYPE ONLY */

  /* — text on red fills — */
  /* pair: --mat on --red @ 3 */             /*   3.87:1  (worst 3.72) — the big CTA: bold, >= 20px */
  /* pair: --mat on --red-ink */             /*   6.35:1  (worst 6.11) — any size */
  /* pair: --paper on --red-ink */           /*   5.18:1  (worst 5.17) — any size */

  /* — lines and shadow (not text; no pairing needed) — */
  --line:        #EFC6D3;   /* hairlines on paper; overridden by color-mix() below where supported */
  --line-strong: #E1A9BC;
  --shadow-ink:  rgb(74 30 40 / 0.20);  /* the colour of shade; warms through the day */
}

@supports (color: color-mix(in srgb, red 50%, blue)) {
  :root {
    /* Lines follow the ground as it warms, so a hairline in golden hour is
       peach-grey rather than pink-grey. Text tokens stay literal hex above so
       they remain measurable. */
    --line:        color-mix(in srgb, var(--ink) 12%, var(--paper));
    --line-strong: color-mix(in srgb, var(--ink) 24%, var(--paper));
  }
}


/* ---------------------------------------------------------------------------
   3  THE LIGHT
   One value, --sun, is the time of day: 0 is first light, 1 is golden hour.
   It is animated across the whole document scroll and everything else about
   the light is derived from it here — above all the DIRECTION of shadow,
   which is the tell that separates real light from a gradient.

   The sun's path (a northern-hemisphere morning through afternoon, read as
   the page is read): it rises high on the RIGHT, stands nearly overhead at
   eleven, and settles low on the LEFT for golden hour. So shadows fall
   down-left and short at the top of the page, shrink to almost nothing
   around the eleven o'clock stop, then lengthen down-RIGHT toward the
   contact section. Elevation is lowest at the ends of the day, so length
   grows with distance from the eleven o'clock stop (at --sun 0.35).

   Every keyframe redeclares the full set of text tokens so the checker
   measures the same pairings at every stop of the day — see section 2 for
   the pairings and the numbers. Paper luminance never drops below 0.79: the
   day warms in HUE, not in value, which is what keeps bright red display
   type above 3:1 all the way down the page. It never goes dark.
   ------------------------------------------------------------------------ */
:root {
  --sun: 0.2;   /* the resting state: mid-morning. Used where the scroll timeline is unsupported or motion is reduced. */
}

/* DERIVED ON BODY, NOT :ROOT — and this is the fix that made the light real.
   ls-sunday animates --sun on <body>. A calc() that reads var(--sun) resolves
   at the element that DECLARES it, so with these on :root every derived value
   was computed from :root's static 0.2 and never moved: the sky's glow sat
   still, the shadows pointed one way all day, and only the colours — declared
   inside the keyframes themselves — actually changed. Measured on the live
   site: --sun-cx computed to "calc(88% - 0.2 * 76%)" at the top and at the
   bottom of the page, the literal 0.2 baked in. Declared on body they read
   body's animated --sun, and everything below finally follows the sun. The
   reduced-motion sections are listed too, so the static stops they declare
   drive their own shadows. */
body,
#make, #websites, #work, #how, #contact, .footer,
[data-daylight="morning"], [data-daylight="midday"],
[data-daylight="afternoon"], [data-daylight="golden"] {
  /* sun position for the sky and for anything that wants to point at it */
  --sun-x:   calc(1 - 2 * var(--sun));                  /* +1 east (right) → −1 west (left) */
  --sun-cx:  calc(88% - var(--sun) * 76%);              /* horizontal position in the viewport */
  --sun-cy:  calc(-8% + var(--sun) * 30%);              /* rises to the top, sinks a little by evening */

  /* shadow geometry, derived */
  --shade-elev: max(var(--sun) - 0.35, 0.35 - var(--sun));    /* 0 at eleven o'clock, up to .65 at golden hour */
  --shadow-len:  calc(6px + var(--shade-elev) * 34px);
  --shadow-x:    calc(var(--sun-x) * -1 * var(--shadow-len));  /* light from the right throws shade to the left */
  --shadow-y:    calc(5px + var(--shadow-len) * 0.55);         /* always downward; longer as the sun lowers */
  --shadow-blur: calc(14px + var(--shadow-len) * 1.1);

  /* the three shadows the page uses. One light, three depths. */
  --shadow:     var(--shadow-x) var(--shadow-y) var(--shadow-blur) calc(var(--shadow-blur) * -0.35) var(--shadow-ink);
  --shadow-sm:  calc(var(--shadow-x) * 0.4) calc(var(--shadow-y) * 0.45) calc(var(--shadow-blur) * 0.5) calc(var(--shadow-blur) * -0.2) var(--shadow-ink);
  --shadow-lg:  calc(var(--shadow-x) * 1.6) calc(var(--shadow-y) * 1.5) calc(var(--shadow-blur) * 1.7) calc(var(--shadow-blur) * -0.5) var(--shadow-ink);
  /* a contact shadow: the dark join where a print meets the wall */
  --shadow-contact: 0 1px 0 0 rgb(74 30 40 / 0.06), 0 0 0 1px rgb(74 30 40 / 0.05);
}

@keyframes ls-sunday {
  0% {
    /* FIRST LIGHT — the hero. The brand pink verbatim; a clean, high, cool
       sun on the right; short shadows down-left. 11am on a good Sunday is
       coming, and the page opens just before it. */
    --sun: 0;
    --paper: #FEDEED;  --blush: #FBCFE1;  --mat: #FFFDFB;
    --sky:  #FFF1F6;   --glow:  #FFD9C2;
    --shadow-ink: rgb(74 30 40 / 0.20);
    --red: #FE1717;    --red-ink: #BD1212;  --ink: #3B1C24;  --ink-soft: #7D4B53;
  }
  35% {
    /* ELEVEN O'CLOCK — the brightest, cleanest moment. Sun nearly overhead,
       shadows almost gone, the mat truly white. */
    --sun: 0.35;
    --paper: #FFE4EE;  --blush: #FCD5E3;  --mat: #FFFFFF;
    --sky:  #FFFAFC;   --glow:  #FFF0B8;
    --shadow-ink: rgb(74 30 40 / 0.14);
    --red: #FE1717;    --red-ink: #BD1212;  --ink: #3B1C24;  --ink-soft: #7D4B53;
  }
  70% {
    /* AFTERNOON — the pink warms toward coral. Shadows begin to swing right
       and lengthen; the shade itself turns browner. */
    --sun: 0.7;
    --paper: #FFE1DF;  --blush: #FCD1CF;  --mat: #FFFCF7;
    --sky:  #FFF3E8;   --glow:  #FFD6A0;
    --shadow-ink: rgb(92 42 30 / 0.16);
    --red: #FE1717;    --red-ink: #BD1212;  --ink: #3B1C24;  --ink-soft: #7D4B53;
  }
  100% {
    /* GOLDEN HOUR — the contact section. Apricot ground, gold halo, long warm
       shadows to the right. As bright as the morning, only warmer: this is
       where the reader is left. */
    --sun: 1;
    --paper: #FFE2C8;  --blush: #FBD3B4;  --mat: #FFF8EE;
    --sky:  #FFE9C9;   --glow:  #FFD29B;   /* was #FFC077; lifted so text on the composited wash keeps a margin — see the note below the keyframes */
    --shadow-ink: rgb(110 52 22 / 0.20);
    --red: #FE1717;    --red-ink: #BD1212;  --ink: #3B1C24;  --ink-soft: #7D4B53;
  }
}

/* The day is driven by the document scroll. Guarded, because a browser that
   does not know scroll timelines would otherwise play this as a zero-second
   time animation and freeze on the last frame. Without support, the page
   rests in the :root state above — mid-morning, and complete. */
@supports (animation-timeline: scroll()) {
  body {
    animation: ls-sunday linear both;
    animation-timeline: scroll(root block);
  }
}

/* The sky: an optional fixed wash that puts the sun's warmth where the sun
   is. One element, first child of body, aria-hidden. Cheap enough because it
   is one gradient; do not add more layers to it. */
.sky {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(70vmax 56vmax at var(--sun-cx) var(--sun-cy), var(--glow) 0%, transparent 62%),
    linear-gradient(to bottom, var(--sky), transparent 46%);
}

/* THE WASH IS A SURFACE TOO. .sky is a fixed layer above the canvas, so text
   on the ground actually sits on --paper WITH the sun's radial glow composited
   over it, and that composite is not a token the checker can measure. It was
   swept by hand (390x664 and 1440x900, every canvas-backed text element,
   every scroll position). Worst reachable backdrops:
     the How-we-work numerals   rgb(255,202,139)   (--sun ~0.96, glow alpha ~0.93)
     the contact block          rgb(255,208,154)
   Against those, with --glow at #FFD29B in the 100% keyframe (measured at
   full alpha): --ink 10.85, --ink-soft 4.98, --red-ink 4.59. Bright --red
   measures 2.44–2.98 on the golden-hour wash, which is why .step__n is set in
   --red-ink and not --red. Do not add a "--wash" pair: no text sits on the
   glow at full alpha, and an honest token for it would fail the build for a
   surface nothing occupies. Re-sweep if the sun's path or the glow changes. */

/* A section may re-light itself — a warmer inset, a whiter card — by setting
   tokens on its own scope. Anything that does MUST redeclare the text tokens
   too, so the checker measures that scope. Pattern:
     .my-inset { --paper: #FFF8EE; --ink: #3B1C24; --ink-soft: #7D4B53; --red-ink: #BD1212; --red: #FE1717; }
   Do not invent surfaces that are not measured. */


/* ---------------------------------------------------------------------------
   4  TYPE
   The brand is a red smiley; the studio is serious. Both must be true in the
   type, so:

   FRAUNCES for display. A soft, old-style serif drawn with an optical-size
   axis, a SOFT axis (rounded terminals — set high, so the letters are as
   rounded and glad as the mark) and a WONK axis (the alternate a, g, k, y
   with the tilted, hand-cut warmth — set on). At hero size on pink it is
   joyful without being cute; at a caption size it is quietly bookish. It
   never looks like a template.

   FIGTREE for text. A friendly geometric grotesque with open counters, a
   true italic, and enough discipline that a paragraph reads as a studio
   wrote it. It is warm next to Fraunces where a colder grotesque would argue
   with it.

   DM MONO for notation. The photographer's register: contact-sheet labels,
   altitude marks, the address bar of the browser frame, eyebrows. It is the
   precision layer, and it is used small and sparingly.

   Scale: phone-first clamp() from 390px to about 1280px. Body is 17px on a
   phone and 18px on a desk. Display steps grow faster than text steps.
   ------------------------------------------------------------------------ */
:root {
  --font-display: "Fraunces", "Iowan Old Style", "Palatino Linotype", Georgia, serif;
  --font-text:    "Figtree", "Avenir Next", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-mono:    "DM Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* text steps */
  --text-xs:   clamp(0.75rem,   0.72rem + 0.14vw,  0.8125rem); /* 12 → 13   notation, footnotes */
  --text-sm:   clamp(0.875rem,  0.84rem + 0.16vw,  0.9375rem); /* 14 → 15   captions, nav */
  --text-base: clamp(1.0625rem, 1.03rem + 0.16vw,  1.125rem);  /* 17 → 18   body */
  --text-lg:   clamp(1.1875rem, 1.10rem + 0.40vw,  1.375rem);  /* 19 → 22   lede, big buttons */
  /* display steps */
  --text-xl:   clamp(1.5rem,    1.25rem + 1.05vw,  2rem);      /* 24 → 32   h3, pull lines */
  --text-2xl:  clamp(2rem,      1.55rem + 1.85vw,  3rem);      /* 32 → 48   h2 */
  --text-3xl:  clamp(2.75rem,   1.95rem + 3.30vw,  4.75rem);   /* 44 → 76   h1, the hero promise */
  --text-4xl:  clamp(3.5rem,    2.20rem + 5.40vw,  6.75rem);   /* 56 → 108  the statement, alone */

  /* the large-text floor. Anything set in bright --red must be at least
     this big (24px) or 18.66px and bold; the classes below enforce it. */
  --text-large-min: 1.5rem;

  --leading-tight: 1.02;
  --leading-snug:  1.15;
  --leading-body:  1.55;
  --tracking-display: -0.015em;
  --tracking-caps: 0.12em;
}

/* roles */
h1, h2, h3, .statement, .display {
  font-family: var(--font-display);
  font-weight: 500;
  font-variation-settings: "SOFT" 100, "WONK" 1;
  letter-spacing: var(--tracking-display);
  line-height: var(--leading-tight);
  text-wrap: balance;
  color: var(--ink);
}
h1, .display-1 { font-size: var(--text-3xl); }
h2, .display-2 { font-size: var(--text-2xl); }
h3, .display-3 { font-size: var(--text-xl); line-height: var(--leading-snug); }
.statement       { font-size: var(--text-4xl); line-height: 1.0; letter-spacing: -0.02em; }
.display-italic  { font-style: italic; font-weight: 400; }

p { text-wrap: pretty; }
.lede   { font-size: var(--text-lg); line-height: 1.4; }
.small  { font-size: var(--text-sm); }
.prose  { max-width: var(--measure); }
.prose > * + * { margin-block-start: 1em; }

/* notation — the mono register */
.eyebrow, .notation, .caption {
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: var(--text-xs);
  line-height: 1.4;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}
.caption { text-transform: none; letter-spacing: 0.02em; font-size: var(--text-sm); }

/* colour roles for type. Bright red is allowed ONLY through the two classes
   that force display size — that is how the 3:1 large-text rule is kept
   honest in the markup rather than by memory. */
.t-ink      { color: var(--ink); }
.t-soft     { color: var(--ink-soft); }
.t-red-ink  { color: var(--red-ink); }
.t-red      { color: var(--red); font-size: max(var(--text-large-min), 1em); }             /* display only */
.t-red-bold { color: var(--red); font-weight: 700; font-size: max(1.1875rem, 1em); }        /* 19px bold clears the bold large-text floor of 18.66px */
h1 .t-red, h2 .t-red, .statement .t-red, .display .t-red { font-size: inherit; }           /* already display-sized */

/* surfaces */
.s-paper { background: var(--paper); color: var(--ink); }
.s-mat   { background: var(--mat);   color: var(--ink); }
.s-blush { background: var(--blush); color: var(--ink); }
.s-red-ink { background: var(--red-ink); color: var(--mat); }
.s-red-ink a, .s-red-ink .mark { color: var(--mat); }
.s-red { background: var(--red); color: var(--mat); font-weight: 700; font-size: max(1.25rem, 1em); } /* large type only; see --mat on --red @ 3 */
.s-red .mark { color: var(--mat); }


/* ---------------------------------------------------------------------------
   5  SPACING, RADIUS, LAYOUT PRIMITIVES
   ------------------------------------------------------------------------ */
:root {
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: clamp(2rem, 1.5rem + 2vw, 3rem);
  --space-7: clamp(3rem, 2rem + 4vw, 5rem);
  --space-8: clamp(4.5rem, 3rem + 7vw, 8.5rem);      /* between sections */
  --space-9: clamp(6rem, 4rem + 10vw, 12rem);        /* around the contact section: the largest room on the page */

  --gutter:  clamp(1.25rem, 4vw, 4rem);
  --wrap:    72rem;
  --measure: 34em;                                    /* body copy, at most */
  --measure-narrow: 24em;

  --r-1: 6px;
  --r-2: 14px;
  --r-3: 24px;
  --r-pill: 999px;
  --r-print: 2px;                                     /* prints have corners */

  --tap: 44px;                                        /* minimum touch target */
}

.wrap        { width: min(100% - 2 * var(--gutter), var(--wrap)); margin-inline: auto; }
.wrap--narrow{ width: min(100% - 2 * var(--gutter), 52rem); margin-inline: auto; }
.section     { padding-block: var(--space-8); }
.section--roomy { padding-block: var(--space-9); }
.cq          { container-type: inline-size; }         /* put on any wrapper whose children should respond to ITS width */
.stack > * + * { margin-block-start: var(--space-4); }
.cluster     { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }
.visually-hidden { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }


/* ---------------------------------------------------------------------------
   6  FRAMES
   The photographs are 640px on the long edge and already compressed. Frames
   are how we show them honestly: fixed aspect boxes with hard caps on
   display width so nothing is ever drawn above its natural resolution, and a
   full-resolution replacement drops in with ONE src change — same box, same
   crop, no layout shift, no class to edit.

   Rules for the markup:
     <figure class="frame frame--portrait frame--print" style="--tilt:-1.5deg">
       <img src="img/product-juice.jpg" width="480" height="640" alt="…" loading="lazy" decoding="async">
     </figure>
   Always width/height attributes. loading="lazy" below the fold only.

   Caps (CSS px, ~80% of natural width so the file stays sharp):
     --square    1:1    ≤ 520px   from 640×640
     --portrait  3:4    ≤ 400px   from 480×640 (and 499×640 hospitality, cropped)
     --tall      9:16   ≤ 300px   from 361×640 (behind the scenes)
     --reel      9:16   ≤ 320px   a phone-shaped crop of a 640×640 social tile
     --film      9:16   ≤ 360px   the aerial reel: a 720×1280 poster and film (section 11)
     --wide      21:9   unused today; kept as a shape
     --screen    16:10  no image yet — the browser frame for the case study
   ------------------------------------------------------------------------ */
.frame {
  --ar: 1 / 1;
  --frame-max: 520px;
  --mat-w: 0px;
  position: relative;
  display: block;
  width: 100%;
  max-width: var(--frame-max);
  aspect-ratio: var(--ar);
  padding: var(--mat-w);
  overflow: hidden;
  background: var(--blush);
  border-radius: var(--r-print);
  isolation: isolate;
  rotate: var(--tilt, 0deg);
}
.frame > img,
.frame > video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--focus, 50% 50%);   /* set --focus per image to choose the crop */
  border-radius: inherit;
}
@supports not (aspect-ratio: 1) {
  /* older engines: hold the box open with padding instead */
  .frame::before { content: ""; display: block; padding-bottom: calc(100% / (var(--ar))); }
  .frame > img, .frame > video { position: absolute; inset: var(--mat-w); width: auto; height: auto; }
}

/* shapes */
.frame--square   { --ar: 1 / 1;   --frame-max: 520px; }
.frame--portrait { --ar: 3 / 4;   --frame-max: 400px; }
.frame--tall     { --ar: 9 / 16;  --frame-max: 300px; }
.frame--reel     { --ar: 9 / 16;  --frame-max: 320px; }
.frame--wide     { --ar: 21 / 9;  --frame-max: none; }
.frame--screen   { --ar: 16 / 10; --frame-max: none; }

/* — the print: a white mat, a real shadow with the day's direction, a
     contact line where it meets the wall. --tilt sets it a degree or two off
     square if a cluster wants to feel pinned up rather than gridded. */
.frame--print {
  --mat-w: clamp(8px, 3.5%, 16px);
  background: var(--mat);
  border-radius: var(--r-print);
  box-shadow: var(--shadow), var(--shadow-contact);
}
/* Safari can resolve an in-flow image’s percentage height beyond the
   aspect-ratio frame’s content area. Size the media against the whole frame
   and include the mat inside its border box, keeping all four sides visible. */
.frame--print > img,
.frame--print > video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  box-sizing: border-box;
  padding: inherit;
}
.frame--print > img { border-radius: 1px; }
.frame--print.frame--deep { --mat-w: clamp(14px, 6%, 28px); }   /* a wider mat for a hero print */

/* — the reel: a phone-shaped crop for the social work. The bezel is ink,
     not black, and the corners are a phone's. */
.frame--reel {
  border-radius: clamp(20px, 9%, 34px);
  border: 5px solid var(--ink);
  background: var(--ink);
  box-shadow: var(--shadow-lg);
}
.frame--reel > img { border-radius: calc(clamp(20px, 9%, 34px) - 5px); }

/* — the contact sheet: a grid of small squares on a mat with mono labels.
     Cells stay small (≤ 220px) so 640px files are far inside their limit. */
.sheet {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--mat);
  color: var(--ink);
  border-radius: var(--r-2);
  box-shadow: var(--shadow-sm), var(--shadow-contact);
}
.sheet > figure { display: grid; gap: var(--space-2); min-width: 0; }
.sheet .frame { --frame-max: 220px; --ar: 1 / 1; border-radius: var(--r-1); margin-inline: auto; width: 100%; }
.sheet figcaption { font-family: var(--font-mono); font-size: var(--text-xs); letter-spacing: 0.06em; color: var(--ink-soft); text-transform: uppercase; }
@container (min-width: 40rem) {
  .sheet { grid-template-columns: repeat(4, 1fr); gap: var(--space-4); padding: var(--space-5); }
}

/* — notation inside any frame: small mono labels pinned to a corner. */
.frame > .notation {
  position: absolute;
  z-index: 2;
  inset: auto auto var(--space-3) var(--space-3);
  margin: 0;
  color: var(--ink-soft);
  background: var(--mat);
  padding: 0.35em 0.6em;
  border-radius: var(--r-1);
}
.frame > .notation--tl { inset: var(--space-3) auto auto var(--space-3); }
.frame > .notation--tr { inset: var(--space-3) var(--space-3) auto auto; }
.frame > .notation--br { inset: auto var(--space-3) var(--space-3) auto; }

/* — the browser frame: a mat, a title bar with three quiet dots and a mono
     address pill, and a 16:10 stage. The stage is COMPOSED until a real
     screenshot lands: a wall meeting a floor, in the day's light, a nod to
     the gallery it stands for. The moment a real frame lands (<img> as the
     first child) the composition retires by itself. */
.browser {
  background: var(--mat);
  color: var(--ink);
  border-radius: var(--r-2);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-lg), var(--shadow-contact);
  overflow: hidden;
}
.browser__bar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--line);
}
.browser__dots {
  flex: none;
  width: 34px;
  height: 10px;
  background:
    radial-gradient(circle at 5px  5px, var(--line-strong) 4px, transparent 4.6px),
    radial-gradient(circle at 17px 5px, var(--line-strong) 4px, transparent 4.6px),
    radial-gradient(circle at 29px 5px, var(--line-strong) 4px, transparent 4.6px);
}
.browser__url {
  min-height: var(--tap);
  line-height: calc(var(--tap) - .9em);
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.02em;
  color: var(--ink-soft);
  background: var(--paper);
  padding: 0.45em 0.9em;
  border-radius: var(--r-pill);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-decoration: none;
}
.browser__url:hover { color: var(--ink); }
.browser .frame--screen {
  border-radius: 0;
  /* A wall, lit from above, warming toward the floor.
     Three versions of this drew a floor: first as ruled lines (read as
     notebook paper), then as a hard join with a contact shadow (read as a
     bar across the window — the "odd break"). Every attempt to draw the
     floor's EDGE failed, because at this scale an edge is a line and a line
     across an empty window is a defect. What a gallery wall actually looks
     like from across the room is light falling off: bright where the
     skylight hits it, warmer and dimmer toward the ground, with no line
     anywhere. So that is all this is now — one continuous fall of light. The
     hung piece gives it scale; the fall of light gives it depth. */
  background:
    radial-gradient(60% 45% at 50% 30%, rgb(255 253 251 / 0.7) 0%, rgb(255 253 251 / 0) 70%),
    linear-gradient(to bottom, var(--sky) 0%, var(--mat) 40%, var(--paper) 78%, var(--blush) 100%);
}

/* When a real picture arrives, the composed treatments step aside on their own. */
.frame:has(> img)::before,
.frame:has(> img)::after,
.frame:has(> img) > .mark,
.frame:has(> img) > .notation--placeholder { display: none; }


/* ---------------------------------------------------------------------------
   7  THE MARK
   img/mark.svg is the mark. It is the sun. It is already right: do not
   redraw it, do not swap it for an emoji. It paints with currentColor and its
   face is a mask, so it sits correctly on pink, on white and over a picture.

   Two ways to place it:
     inline the SVG inside <span class="mark mark--sun" aria-hidden="true">
       when it must animate, glow, or take a colour from its parent;
     <img src="img/mark.svg" alt=""> inside .mark otherwise — the image
       renders in black, so ONLY use <img> inside a .mark that is masked or
       where black is wanted, which is nowhere; prefer inline.
     Favicon: inline a copy as a data URI with style="color:#FE1717" on the
       <svg>, because currentColor in a favicon has no parent to read.

   COLOUR RULES
     • On paper, mat or blush the mark is --red. Always the true red — the
       mark is never set in --red-ink, that token is for words.
     • On a red fill (.s-red, .btn-sun) the mark is --mat.
     • Over a photograph the mark is --mat, or --red on a mat-coloured disc.
   SIZE RULES
     • Never below 20px, except as a text bullet (1em of body copy ≥ 17px).
     • .mark--nav 32px · .mark--bullet 1em · .mark--sun and .mark--anchor
       are the two big suns, sized to the viewport and capped.
   ------------------------------------------------------------------------ */
.mark {
  --mark-size: 1em;
  display: inline-block;
  flex: none;
  inline-size: var(--mark-size);
  block-size: var(--mark-size);
  color: var(--red);
  vertical-align: -0.12em;
  line-height: 0;
}
.mark > svg, .mark > img { width: 100%; height: 100%; }

.mark--nav    { --mark-size: 2rem; }
.mark--bullet { --mark-size: 1em; }
.mark--inline { --mark-size: 0.9em; vertical-align: -0.1em; }
.mark--sun    { --mark-size: clamp(6rem, 28vw, 13.5rem); }     /* 96 → 216px: the hero */
.mark--anchor { --mark-size: clamp(7.5rem, 38vw, 17rem); }    /* 120 → 272px: the contact section */
.mark--stop   { --mark-size: 1.5rem; }                         /* the full stop at the end of the page */

/* the sun behaviour: a halo the colour of the day's glow, following the
   mask's edge, so it reads as light coming off the disc rather than a blur */
.mark--sun, .mark--anchor {
  filter: drop-shadow(0 0 calc(var(--mark-size) * 0.16) var(--glow));
  will-change: filter;
}
.mark--anchor { filter: drop-shadow(0 0 calc(var(--mark-size) * 0.22) var(--glow)); }

/* the sunrise: used on load in the hero, and on entry at the contact anchor.
   It translates and fades; it never touches the scroll. */
@keyframes ls-sunrise {
  from { translate: 0 38%; opacity: 0; }
  to   { translate: 0 0;   opacity: 1; }
}
.mark--sun.is-rising { animation: ls-sunrise var(--dur-slow) var(--ease-out) both; animation-delay: 120ms; }
@supports (animation-timeline: view()) {
  .mark--anchor.is-rising {
    animation: ls-sunrise linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 90%;
  }
}

/* the mark as a bullet: the SVG's alpha is the mask, currentColor is the
   fill, so a list of red suns needs no markup at all */
.sun-list { list-style: none; padding: 0; margin: 0; }
.sun-list > li { position: relative; padding-inline-start: 1.9em; }
.sun-list > li + li { margin-block-start: var(--space-3); }
.sun-list > li::before {
  content: "";
  position: absolute;
  left: 0;
  top: calc((1lh - 1.15em) / 2);
  width: 1.15em;
  height: 1.15em;
  background: var(--red);
  border-radius: 50%;                                   /* the fallback: a plain red sun */
}
@supports (mask: url("img/mark.svg")) or (-webkit-mask: url("img/mark.svg")) {
  .sun-list > li::before {
    border-radius: 0;
    -webkit-mask: url("img/mark.svg") center / contain no-repeat;
            mask: url("img/mark.svg") center / contain no-repeat;
  }
}
@supports not (top: 1lh) {
  .sun-list > li::before { top: 0.2em; }
}


/* ---------------------------------------------------------------------------
   8  BUTTONS, LINKS, FOCUS
   Three buttons, one language. The bright-red button exists for the call to
   action and is ONLY legal at display weight and size — the class forces
   20px bold, which is how white on #FE1717 (3.87:1) is inside AA. Anything
   smaller uses the red-ink fill (6.35:1 at any size) or the outline.
   ------------------------------------------------------------------------ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6em;
  min-height: var(--tap);
  padding: 0.8em 1.45em;
  border-radius: var(--r-pill);
  border: 2px solid transparent;
  font-family: var(--font-text);
  font-weight: 600;
  font-size: var(--text-base);
  line-height: 1.1;
  text-decoration: none;
  white-space: nowrap;
  transition: translate var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.btn:hover  { translate: 0 -1px; }
.btn:active { translate: 0 0; transition-duration: 60ms; }

.btn-sun {
  background: var(--red);
  color: var(--mat);
  font-weight: 700;
  font-size: max(1.25rem, var(--text-lg));            /* the large-text floor: 20px bold minimum */
  padding: 0.85em 1.6em;
  box-shadow: var(--shadow);
}
.btn-sun:hover { box-shadow: var(--shadow-lg); color: var(--mat); }
.btn-sun .mark { color: var(--mat); }

.btn-ink { background: var(--red-ink); color: var(--mat); box-shadow: var(--shadow-sm); }
.btn-ink:hover { color: var(--mat); box-shadow: var(--shadow); }
.btn-ink .mark { color: var(--mat); }

.btn-line { background: transparent; color: var(--red-ink); border-color: var(--red-ink); }
.btn-line:hover { background: var(--mat); color: var(--red-ink); }

.btn-quiet { background: var(--mat); color: var(--ink); box-shadow: var(--shadow-contact); }
.btn-quiet:hover { color: var(--ink); box-shadow: var(--shadow-sm), var(--shadow-contact); }

/* text links that want the studio's underline rather than the default */
.link {
  color: var(--red-ink);
  text-decoration: underline;
  text-decoration-thickness: 0.09em;
  text-underline-offset: 0.16em;
  text-decoration-color: color-mix(in srgb, var(--red-ink) 45%, transparent);
  transition: text-decoration-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.link:hover { color: var(--ink); text-decoration-color: currentColor; }
.link--big { font-family: var(--font-display); font-size: var(--text-xl); font-variation-settings: "SOFT" 100, "WONK" 1; }

/* focus: one ring for everything, visible on every surface. Red-ink ring,
   a paper gap so it separates from a red fill, generous offset. */
:focus { outline: none; }
:focus-visible {
  outline: 3px solid var(--red-ink);
  outline-offset: 3px;
  /* NO border-radius here, deliberately.
     This rule and `.btn` are both specificity (0,1,0) and this one comes
     later, so any radius set here WINS over the element's own — a 999px pill
     snapped to a rounded rectangle the instant it took keyboard focus, which
     is a shape change nobody asked for and only keyboard users ever saw.
     It is unnecessary as well as harmful: `outline` already follows the
     element's own border-radius in every browser we target, so the ring is
     correctly round on the pills and correctly square on the links. */
}
.btn:focus-visible, .frame:focus-visible, .browser__url:focus-visible {
  outline-offset: 4px;
}
.btn-sun:focus-visible, .btn-ink:focus-visible, .s-red :focus-visible, .s-red-ink :focus-visible {
  outline-color: var(--ink);
  box-shadow: 0 0 0 3px var(--paper);
}


/* ---------------------------------------------------------------------------
   9  MOTION
   Nothing on this page hijacks the scroll or delays the content. Motion is
   light moving, a sun rising, a print settling into place. Durations are
   short; easing is a quick start and a soft landing.
   ------------------------------------------------------------------------ */
:root {
  --dur-fast: 160ms;
  --dur:      320ms;
  --dur-slow: 900ms;
  --ease-out: cubic-bezier(0.2, 0.7, 0.2, 1);
  --ease-in-out: cubic-bezier(0.6, 0, 0.2, 1);
  --rise: 18px;
}

/* the reveal: an element rises a little and fades in as it enters the
   viewport, driven by its own position, not a timer. Where view timelines
   are unsupported the class does nothing and the element is simply there. */
@keyframes ls-rise {
  from { opacity: 0; translate: 0 var(--rise); }
  to   { opacity: 1; translate: 0 0; }
}
@supports (animation-timeline: view()) {
  .rise {
    animation: ls-rise linear both;
    animation-timeline: view();
    animation-range: entry 8% entry 55%;
  }
  .rise--late { animation-range: entry 20% entry 75%; }
}

/* a print that settles: on hover the tilt relaxes toward square and the
   shadow deepens as it lifts. Pointer devices only. */
@media (hover: hover) and (pointer: fine) {
  .frame--print { transition: rotate var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out); }
  .frame--print:hover { rotate: 0deg; box-shadow: var(--shadow-lg), var(--shadow-contact); }
}

/* REDUCED MOTION — the page settles to one state and stops.
   The light rests at mid-morning (the :root values: brand pink, sun high on
   the right, short shadows down-left). Nothing rises, nothing fades, nothing
   scrolls smoothly on the reader's behalf. It is meant to be beautiful
   frozen, and it is: the same tokens, the same shadows, at rest. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition: none !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  html { scroll-behavior: auto; }
  body { animation: none; }
  .rise, .rise--late, .mark--sun.is-rising, .mark--anchor.is-rising { opacity: 1; translate: 0 0; }
  .frame--print:hover { rotate: var(--tilt, 0deg); }
  .mark--sun, .mark--anchor { will-change: auto; }
  .hand { display: none; }                /* the script never starts it either; belt and braces */
  .sky::after { display: none; }          /* the sun does not cross a sky that does not move */
  .peek { display: none; }                /* and nothing plays hide-and-seek on a still page */
}


/* ---------------------------------------------------------------------------
   9b  DELIGHT — the small rewards for scrolling

   Section 9 gave the page one gesture: things rise as they arrive. That is
   the right foundation and the wrong ceiling. Read down a page where every
   element does the identical thing and the eye stops noticing by the second
   screen. What keeps a reader scrolling is variety in a single vocabulary:
   a cascade here, a settle there, a row of suns lighting up one by one — each
   small, each quick, each finishing before it can be waited for.

   RULES, all of them load-bearing:
   · Driven by the reader's scroll (view timelines), never by a clock. The
     page does nothing the reader did not cause and stops when they stop.
   · transform and opacity only. Nothing here can cause layout.
   · The BASE state is the FINISHED state; keyframes only define `from`.
     That is what makes reduced motion free: `animation: none` leaves every
     element resolved — suns lit, prints settled, underline drawn — rather
     than frozen at zero. Do not write a `to` that differs from the base.
   · Stagger is done by offsetting animation-range per sibling, not by
     delays. A delay is a clock; a range offset is still the reader's scroll.
   · Hover work is pointer-only. Touch gets the press, not the lift.
   ------------------------------------------------------------------------ */
@supports (animation-timeline: view()) {

  /* — a print settles: it rises, and lands from a few degrees off square,
       the way a print put down on a table finds its place. Applied to the
       CONTAINER (work-item, phone, reel), never to .frame--print itself,
       whose own rotate carries the tilt and the hover — an animation on the
       same element would win the cascade and freeze the hover. — */
  @keyframes ls-settle {
    from { opacity: 0; translate: 0 var(--rise); rotate: var(--settle-from, -1.6deg); scale: 0.97; }
  }
  .phone.rise, .reel.rise { animation-name: ls-settle; transform-origin: 50% 80%; }
  .work-item.rise { animation: none; } /* only the pinned photograph moves; its story label stays still */
  .reel.rise { --settle-from: 1deg; }

  /* — steps arrive one after another as the reader reaches them. — */
  .steps > .step:nth-child(2) { animation-range: entry 14% entry 60%; }
  .steps > .step:nth-child(3) { animation-range: entry 20% entry 66%; }
  .steps > .step:nth-child(4) { animation-range: entry 26% entry 72%; }

  /* — the suns light up, in order. The bullet is a pseudo-element, so it
       borrows its parent's view timeline by name. A little overshoot,
       because a sun coming on should feel like a sun coming on. — */
  @keyframes ls-pop {
    from { scale: 0; opacity: 0; }
    62%  { scale: 1.22; opacity: 1; }
  }
  .sun-list > li { view-timeline-name: --sun; }
  .sun-list > li::before { animation: ls-pop linear both; animation-timeline: --sun; animation-range: entry 0% entry 55%; transform-origin: center; }
  .sun-list > li:nth-child(2)::before { animation-range: entry 10% entry 65%; }
  .sun-list > li:nth-child(3)::before { animation-range: entry 20% entry 75%; }
  .sun-list > li:nth-child(4)::before { animation-range: entry 30% entry 85%; }
  .sun-list > li:nth-child(5)::before { animation-range: entry 40% entry 95%; }

  /* — the numeral arrives a beat before its step, so the eye reads
       01 → heading → line, in that order — */
  @keyframes ls-numeral { from { opacity: 0; translate: 0 0.4em; } }
  .step { view-timeline-name: --step; }
  .step__n { display: block; animation: ls-numeral linear both; animation-timeline: --step; animation-range: entry 0% entry 38%; }

  /* — eyebrows come in from the left while everything else rises: a second
       axis, so the page has texture rather than one direction — */
  @keyframes ls-slide { from { opacity: 0; translate: -14px 0; } }
  .eyebrow { animation: ls-slide linear both; animation-timeline: view(); animation-range: entry 0% entry 45%; }

  /* — "look great" underlines itself as the statement lands. An underline,
       NOT a highlighter: a highlighter behind red display type measures
       2.98:1 on --glow and 2.83 on --blush, under the 3:1 floor, and the
       checker cannot see a background image. A line beneath the baseline
       touches no glyph. — */
  @keyframes ls-draw { from { background-size: 0% 0.075em; } }
  .statement { view-timeline-name: --stmt; }
  .statement .t-red {
    background: linear-gradient(var(--red), var(--red)) no-repeat 0 96% / 100% 0.075em;
    animation: ls-draw linear both;
    animation-timeline: --stmt;
    animation-range: entry 45% entry 100%;
  }

  /* — the contact sheet: four stills, four beats — */
  .sheet { view-timeline-name: --sheet; }
  .sheet > figure { animation: ls-settle linear both; animation-timeline: --sheet; --settle-from: -1.2deg; transform-origin: 50% 80%; }
  .sheet > figure:nth-child(1) { animation-range: entry 10% entry 50%; }
  .sheet > figure:nth-child(2) { animation-range: entry 18% entry 58%; --settle-from: 1.2deg; }
  .sheet > figure:nth-child(3) { animation-range: entry 26% entry 66%; }
  .sheet > figure:nth-child(4) { animation-range: entry 34% entry 74%; --settle-from: 1.2deg; }

  /* — the hero prints drift as the hero leaves, at two speeds, so the
       stack has depth for the one moment the reader is looking away from it.
       `exit` range: the element is leaving at the top. Base translate is 0,
       so at rest and under reduced motion nothing has moved. — */
  @keyframes ls-drift-near { to { translate: 0 -64px; } }
  @keyframes ls-drift-far  { to { translate: 0 -28px; } }
  .scene > .frame--print:nth-of-type(1) { animation: ls-drift-far  linear both; animation-timeline: view(); animation-range: exit 0% exit 100%; }
  .scene > .frame--print:nth-of-type(2) { animation: ls-drift-near linear both; animation-timeline: view(); animation-range: exit 0% exit 100%; }
}

/* — the hand: what happens under the pointer. Pointer devices only; a
     finger gets the press below. — */
@media (hover: hover) and (pointer: fine) {
  /* nav links draw their underline instead of switching it on */
  .nav__links a {
    background: linear-gradient(currentColor, currentColor) no-repeat 0 calc(100% - 0.55em) / 0% 1.5px;
    transition: background-size var(--dur) var(--ease-out), color var(--dur-fast) var(--ease-out);
  }
  .nav__links a:hover { text-decoration: none; background-size: 100% 1.5px; }
  /* (no zoom inside the mat: the image is an in-flow block inside the frame's
     padding, and overflow clips at the padding edge, so a scaled picture would
     grow OVER the mat. The lift-and-settle above is the print's hover.) */
  /* the outlined buttons fill on hover the way the filled ones lift */
  .btn-line { transition: translate var(--dur) var(--ease-out), background-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out); }
}
/* the press: everyone gets this, it is the one piece of feedback a finger
   can feel. Quick in, and the button is under the thumb so it must be small. */
.btn:active { scale: 0.985; }

/* ---------------------------------------------------------------------------
   9c  THE FLEX

   9b made scrolling rewarding. This makes the page feel alive — and every
   piece of it is the brand's own idea taken one step further, not a trick
   bolted on. The sun crosses the sky because the page is a Sunday. The mark
   winks because that IS the mark. Prints develop because this is a
   photography studio. The light follows your hand because light is the whole
   argument. Same rules as 9b: scroll-driven or pointer-driven, never a clock
   left running; transform, opacity and filter only; base state is the
   finished state; everything below is inert under reduced motion.
   ------------------------------------------------------------------------ */

/* — the sun crosses the sky. Now that --sun-cx/--sun-cy actually move (see
     the note where they are declared), a small sun in the fixed sky layer
     tracks them: it comes in from above-right as the hero leaves — cy is
     negative at first light, so in the hero it is off the top of the screen
     and does not double the big sun — climbs to eleven o'clock, and settles
     low on the left by the contact section. The page's own light model, made
     visible. Same masked-mark technique as the list bullets. — */
.sky::after {
  content: "";
  position: absolute;
  left: var(--sun-cx);
  top: var(--sun-cy);
  width: 26px;
  height: 26px;
  translate: -50% -50%;
  background: var(--red);
  border-radius: 50%;                                   /* fallback: a plain red sun */
  filter: drop-shadow(0 0 10px var(--glow)) drop-shadow(0 0 28px var(--glow));
}
@supports (mask: url("img/mark.svg")) or (-webkit-mask: url("img/mark.svg")) {
  .sky::after {
    border-radius: 0;
    -webkit-mask: url("img/mark.svg") center / contain no-repeat;
            mask: url("img/mark.svg") center / contain no-repeat;
  }
}

/* — the wink. The heart eye is the whole charm of the mark, so the mark
     does the one thing a heart-eyed face can do. Two marks wear a second
     copy of the face, #ls-face-wink, whose heart path can animate: the one in
     the nav (on hover — hover the logo and it winks at you) and the big sun
     at the end (once, on arrival, set by the script). The other marks keep
     the still face, so the gesture stays deliberate rather than ambient. The
     path lives inside a <mask> in a sprite outside any section, so :has() on
     body reaches it from the hover. — */
#ls-face-wink .heart { transform-box: fill-box; transform-origin: center; }
@keyframes ls-wink {
  0%, 100% { transform: scaleY(1); }
  40%, 60% { transform: scaleY(0.06); }
}
body:has(.brand:hover) #ls-face-wink .heart,
body.is-winking #ls-face-wink .heart { animation: ls-wink 560ms var(--ease-in-out) 1; }

/* — the promise arrives a word at a time. A load gesture like the sunrise,
     not a scroll one: 60ms in, then 42ms per word, so the whole line has
     landed in under half a second and nobody has waited for anything. — */
@keyframes ls-word { from { opacity: 0; translate: 0 0.35em; } }
.hero h1 .w {
  display: inline-block;
  animation: ls-word var(--dur-slow) var(--ease-out) both;
  animation-delay: calc(60ms + var(--i, 0) * 42ms);
}

@supports (animation-timeline: view()) {
  /* — prints develop. A photograph does not arrive; it comes up. Each print
       in the grid and the contact sheet fades in from a soft, desaturated
       blur as it lands, on its container's timeline, a beat after the settle.
       filter and opacity only, so it costs no layout. — */
  @keyframes ls-develop { from { opacity: 0; filter: blur(14px) saturate(0.35); } }
  .work-item { view-timeline-name: --print; }
  .work-item .frame--print > img { animation: ls-develop linear both; animation-timeline: --print; animation-range: entry 22% entry 88%; }
  .sheet .frame > img { animation: ls-develop linear both; animation-timeline: --sheet; animation-range: entry 28% entry 92%; }

  /* — depth: each discipline's visual drifts a little slower than its copy,
       so the page has a near plane and a far plane. Base translate is 0 at
       the midpoint of the cover range, which is also where it rests under
       reduced motion. — */
  @keyframes ls-parallax { from { translate: 0 34px; } to { translate: 0 -34px; } }
  .discipline__visual { animation: ls-parallax linear both; animation-timeline: view(); animation-range: cover 0% cover 100%; }

  /* — the full stop at the end of the page pops on like the bullets did — */
  .mark--stop { animation: ls-pop linear both; animation-timeline: view(); animation-range: entry 0% entry 100%; }
}

/* — the hand. Light on the wall that follows the pointer, and buttons that
     lean toward it. Pointer devices only, and the script that drives it
     stays off under reduced motion, so a finger or a still page never sees
     any of this. The light only ever LIGHTENS (it is the mat colour at half
     strength), so it can only raise contrast, never lower it. — */
.hand {
  position: fixed;
  inset: 0;
  z-index: -1;                       /* with .sky, behind everything; later in the DOM, so above it */
  pointer-events: none;
  opacity: 0;
  transition: opacity 700ms var(--ease-out);
  background: radial-gradient(24rem 24rem at var(--hx, 50%) var(--hy, 50%), rgb(255 253 251 / 0.55) 0%, rgb(255 253 251 / 0) 70%);
}
body.has-hand .hand { opacity: 1; }
@media (hover: hover) and (pointer: fine) {
  .btn-sun { transition: translate var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out), scale var(--dur-fast) var(--ease-out); }
  .btn-sun.is-drawn { transition-duration: 70ms; }         /* snappy while it follows, eased when it lets go */
  .btn-line [aria-hidden] { display: inline-block; transition: translate var(--dur) var(--ease-out); }
  .btn-line:hover [aria-hidden] { translate: 2px -2px; }  /* the arrow goes where it points */
}

/* ---------------------------------------------------------------------------
   9d  HIDE AND SEEK

   The little sun in the sky stopped being scenery and became a character. It
   hides behind things on the page — the edge of a print, the top of a button,
   the corner of the browser frame — with a little of its face showing, and it
   plays: notice it and it ducks; get close and it runs somewhere else; catch
   it and it winks, squishes, and bolts, a little faster each time. Scroll its
   hiding place off the screen and it runs to a new one. It is never just
   sitting in the open except in the moment of a dash.

   How "behind" works for free: .peek is fixed at z-index -1 like the sky, so
   the page paints over it and any opaque element occludes it. The script only
   has to put it mostly under an edge; the sliver that pokes out is the game.

   Division of labour: the script sets one thing — the element's translate —
   and toggles states. Everything that MOVES is CSS: the dash is a transition
   on translate, the hop, wink, squish and idle look-around are keyframes on
   the inner face. Base state is the finished state throughout. Under reduced
   motion the character is gone and the script never starts; without the
   script, the sky's own scroll-driven sun stays as it was.
   ------------------------------------------------------------------------ */
.peek {
  position: absolute;                        /* NOT fixed: in document coordinates it scrolls with its
                                                hiding place natively. Fixed and re-placed by script on
                                                every scroll frame was always one frame behind the
                                                compositor — the "laggy" the eye caught. */
  left: 0;
  top: 0;
  z-index: -1;                               /* with .sky and .hand: behind the page */
  width: 26px;
  height: 26px;
  pointer-events: none;                      /* the page owns the pointer; the script hit-tests by distance */
  translate: -200px -200px;                  /* parked off-page until placed */
  color: var(--red);
  will-change: translate;
}
/* the glow is a gradient, not a filter: two drop-shadows on a moving element
   re-rasterise every frame and that, too, read as lag */
.peek::before {
  content: "";
  position: absolute;
  inset: -80%;
  border-radius: 50%;
  z-index: -1;
  background: radial-gradient(circle, var(--glow) 0%, rgb(255 217 194 / 0) 62%);
  opacity: 0.9;
}
.peek__face { display: block; width: 100%; height: 100%; transform-origin: 50% 60%; }
body.has-peek .sky::after { display: none; }   /* the character replaces the sky's sun */

/* — how it moves. The script only ever sets a destination; these decide how
     it gets there. Tracking a hiding place through a scroll must be instant
     or it smears; a dash is a sprint with a little overshoot; a duck is
     quick; the first reveal is slow. — */
.peek.is-tracking  { transition: none; }
.peek.is-ducking   { transition: translate 180ms var(--ease-out); }
.peek.is-revealing { transition: translate 560ms var(--ease-out); }
.peek.is-dashing   { transition: translate var(--dash, 700ms) cubic-bezier(0.22, 0.9, 0.32, 1.06); }
/* going home: the run to the big sun at the end is longer and softer, and it
   dives in — up, then down to nothing — as it arrives */
.peek.is-homing    { transition: translate var(--dash, 1000ms) cubic-bezier(0.35, 0.6, 0.25, 1); }

/* — how it lives. Idle: a slow look-around, so it never reads as a sticker.
     Dashing: it hops. Caught: it winks and squishes. — */
@keyframes ls-lookaround {
  0%, 100% { translate: 0 0;    rotate: 0deg; }
  32%      { translate: 0 -2px; rotate: -7deg; }
  64%      { translate: 0 1px;  rotate: 6deg; }
}
@keyframes ls-hop {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -18px; }
}
@keyframes ls-squish {
  0%   { scale: 1 1; }
  22%  { scale: 1.28 0.76; }
  48%  { scale: 0.88 1.18; }
  74%  { scale: 1.08 0.94; }
  100% { scale: 1 1; }
}
@keyframes ls-dive {
  0%   { translate: 0 0;     scale: 1; }
  45%  { translate: 0 -28px; scale: 1.08; }
  100% { translate: 0 0;     scale: 0; }
}
@keyframes ls-emerge { from { scale: 0; } }
@keyframes ls-catch {
  0%   { scale: 1; }
  30%  { scale: 1.07; }
  62%  { scale: 0.96; }
  100% { scale: 1; }
}
.peek__face                   { animation: ls-lookaround 4.4s ease-in-out infinite; }
.peek.is-dashing  .peek__face { animation: ls-hop var(--dash, 700ms) var(--ease-in-out) 1; }
.peek.is-caught   .peek__face { animation: ls-squish 620ms var(--ease-in-out) 1; }
.peek.is-homing   .peek__face { animation: ls-dive var(--dash, 1000ms) var(--ease-in-out) forwards; }
.peek.is-home     .peek__face { animation: none; scale: 0; }      /* inside the big sun */
.peek.is-escaping .peek__face { animation: ls-emerge 380ms var(--ease-out) both; }
/* the big sun catches it: a small satisfied bounce on the disc, and the wink
   it already knows how to do. On the inner svg so the sunrise animation on
   the mark itself is left alone. */
.mark--anchor.is-catching > svg { animation: ls-catch 640ms var(--ease-in-out) 1; transform-origin: center; }
#ls-face-peek .heart { transform-box: fill-box; transform-origin: center; }
body:has(.peek.is-caught) #ls-face-peek .heart,
body:has(.peek.is-winking) #ls-face-peek .heart { animation: ls-wink 560ms var(--ease-in-out) 1; }

/* ---------------------------------------------------------------------------
   10  PAGE LAYOUT — site/index.html
   Section layout only, built on everything above. NO new colours and NO new
   text-on-surface pairings: every piece of type below sits on --paper, --mat
   or --blush in --ink, --ink-soft, --red-ink, or display-sized --red, and all
   of those pairings are declared and measured in section 2. Shadows are only
   the ones section 3 derives from the sun. Nothing here is dark.
   ------------------------------------------------------------------------ */

/* the one inlined copy of the mark's mask. Instances on the page are a single
   currentColor circle that references it, so the SVG is inlined once, drawn
   many times, and never carries a duplicate id. */
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* skip link: off-canvas until it has focus. Fixed, not absolute: absolute
   would resolve against the document origin and drag a mid-page reader back
   to the top on their first Tab. body has no transform/filter/contain, so
   the viewport is the containing block. */
.skip { position: fixed; z-index: 100; top: -100vh; left: var(--gutter); }
.skip:focus { top: var(--space-3); }
/* the skip target takes focus (tabindex=-1) but is not a control: no ring */
main:focus-visible { outline: none; }

/* — nav: the mark, three links, one call to action. Static, transparent, so
     the sky is continuous behind the hero. — */
.nav__row { display: flex; align-items: center; gap: var(--space-4); min-height: 4.25rem; padding-block: var(--space-3); }
.brand {
  display: inline-flex; align-items: center; gap: var(--space-3); min-height: var(--tap);
  color: var(--ink); text-decoration: none;
  font-family: var(--font-display); font-weight: 500; font-size: 1.375rem; line-height: 1;
  font-variation-settings: "SOFT" 100, "WONK" 1; letter-spacing: var(--tracking-display);
}
.brand:hover { color: var(--ink); }
.nav__links { display: none; gap: var(--space-5); margin-inline-start: auto; }
.nav__links a { color: var(--ink); text-decoration: none; font-weight: 500; font-size: var(--text-sm); padding: 0.75em 0; }
.nav__links a:hover { color: var(--red-ink); text-decoration: underline; text-underline-offset: 0.2em; }
.nav__cta { margin-inline-start: auto; font-size: var(--text-sm); }
@media (min-width: 48rem) {
  .nav__links { display: flex; }
  .nav__cta { margin-inline-start: 0; font-size: var(--text-base); }
}

/* The same four links stay available on a phone. A second, compact row
   avoids a hidden menu and keeps the brand and contact button intact. */
@media (max-width: 47.99rem) {
  .nav__row { flex-wrap: wrap; gap: var(--space-1) var(--space-3); }
  .brand { flex-shrink: 0; white-space: nowrap; font-size: 1.25rem; gap: var(--space-2); }
  .nav__links { display: flex; order: 3; flex-basis: 100%; margin-inline-start: 0; justify-content: space-between; gap: var(--space-1); }
  .nav__links a { display: inline-flex; align-items: center; min-height: var(--tap); white-space: nowrap; }
  .nav__cta { flex-shrink: 0; }
}

/* The client door stays visible beside the studio name. The four section
   links retain their own row whenever both rows need breathing room. */
.nav__actions { display: flex; align-items: center; gap: var(--space-4); margin-inline-start: auto; flex-shrink: 0; }
.nav__login { display: inline-flex; align-items: center; justify-content: center; min-height: var(--tap); color: var(--ink); font-size: var(--text-sm); font-weight: 600; white-space: nowrap; text-decoration-color: var(--line-strong); text-underline-offset: .25em; }
.nav__login:hover { color: var(--red-ink); }
@media (max-width: 67.99rem) {
  .nav--client .nav__row { flex-wrap: wrap; gap: var(--space-1) var(--space-3); }
  .nav--client .nav__links { display: flex; order: 3; flex-basis: 100%; margin-inline-start: 0; justify-content: space-between; }
  .nav--client .nav__links a { display: inline-flex; align-items: center; min-height: var(--tap); white-space: nowrap; }
}
@media (max-width: 47.99rem) {
  .nav__actions .nav__cta { display: none; }
  .nav__login { padding-inline: .8em; border: 1px solid var(--line-strong); border-radius: var(--r-pill); }
}

/* Client login uses the same warm mat and plum type as a print caption.
   All pairings below are already measured on --mat and --paper above. */
html.client-login-open, html.client-login-open body { overflow: hidden; }
.client-login {
  --mat: #FFFDFB;
  --paper: #FEDEED;
  --ink: #3B1C24;
  --ink-soft: #7D4B53;
  --red-ink: #BD1212;
  width: min(480px, calc(100% - 32px));
  max-width: none;
  max-height: calc(100vh - 32px);
  max-height: calc(100dvh - 32px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px));
  margin: auto;
  padding: 0;
  border: 1px solid rgb(125 75 83 / .16);
  border-radius: 24px;
  background: var(--mat);
  color: var(--ink);
  box-shadow: 0 24px 90px rgb(59 28 36 / .22), 0 2px 8px rgb(59 28 36 / .08);
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.client-login::backdrop { background: rgb(59 28 36 / .46); backdrop-filter: blur(6px); }
.client-login__sheet { padding: 20px clamp(22px, 5vw, 40px) 28px; }
.client-login__top { position: sticky; top: 0; z-index: 1; display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 0 -10px 12px 0; background: var(--mat); }
.client-login__eyebrow { color: var(--ink-soft); font-size: .75rem; font-weight: 600; letter-spacing: .12em; text-transform: uppercase; }
.client-login__close, .team-login__close, .model-login__close { display: grid; place-items: center; flex: 0 0 44px; width: 44px; height: 44px; border-radius: 50%; color: var(--ink); }
.client-login__close:hover, .team-login__close:hover, .model-login__close:hover { background: var(--paper); }
.client-login__close svg, .team-login__close svg, .model-login__close svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }
.client-login h2 { font-size: clamp(2rem, 7vw, 2.65rem); font-weight: 400; line-height: 1.12; letter-spacing: -.035em; overflow-wrap: break-word; }
.client-login__intro { margin-top: 12px; color: var(--ink-soft); font-size: 1rem; line-height: 1.5; }
.client-login__form { display: grid; gap: 10px; margin-top: 28px; }
.client-login__form label { font-size: .9rem; font-weight: 600; }
.client-login__form input { display: block; width: 100%; min-width: 0; min-height: 52px; padding: 12px 14px; background: #fff; color: var(--ink); border: 1px solid rgb(125 75 83 / .42); border-radius: 10px; font-size: 16px; line-height: 1.5; }
.client-login__form input:focus-visible { outline-width: 2px; outline-offset: 2px; }
.client-login__hint { color: var(--ink-soft); font-size: .82rem; line-height: 1.45; }
.client-login__submit { width: 100%; min-height: 50px; margin-top: 10px; font-size: 1rem; white-space: normal; line-height: 1.25; }
.client-login__delivery { margin-bottom: 8px; font-size: .94rem; line-height: 1.5; color: var(--ink-soft); }
.client-login__delivery strong { color: var(--ink); overflow-wrap: anywhere; }
.client-login__form .client-login__code { font-family: var(--font-mono); font-size: 1.4rem; letter-spacing: .25em; }
.client-login__options { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0 12px; }
.client-login__text-button { min-height: 44px; padding: 8px 0; font-size: .85rem; text-decoration: underline; text-underline-offset: .22em; color: var(--ink-soft); }
.client-login button:disabled { cursor: default; }
.client-login__text-button:disabled { text-decoration: none; }
.client-login__submit:disabled { background: var(--ink-soft); box-shadow: none; translate: none; }
.client-login__status { margin-top: 16px; padding: 12px 14px; border-radius: 10px; background: var(--paper); font-size: .88rem; line-height: 1.5; overflow-wrap: anywhere; }
:is(.client-login__help, .team-login__help, .model-login__help) { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 0 .35em; margin-top: 16px; padding-top: 14px; border-top: 1px solid rgb(125 75 83 / .18); color: var(--ink-soft); font-size: .85rem; }
:is(.client-login__help, .team-login__help, .model-login__help) a { display: inline-flex; align-items: center; min-height: 44px; color: var(--ink); }
.client-login__portals { margin-top: 28px; font-size: .95rem; }
.client-login__choices { display: grid; gap: 10px; margin-top: 16px; }
.client-login__choices a { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 16px; min-height: 60px; border: 1px solid rgb(125 75 83 / .25); border-radius: 12px; color: var(--ink); text-decoration: none; background: #fff; }
.client-login__choices a:hover { background: var(--paper); }
.client-login__choices a span:first-child { min-width: 0; overflow-wrap: anywhere; }
.client-login__choices a span:last-child { flex: 0 0 auto; color: var(--red-ink); }
.client-login [hidden] { display: none; }
@media (max-width: 23rem) {
  .client-login { width: calc(100% - 20px); border-radius: 20px; }
  .client-login__sheet { padding: 12px 20px 18px; }
  .client-login__form { margin-top: 22px; }
}
@media (prefers-reduced-motion: no-preference) {
  .client-login[open] { animation: ls-lightbox-in 180ms var(--ease-out); }
}

/* — section heads — */
.section__head { display: grid; gap: var(--space-3); max-width: 40rem; margin-block-end: var(--space-7); }
.section--tight { padding-block-start: var(--space-6); }

/* — one photograph, given room; three ways to see the studio’s work. — */
.hero { padding-block: clamp(2rem, 6vw, 5rem) clamp(4rem, 9vw, 8rem); }
.hero__grid { display: grid; gap: 3.75rem; align-items: center; }
.hero__copy { min-width: 0; }
.hero__copy .eyebrow { font-size: .68rem; letter-spacing: .13em; }
.hero h1 { margin-top: 1.25rem; font-size: clamp(3.4rem, 6vw, 5.6rem); line-height: 1.06; letter-spacing: -.045em; }
.hero h1 em { font-weight: 400; color: var(--red-ink); }
.hero .lede { max-width: 34ch; margin-top: 1.7rem; font-size: clamp(1.08rem, 1.55vw, 1.25rem); line-height: 1.6; }
.hero__actions { margin-top: 2rem; gap: .7rem 1.5rem; }
.hero__actions .link { display: inline-flex; align-items: center; gap: .4em; min-height: var(--tap); white-space: nowrap; }
.hero__signature { margin-top: 1.7rem; color: var(--ink-soft); font-family: var(--font-display); font-style: italic; font-size: 1.1rem; }
.hero-studio { position: relative; min-width: 0; width: 100%; max-width: 27rem; margin-inline: auto; padding-top: 1.9rem; }
.hero-studio__feature { min-width: 0; }
.hero-studio__print { --ar: 4 / 5; --mat-w: 10px; --frame-max: none; width: 100%; }
.hero-studio__print .photo-paper::after { box-shadow: 0 15px 38px rgb(85 35 53 / .1), 0 0 0 1px rgb(125 75 83 / .12); }
.hero-studio__caption { margin-top: 1.2rem; color: var(--ink-soft); font-family: var(--font-display); font-style: italic; font-size: clamp(1.2rem, 1.8vw, 1.45rem); line-height: 1.3; text-align: center; }
.hero-studio__choices { display: flex; justify-content: center; gap: 1.75rem; margin-top: .6rem; }
.hero-studio__choice { position: relative; display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; padding-inline: .2rem; color: var(--ink-soft); text-decoration: none; font-size: .86rem; font-weight: 500; }
.hero-studio__choice::after { content: ""; position: absolute; left: .2rem; right: .2rem; bottom: .4rem; border-bottom: 1px solid transparent; }
.hero-studio__choice.is-selected { color: var(--red-ink); }
.hero-studio__choice.is-selected::after { border-color: currentColor; }
.hero-studio__choice:hover { color: var(--red-ink); }
.hero-studio__choice:focus-visible { outline: 2px solid var(--red-ink); outline-offset: 3px; border-radius: 2px; }
.hero-studio__status { margin-top: .8rem; color: var(--red-ink); font-size: .8rem; text-align: center; }
.hero-studio__status[hidden] { display: none; }
@media (prefers-reduced-motion: no-preference) {
  @keyframes ls-hero-print { from { opacity: .45; filter: saturate(.7); } to { opacity: 1; filter: saturate(1); } }
  .hero-studio__print.is-developing img { animation: ls-hero-print 320ms var(--ease-out); }
}
@media (min-width: 60rem) {
  .hero__grid { grid-template-columns: minmax(0, 1.02fr) minmax(0, 1fr); gap: clamp(3rem, 5vw, 6rem); }
  .hero-studio { margin-top: .5rem; }
}
@media (min-width: 40rem) and (max-width: 59.99rem) {
  .hero__copy { max-width: 42rem; }
  .hero h1 { font-size: 4.4rem; }
  .hero .lede { max-width: 42ch; }
  .hero-studio { max-width: 27rem; }
}
@media (max-width: 39.99rem) {
  .hero { padding-block-start: 1.65rem; }
  .hero__grid { gap: 2.5rem; }
  .hero h1 { margin-top: 1rem; font-size: clamp(2.35rem, 11.5vw, 4rem); line-height: 1.04; }
  .hero .lede { margin-top: 1.25rem; max-width: 35ch; font-size: 1rem; line-height: 1.55; }
  .hero__actions { margin-top: 1.4rem; gap: .35rem 1rem; }
  .hero__actions .btn { padding-inline: 1.25em; font-size: .9rem; min-height: 50px; }
  .hero__actions .link { font-size: .88rem; }
  .hero__signature { display: none; }
  .hero-studio { width: calc(100% - 12px); max-width: 23rem; padding-top: 1.9rem; }
  .hero-studio__print { --mat-w: 8px; }
  .hero-studio__caption { margin-top: 1rem; font-size: 1.2rem; }
  .hero-studio__choices { gap: 1.5rem; }

}
@media (max-width: 30rem) {
  .contact .statement { font-size: clamp(2.75rem, 1rem + 9vw, 3.5rem); }
}

/* — the statement, alone — */
.statement-block { padding-block: var(--space-7) var(--space-8); }
.statement { max-width: 16ch; }
.statement__tail {
  display: block; margin-block-start: var(--space-5); max-width: 24ch;
  font-size: var(--text-2xl); font-style: italic; font-weight: 400;
  line-height: var(--leading-snug); letter-spacing: var(--tracking-display);
  color: var(--ink-soft);
}

/* — social content, film and photography — */
.disciplines { display: grid; gap: var(--space-8); }
.discipline { display: grid; gap: var(--space-6); align-items: center; }
.discipline__visual { display: grid; justify-items: center; min-width: 0; }
.discipline__copy { display: grid; gap: var(--space-5); align-content: start; }
.discipline__title { display: grid; gap: var(--space-3); max-width: 20ch; font-size: var(--text-2xl); line-height: var(--leading-tight); }  /* an h3 for the outline, sized as the h2 it reads as */
.discipline__name {
  font-family: var(--font-mono); font-weight: 400; font-size: var(--text-xs); line-height: 1.4;
  letter-spacing: var(--tracking-caps); text-transform: uppercase; color: var(--ink-soft);
}
.discipline__lead > * + * { margin-block-start: var(--space-5); }
.discipline__lead p { max-width: var(--measure); }
.discipline__list > * + * { margin-block-start: var(--space-5); }
.discipline__for { font-style: italic; color: var(--ink-soft); max-width: var(--measure-narrow); }
@media (min-width: 56rem) {
  .discipline:not(.discipline--wide) { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--space-7); }
  .discipline--flip .discipline__visual { order: -1; }
  .discipline--wide .discipline__copy { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: var(--space-5) var(--space-8); align-items: start; }
}

/* the social tile is a finished 1:1 post, so inside the phone it is shown
   whole on a light feed rather than cropped through its own headline. The
   device is 9:12.5 — still phone-shaped, but the tile fills it with a
   status-bar gap above and a caption strip below rather than a 9:16 void. The
   label sits under the phone, on paper, where it cannot wrap inside the bezel. */
.phone { display: grid; gap: var(--space-4); justify-items: center; width: 100%; max-width: 320px; }
/* The social tile is a 1:1 post and is now framed as one. It used to sit in a
   phone body at 9/12.5 with object-fit:contain, which left a band of empty mat
   above and below a square picture — a void, not a margin. The phone bezel now
   belongs to the reel alone, which is genuinely 9:16, so each discipline wears
   the shape of its own medium: square post, contact sheet, phone reel, browser. */

/* a four-up contact sheet stays two-by-two at every width */
.sheet--four { grid-template-columns: repeat(2, minmax(0, 1fr)); width: 100%; max-width: 32rem; }
.sheet figcaption { display: flex; justify-content: space-between; gap: var(--space-2); }

/* — the case study — */
.case { display: grid; gap: var(--space-6); }
.case .browser { width: 100%; max-width: 60rem; margin-inline: auto; }
.browser .frame--screen > .mark { position: absolute; z-index: 1; top: 12%; right: 10%; --mark-size: clamp(2rem, 5vw, 3.5rem); }
.case__body { display: grid; gap: var(--space-6); }
.case__proof > * + * { margin-block-start: var(--space-5); }

/* — Whitefish, the independent concept: told the way the case study under it
     is told. A browser on the wall, the atlas inside it, the words beneath —
     not a screenshot floating beside a headline. The screen keeps the atlas's
     own proportions (a wide monitor) rather than cropping a live UI to 16:10;
     the one control is the mat pill the reel and the gallery already use,
     carrying the sun; and the whole window is the link, so the pill is a
     promise the frame keeps wherever it is pressed. */
.atlas { display: block; color: inherit; text-decoration: none; }
.atlas .browser__bar { display: flex; }                /* the bar is a span now: one link, no link inside it */
.atlas .frame { display: block; }
.frame--atlas { --ar: 3020 / 1456; --frame-max: none; }
.atlas .demo__launch { left: 4%; top: auto; bottom: 7%; translate: 0 0; }   /* bottom-left: off the pins, where the eye lands last */
.atlas .demo__launch .mark { --mark-size: 1.15em; }
.atlas:hover .demo__launch,
.atlas:focus-visible .demo__launch { transform: translateY(-1px); box-shadow: var(--shadow-lg), var(--shadow-contact); }
.atlas:focus-visible { outline: 2px solid var(--red-ink); outline-offset: 4px; }
.atlas-head h2 em { color: var(--red-ink); }
.atlas__credit { text-align: right; color: var(--ink-soft); margin-block-start: calc(-1 * var(--space-4)); }
@media (min-width: 56rem) {
  .case__body { grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: var(--space-8); align-items: start; }
}

/* — website creation: one shared frame for the service and both projects — */
.website-studio__frame {
  padding: clamp(1.5rem, 4vw, 3rem);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-3);
  background: var(--mat);
}
.website-studio__intro { display: grid; gap: var(--space-5); }
.website-studio__intro h2 { max-width: 19ch; margin-block-start: var(--space-3); }
.website-studio__intro h2 em { color: var(--red-ink); }
.website-studio__copy { display: grid; align-content: start; gap: var(--space-4); }
ul.website-studio__details {
  display: grid; gap: var(--space-3) var(--space-5);
  margin-block: var(--space-6); padding-block: var(--space-5);
  border-block: 1px solid var(--line);
  list-style: none; font-size: var(--text-sm); color: var(--ink-soft);
}
.website-studio__details li { display: flex; align-items: baseline; gap: .65em; }
.website-studio__details li::before { content: "↗"; color: var(--red-ink); }
.website-studio__projects { display: grid; gap: var(--space-7); }
.website-project { display: flex; flex-direction: column; min-width: 0; scroll-margin-top: 8rem; }
.website-project .browser { width: 100%; flex: none; }
.website-project .demo__stage { --ar: 3020 / 1456; }
@media (min-width: 40.001rem) {
  .website-project .demo__stage { display: grid; align-content: center; justify-items: center; gap: var(--space-3); padding: var(--space-4); }
  .website-project .demo__stage .wall-plate,
  .website-project .demo__stage .demo__launch { position: static; translate: none; top: auto; left: auto; }
  .website-project .demo__stage .wall-plate { width: min(26%, 140px); padding: 8px; }
}
.website-project__caption { display: grid; gap: var(--space-3); margin-block-start: var(--space-5); }
.website-project__caption .eyebrow { font-size: .65rem; letter-spacing: .09em; }
.website-project__caption h3 { font-size: clamp(1.5rem, 2.4vw, 2rem); line-height: var(--leading-snug); }
.website-project__caption > p:last-child { font-size: var(--text-sm); }
.website-project .atlas__credit { order: 1; margin-block-start: var(--space-3); text-align: left; font-size: .65rem; }
.website-studio__footer {
  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
  gap: var(--space-4); margin-block-start: var(--space-6);
  padding-block-start: var(--space-5); border-top: 1px solid var(--line);
}
.website-studio__footer p { font-family: var(--font-display); font-size: var(--text-lg); }
@media (min-width: 56rem) {
  .website-studio__intro { grid-template-columns: 1fr 1fr; gap: var(--space-6); }
  .website-studio__details { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .website-studio__projects { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-6); }
}
@media (max-width: 40rem) {
  .website-studio__frame { padding: var(--space-6) 0; border-inline: 0; border-radius: 0; background: none; }
  .website-project .demo__stage { --ar: 4 / 3; }
  .website-studio__footer .btn { width: 100%; white-space: normal; text-align: center; }
}

/* — the work: prints in columns, captions beneath — */
/* (the work grid is laid out in section 13, THE DARKROOM) */
/* the grid reconverges on the offer: one line and the red pill, then the case study */
.work__cta { display: grid; gap: var(--space-5); justify-items: start; max-width: var(--measure); margin-block-start: var(--space-4); }

/* — how we work: four beats, red numerals — */
.steps { display: grid; gap: var(--space-6); }
.step { display: grid; gap: var(--space-3); align-content: start; padding-block-start: var(--space-4); border-top: 1px solid var(--line-strong); }
.step__n {
  font-family: var(--font-display); font-weight: 500; font-size: var(--text-2xl); line-height: 1;
  font-variation-settings: "SOFT" 100, "WONK" 1;
  color: var(--red-ink);                   /* --red-ink on --paper. Not --red: these numerals pass under the golden-hour glow, where bright red measures 2.6–2.7:1 against a 3.0 floor; red-ink measures 4.16 (see the wash note in section 3) */
}
.step h3 { max-width: 14ch; }
.step p { max-width: var(--measure-narrow); }
@media (min-width: 40rem) { .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-7) var(--space-6); } }
@media (min-width: 72rem) { .steps { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* — contact: the warmest, largest room on the page — */
.contact { text-align: center; }
.contact__inner { display: grid; justify-items: center; gap: var(--space-5); max-width: 44rem; margin-inline: auto; }
.contact__sun { display: block; margin-block-end: var(--space-4); }
.contact .statement { max-width: 12ch; }
.contact .lede { max-width: var(--measure); }
.contact__actions { justify-content: center; gap: var(--space-4); margin-block-start: var(--space-4); }
.contact__direct { margin-block-start: var(--space-4); }
.contact__direct .link--big { overflow-wrap: anywhere; display: inline-block; padding-block: 0.5em; }   /* a 44px hit box around the address; inline-block so a long address still wraps */

/* — footer — */
.footer { padding-block: var(--space-7) var(--space-8); border-top: 1px solid var(--line); }
.footer__row { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: var(--space-5) var(--space-7); }
.footer__text { font-size: var(--text-sm); color: var(--ink-soft); max-width: 44ch; }
.footer__text .mark--stop { margin-inline-start: 0.12em; vertical-align: -0.4em; }
.footer__links { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-5); font-size: var(--text-sm); }
.footer__links a { min-height: var(--tap); align-content: center; color: var(--ink-soft); text-decoration-color: var(--line-strong); display: inline-block; padding-block: 0.6em; }   /* The label stays quiet; its full 44px row is comfortable to tap. */
.footer__links a:hover { color: var(--ink); }


@media (max-width: 47.99rem) {
  .footer__links { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); width: 100%; gap: var(--space-1) var(--space-5); }
}

/* ---------------------------------------------------------------------------
   11  THE REEL — the aerial film. site/index.html #aerial, site/love-sunday.js
   The one piece of moving picture on the page, and the proof the aerial
   discipline was missing. It is VERTICAL, 9:16, because that is what a
   social-first studio delivers and the frame it is watched in — so it wears
   the same phone-shaped bezel as the social work, at a true 9:16 and a little
   larger. Nothing is letterboxed and nothing is pillared.

   HOW IT LOADS (the script enforces it; this section only dresses the states)
     idle      no <video> exists. A lazy poster <img>, the mark as a small sun
               on a mat disc top right, and one control: a big mat disc in the
               centre. Without JS the control is a link to the film.
     is-armed  the script has built the <video> and attached its sources —
               only once the reader is within a viewport of the frame, and
               never on a saveData / 2g / reduced-motion visit until tapped.
     is-live   it has played at least once: the control retires to the
               bottom-left corner at tap size and becomes the pause/play toggle.
     is-playing  the glyph is a pause bar. Cleared whenever it pauses —
               including when it scrolls out of view and pauses itself.

   Colour: NO new text-on-surface pairings. The only type is the notation and
   caption under the frame, on paper, already declared in section 2. Over the
   picture there are two mat discs — the mark in --red on one (the rule for
   the mark over a photograph, section 7) and an --ink glyph on the other;
   --ink on --mat measures 15.02:1, far past the 3:1 non-text floor.
   The 40px bug never meets the control: the control is centred when idle and
   bottom-left when live, and there are no notation pills inside the frame.
   ------------------------------------------------------------------------ */
.reel { display: grid; gap: var(--space-4); justify-items: center; width: 100%; max-width: 360px; }

/* the stage: the phone bezel around a 9:16 picture. 360 CSS px is the cap
   because the poster and the film are 720 px wide: inside the 5px bezel that
   is 350 CSS px, 700 device px on a 2x phone — never upscaled. The picture,
   not the bordered box, carries the ratio: aspect-ratio on a border-box gives
   the 9:16 to the outer edge and leaves the content box at 350×630 (0.556),
   which would crop about 2px a side off the film. With the box's ratio off,
   the poster's own width/height attributes size the stage — reserved before
   the file arrives, so nothing shifts — and the video at inset: 0 lands on
   it exactly. --ar stays for the no-aspect-ratio fallback in section 6. */
.frame--film { --ar: 9 / 16; --frame-max: 360px; aspect-ratio: auto; }
.frame--film > img { height: auto; }
.frame--film > video {
  position: absolute;
  inset: 0;                       /* the padding box: inside the 5px bezel */
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: calc(clamp(20px, 9%, 34px) - 5px);
  background: var(--ink);
}

/* the mark, as the studio's bug: a 24px sun on a 40px mat disc, top right,
   where the footage is sky. Overrides the frame rule that hides a mark once
   an <img> is present — here the picture and the sun are meant to share. */
.frame.frame--film > .mark {
  display: block;
  position: absolute;
  z-index: 2;
  inset: var(--space-3) var(--space-3) auto auto;
  --mark-size: 2.5rem;
  padding: 0.5rem;
  color: var(--red);
  background: var(--mat);
  border-radius: 50%;
  box-shadow: var(--shadow-sm), var(--shadow-contact);
}

/* the control. One element, two glyphs, three states. */
.reel__play {
  position: absolute;
  z-index: 3;
  top: 50%;
  left: 50%;
  translate: -50% -50%;
  display: grid;
  place-items: center;
  width: 4.5rem;
  height: 4.5rem;
  border-radius: 50%;
  color: var(--ink);
  background: var(--mat);
  box-shadow: var(--shadow), var(--shadow-contact);
  text-decoration: none;
  transition: box-shadow var(--dur-fast) var(--ease-out), scale var(--dur-fast) var(--ease-out);
}
.reel__play:hover { color: var(--ink); scale: 1.04; box-shadow: var(--shadow-lg), var(--shadow-contact); }
.reel__play:active { scale: 0.98; transition-duration: 60ms; }
.reel__play:focus-visible { border-radius: 50%; outline-color: var(--red-ink); box-shadow: 0 0 0 3px var(--mat), var(--shadow); }
.reel__glyph { width: 40%; height: 40%; fill: currentColor; }
.reel__glyph--play { translate: 6% 0; }                        /* optical centre: a triangle sits left of its box */
.reel__glyph--pause, .reel.is-playing .reel__glyph--play { display: none; }
.reel.is-playing .reel__glyph--pause { display: block; }

/* once it has played, the control steps out of the picture: bottom left, tap
   size, still visible. It never hides, so pausing is always one tap away. */
.reel.is-live .reel__play {
  top: auto;
  left: var(--space-3);
  bottom: var(--space-3);
  translate: 0 0;
  width: var(--tap);
  height: var(--tap);
  box-shadow: var(--shadow-sm), var(--shadow-contact);
}
.reel.is-live .reel__play:hover { box-shadow: var(--shadow), var(--shadow-contact); }

/* the label under the frame, on paper: the mono slate and the caption */
.reel__label { display: grid; gap: var(--space-2); justify-self: stretch; }
.reel__row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); flex-wrap: wrap; }

/* — ROTATE THE PHONE.
     The first film is vertical because that is how it is watched. The second
     is a wedding, and a wedding is watched wide. So the phone turns: the bezel
     rotates a quarter turn and scales to the width it has; the picture inside
     counter-rotates so it reads upright; the mark and the control counter-
     rotate with it; the well the bezel sits in eases from the tall height to
     the wide one so the label below simply follows; and where the column
     allows, the reel widens to a real landscape phone. The script sets two
     numbers — the scale and the well's height — and toggles one class.
     Everything that moves is a transition. The second film loads only when
     someone turns the phone. — */
.reel { transition: max-width var(--turn, 720ms) var(--ease-in-out); }
.reel.is-landscape { max-width: 640px; }
.reel__well { position: relative; width: 100%; display: grid; place-items: center; height: var(--well-h, auto); transition: height var(--turn, 720ms) var(--ease-in-out); }
.reel__stage { transform-origin: center; transition: rotate var(--turn, 720ms) var(--ease-in-out), scale var(--turn, 720ms) var(--ease-in-out); }
.reel.is-landscape .reel__stage { rotate: -90deg; scale: var(--turn-scale, 1); aspect-ratio: 9 / 16; }
/* the picture, wide, inside a tall bezel turned on its side: width = the
   bezel's height, height = the bezel's width, turned a quarter back */
.reel.is-landscape .frame--film > img,
.reel.is-landscape .frame--film > video {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 177.78%;
  height: 56.25%;
  translate: -50% -50%;
  rotate: 90deg;
  object-fit: cover;
  max-width: none;                /* the reset caps media at 100% of the box; this picture is deliberately 178% of it */
  max-height: none;
}
.reel.is-landscape .frame--film > .mark,
.reel.is-landscape .reel__play { rotate: 90deg; }

/* the control that turns it: the notation's register, a phone that turns
   with the phone. Hidden until the script is there to keep the promise. */
.reel__rotate {
  display: none;
  align-items: center;
  gap: 0.55em;
  min-height: var(--tap);
  padding: 0.35em 0.9em;
  border: 1.5px solid var(--red-ink);
  border-radius: 999px;
  background: transparent;
  color: var(--red-ink);                       /* pair: --red-ink on --paper */
  font-family: var(--font-mono);
  font-size: var(--text-mono, 0.72rem);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out), translate var(--dur) var(--ease-out);
}
.reel.can-rotate .reel__rotate { display: inline-flex; }
.reel__rotate:hover { background: var(--red-ink); color: var(--mat); translate: 0 -1px; }   /* pair: --mat on --red-ink */
.reel__rotate:active { translate: 0 0; }
.reel__phone { width: 1.1em; height: 1.1em; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; transition: rotate var(--turn, 720ms) var(--ease-in-out); }
.reel.is-landscape .reel__phone { rotate: -90deg; }


/* ---------------------------------------------------------------------------
   12  THE DEMO
   The case study's browser frame runs the gallery itself. The copy is ours,
   served from our own origin (demo/masterpiece/), so nothing about the
   client's live site — including its X-Frame-Options — has to change.

   Idle, the frame is the composed screen it always was; it has simply gained
   a reason to be pressed. Live, the gallery fills it edge to edge.

   No new text-on-surface pairing: the launch control is --ink on --mat, which
   is already declared and measured, and the notations are the existing
   placeholder register on the same composed surface.
   ------------------------------------------------------------------------ */

/* the piece on the wall: a real print, hung at eye level, with the page's own
   mat-and-shadow treatment so it obeys the same light as every other print.
   Without something hung on it the room had nothing to give it scale and read
   as a blank page rather than a gallery. */
.wall-plate {
  position: absolute;
  left: 50%;
  top: 34%;
  translate: -50% -50%;
  z-index: 1;
  width: min(26%, 300px);
  padding: min(4.5%, 14px);
  background: var(--mat);
  box-shadow: var(--shadow), var(--shadow-contact);
}
.wall-plate img { display: block; width: 100%; height: auto; border-radius: 1px }
.browser.is-live .wall-plate { display: none }

.demo__stage > iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
  /* it arrives without a flash of white over the composed screen */
  animation: ls-demo-in var(--dur-2, .5s) var(--ease, ease) both;
}
@keyframes ls-demo-in { from { opacity: 0 } to { opacity: 1 } }

/* the control: the same mat disc language as the reel, widened to carry a word */
.demo__launch {
  position: absolute;
  left: 50%;
  top: 64%;                 /* under the piece, where a gallery label would be */
  translate: -50% -50%;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: .55em;
  min-height: var(--tap, 44px);
  padding: .7em 1.15em .7em .95em;
  border-radius: 999px;
  background: var(--mat);
  color: var(--ink);                  /* pair: --ink on --mat */
  text-decoration: none;
  box-shadow: var(--shadow-md, 0 6px 18px -8px rgba(0,0,0,.35));
  font-family: var(--font-ui, inherit);
  font-size: var(--text-sm, .9rem);
  font-weight: 500;
  white-space: nowrap;
  transition: transform var(--dur-1, .25s) var(--ease, ease),
              box-shadow var(--dur-1, .25s) var(--ease, ease);
}
.demo__launch:hover { transform: translateY(-1px) }
.demo__glyph { width: 1.1em; height: 1.1em; fill: var(--red); flex: none }

/* once it is running the control retires — the gallery has its own chrome */
.browser.is-live .demo__launch,
.browser.is-live .notation--placeholder,
.browser.is-live > .browser__bar + .frame > .mark { display: none }


/* — fullscreen —
   A walkable gallery in a 958px box is a demo; the same gallery filling the
   screen is the thing itself. The control only exists once the gallery is
   running, and Esc leaves the way it always does, so nothing here invents a
   convention the reader has to learn. The stage is what goes fullscreen (not
   the iframe), so the browser chrome stays behind and the gallery gets the
   whole screen; the aspect-ratio box has to stand down while it does. */
.demo__full {
  position: absolute;
  right: var(--space-3);
  bottom: var(--space-3);
  z-index: 4;
  display: inline-flex;
  align-items: center;
  gap: .5em;
  min-height: var(--tap, 44px);
  padding: .5em .9em;
  border: 0;
  border-radius: 999px;
  background: var(--mat);
  color: var(--ink);                  /* pair: --ink on --mat */
  box-shadow: var(--shadow-md, 0 6px 18px -8px rgba(0,0,0,.35));
  font-family: var(--font-ui, inherit);
  font-size: var(--text-sm, .9rem);
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
}
.demo__full .demo__glyph { fill: none; stroke: var(--red); stroke-width: 2; stroke-linecap: round }
.demo__full[hidden] { display: none }

/* the box that normally holds a 16:10 screen must fill the display instead */
.demo__stage:fullscreen { aspect-ratio: auto; width: 100%; height: 100%; max-width: none; border: 0 }
.demo__stage:-webkit-full-screen { aspect-ratio: auto; width: 100%; height: 100%; max-width: none; border: 0 }
.demo__stage:fullscreen > iframe { position: absolute; inset: 0 }
.demo__stage:-webkit-full-screen > iframe { position: absolute; inset: 0 }

@media (prefers-reduced-motion: reduce) {
  .demo__stage > iframe { animation: none }
  .demo__launch { transition: none }
}

/* ---------------------------------------------------------------------------
   THE ARC WITHOUT THE MOTION

   Killing every animation also kills the light, so a reader who asks for
   reduced motion was getting one palette for all sixteen thousand pixels of
   this page. But the descent through a Sunday is a DESIGN idea, not a motion
   effect — reduced motion means do not move things, not make the design flat,
   and the whole argument of the page is that it ends in the best light of the
   day rather than the same light it started in.

   So the arc is rebuilt statically: three fixed stops instead of a continuous
   sweep, changing only where one section ends and the next begins. Nothing
   moves, nothing transitions, and the reader is still left in golden hour.

   The values are the 35%, 70% and 100% keyframes verbatim, so every pairing
   here is one the checker already measures at the animated stops — the
   contrast guarantees are identical, not merely similar.
   ------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  /* eleven o'clock — the brightest, cleanest moment */
  #make, [data-daylight="morning"] {
    --sun: 0.35;
    --paper: #FFE4EE;  --blush: #FCD5E3;  --mat: #FFFFFF;
    --sky:  #FFFAFC;   --glow:  #FFF0B8;
    --shadow-ink: rgb(74 30 40 / 0.14);
    --red: #FE1717;    --red-ink: #BD1212;  --ink: #3B1C24;  --ink-soft: #7D4B53;
  }
  /* afternoon — the pink warms toward coral */
  #websites, #work, [data-daylight="midday"], [data-daylight="afternoon"] {
    --sun: 0.7;
    --paper: #FFE1DF;  --blush: #FCD1CF;  --mat: #FFFCF7;
    --sky:  #FFF3E8;   --glow:  #FFD6A0;
    --shadow-ink: rgb(92 42 30 / 0.16);
    --red: #FE1717;    --red-ink: #BD1212;  --ink: #3B1C24;  --ink-soft: #7D4B53;
  }
  /* golden hour — where the reader is left, motion or no motion */
  #how, #contact, .footer, [data-daylight="golden"] {
    --sun: 1;
    --paper: #FFE2C8;  --blush: #FBD3B4;  --mat: #FFF8EE;
    --sky:  #FFE9C9;   --glow:  #FFD29B;
    --shadow-ink: rgb(110 52 22 / 0.20);
    --red: #FE1717;    --red-ink: #BD1212;  --ink: #3B1C24;  --ink-soft: #7D4B53;
  }
  /* The sections paint their own ground, since the animated sky wash is what
     normally carries the colour down the page.

     Three discrete stops would leave two visible steps at the section joins,
     so the GROUND ramps continuously between them while the TOKENS stay
     discrete. That split is free: --ink and --red-ink are identical at every
     stop of the day, so text contrast does not depend on where in a ramp a
     line happens to sit — the day warms in hue, not in value. The reader gets
     one unbroken descent; the checker still measures three fixed scopes. */
  #make, [data-daylight="morning"]        { background: linear-gradient(to bottom, #FEDEED, #FFE4EE) }
  #websites, [data-daylight="midday"]    { background: linear-gradient(to bottom, #FFE4EE, #FFE1DF) }
  #work, [data-daylight="afternoon"]        { background: linear-gradient(to bottom, #FFE1DF, #FFE2C8) }
  #how, #contact, .footer, [data-daylight="golden"] { background: #FFE2C8 }
}

/* — the case study's room, on a phone —
   The frame is only ~348x218 there, and the launch control's 44px tap target
   is a fifth of that height. Positioning the piece and the control by
   percentage works on a wide screen and collides on a narrow one: measured at
   390px the plate bottomed at 55% and the button started at 54%, so the label
   sat on the painting, and the piece itself came out a 62px thumbnail.
   So on a phone the room stops being laid out by percentages and simply
   stacks: a taller frame, the piece, the label under it, both centred. The
   gradient behind still paints the wall and the floor. */
@media (max-width: 40rem) {
  .demo__stage {
    --ar: 4 / 3;
    display: grid;
    align-content: center;
    justify-items: center;
    gap: var(--space-4);
    padding: var(--space-5) var(--space-4);
  }
  .demo__stage .wall-plate,
  .demo__stage .demo__launch {
    position: static;
    translate: none;
    top: auto;
    left: auto;
  }
  .demo__stage .wall-plate { width: min(46%, 150px); padding: 8px; }
}

/* ---------------------------------------------------------------------------
   13  THE WORK — landing/index.html #work, love-sunday.js THE LINE
   Six stories, each introduced by a generous pinned photograph. Three to a
   row on a wide screen, two on a tablet, one when a phone needs the room.
   The photos keep the studio's 4:5 prints, white mats and wooden pins. Their
   titles and invitations sit below the moving paper on the page itself.

   THE AIR IS NOT IN THIS FILE. A print on a line is a pendulum, and a
   pendulum has state — where it is and how fast it is going — which a CSS
   keyframe cannot hold. The first build animated a gust on :hover, and every
   time the pointer crossed a peg the swing began again from zero, or was cut
   off in mid-air and eased home by a transition. That was the clunk, and no
   retiming fixes it, because the model was wrong. The motion now lives in
   love-sunday.js (THE LINE): one damped pendulum per print, driven by the
   scroll, the pointer and a slow breeze, writing a single transform to .hung
   each frame. This file supplies the geometry that lets the physics read as
   physics:

     · THE PIVOT. .hung turns about the pin at 50% -21px. The figure
       reserves 30px above the print, keeping the peg clear of the heading
       or the previous row. No line is drawn.
     · THE REST POSE. .frame--hang keeps the base .frame rotate(--tilt),
       about the top of the print, where the peg's jaws hold it crooked. A
       different pivot from the swing, and both are right: the print is
       crooked in the peg; the peg swings on the wire. It also means the tilt
       shows with no script at all, and under reduced motion.
     · THE SHADOW, ON ITS OWN LAYER. A print lifting off the wall throws a
       longer, softer shadow, and animating box-shadow is a repaint of a
       blurred rectangle every frame. So the print's own shadow is off and
       the shadow is .hung::after, promoted to its own layer, whose transform
       (a translateY and a small scale) and opacity follow --lift: a style
       recalc of the button's few children when the script writes it, then
       compositor work — never a layout, never a repaint of the blur.
   ------------------------------------------------------------------------ */
.line-room {
  --peg-1:  #C9A57A;
  --peg-2:  #8F6B45;
  --peg-3:  #D8B78C;
  --spring: #8C8C8C;
  overflow-x: clip;                          /* the prints may swing just beyond the outer gutter */
}
.line-room .section__head { margin-block-end: clamp(1.25rem, 2.5vw, 2rem); }

/* Room for six larger prints, with captions held in the same quiet rhythm. */
.work-grid {
  --gap: clamp(1.75rem, 3.8vw, 3rem);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  column-gap: var(--gap);
  row-gap: clamp(2.25rem, 4vw, 3.5rem);
  padding-block-start: 0;
}
@media (min-width: 38rem) { .work-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 64rem) { .work-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.work-item {
  position: relative;
  display: grid;
  grid-template-rows: auto 1fr;
  gap: 1.5rem;
  align-content: start;
  min-width: 0;
  margin: 0;
  padding-block-start: 30px;                 /* the pin has its own breathing room */
  perspective: 1100px;                       /* so the lift reads as depth, not as a squash */
}
.work-card__caption { min-width: 0; padding-inline: .15rem; }
.work-card__category {
  margin: 0 0 .35rem;
  color: var(--ink-soft);
  font-family: var(--font-mono);
  font-size: .68rem;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: .075em;
  text-transform: uppercase;
  overflow-wrap: break-word;
}
.work-card__story {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .7rem 1rem;
  width: 100%;
  min-width: 0;
  min-height: 48px;
  padding: .2rem 0;
  border: 0;
  border-radius: 2px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  text-align: start;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.work-card__title {
  min-width: 0;
  font-family: var(--font-display);
  font-weight: 500;
  font-variation-settings: "SOFT" 100, "WONK" 1;
  font-size: clamp(1.4rem, 2.3vw, 1.65rem);
  line-height: 1.18;
  letter-spacing: var(--tracking-display);
  text-wrap: balance;
  overflow-wrap: break-word;
}
.work-card__open {
  display: inline-flex;
  align-items: center;
  flex: none;
  gap: .35rem;
  color: var(--red-ink);
  font-family: var(--font-text);
  font-weight: 500;
  font-size: .8rem;
  line-height: 1.4;
  white-space: nowrap;
}
.work-card__story:hover .work-card__title { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: .2em; }
.work-card__story:focus-visible { outline: 3px solid var(--red-ink); outline-offset: 5px; }
@media (max-width: 37.99rem) {
  .line-room .section__head { margin-block-end: 1rem; }
  .work-item { gap: 1.25rem; }
  .work-card__category { font-size: .7rem; }
  .work-card__title { font-size: clamp(1.4rem, 5.6vw, 1.65rem); }
  .work-card__open { font-size: .85rem; }
}
/* The wooden pin is the accent; the photographs keep their hanging motion. */
/* — what hangs: peg and print together, pivoting at the wire. The script owns
     this element's transform; nothing here transitions or animates it. — */
.hung {
  position: relative;
  isolation: isolate;                        /* the shadow below sits behind the print and nowhere else */
  transform-origin: 50% -21px;               /* the wire */
}
@media (prefers-reduced-motion: no-preference) {
  .hung { will-change: transform; }                            /* the script writes this while the line moves; under reduced motion it never starts, so nothing is promoted */
  .line-room .hung::after { will-change: transform, opacity; } /* the shadow layer: without this its --lift-driven transform is a repaint of the blur, not a compositor update */
}
.peg {
  position: absolute;
  top: -30px;
  left: 50%;
  translate: -50% 0;
  z-index: 2;
  width: 13px;
  height: 34px;
  border-radius: 6px 6px 3px 3px / 8px 8px 3px 3px;
  background: linear-gradient(to right, var(--peg-1) 0 47%, var(--peg-2) 47% 53%, var(--peg-3) 53%);
  box-shadow: 0 2px 4px rgb(74 30 40 / 0.35);
}
.peg::after {                                /* the spring */
  content: "";
  position: absolute;
  left: -2px;
  right: -2px;
  top: 13px;
  height: 4px;
  border-radius: 2px;
  background: var(--spring);
  box-shadow: 0 1px 1px rgb(0 0 0 / 0.35);
}
.frame--hang { --ar: 4 / 5; --frame-max: none; --mat-w: 10px; width: 100%; transform-origin: 50% 0; }   /* one size, one mat; crooked in the peg's jaws */
/* the print casts no shadow of its own on the line, and hover changes nothing:
   the pointer's effect is the air it moves, in the script */
.line-room .frame--hang, .line-room .frame--hang:hover {
  rotate: var(--tilt, 0deg);
  box-shadow: none;
  transition: none;
}
/* The shared print rule preserves the mat on hanging photographs too. */
/* — the cast shadow. Same shape as the print, same crookedness (--tilt is set
     on .hung so both the frame and this pseudo see it), the page's own light.
     --lift runs 0..1 with the print's lift off the wall: the shadow drops,
     grows a hair and thins, which is what a shadow does when the thing
     casting it comes toward you. — */
.line-room .hung::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: var(--r-print);
  box-shadow: var(--shadow), var(--shadow-contact);
  rotate: var(--tilt, 0deg);
  transform-origin: 50% 0;
  transform: translateY(calc(var(--lift, 0) * 14px)) scale(calc(1 + var(--lift, 0) * 0.03));
  opacity: calc(1 - var(--lift, 0) * 0.4);
}
.work__cta { margin-block-start: var(--space-8); }

/* ---------------------------------------------------------------------------
   14  THE LIGHTBOX — landing/index.html #lightbox, love-sunday.js

   Six titled prints introduce the client stories. Their short captions are
   an invitation; the full story lives here, one press away. Both the print
   and its caption open the same feature without moving the reader's place.

   <dialog>, not a div: showModal() gives the focus trap, the Esc key, an inert
   background and a backdrop — four things a hand-rolled modal gets wrong, and
   two of them silently.

   It is built as a GALLERY that currently holds one picture each. Every print
   carries a <template class="work-more">; whatever <img> or <video> is dropped
   in there joins the print's own picture, the arrows and the thumb strip appear
   on their own at the second item, and nothing else has to change. That is what
   "somewhere to add more media" means here — an extension point, not a promise.

   Contrast: the frame sits on --mat over a dimmed ground, so the story is
   --ink on --mat (15.02:1, already declared in section 2) and no new pairing
   is introduced.
   ------------------------------------------------------------------------ */

/* the print as a control: a button that looks like nothing, so the hanging
   print is exactly what it was and is now pressable */
.hung {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  cursor: zoom-in;
  -webkit-tap-highlight-color: transparent;
}
.hung:focus-visible { outline: 3px solid var(--red-ink); outline-offset: 6px; }

.lightbox {
  overscroll-behavior: contain;
  width: min(92vw, 68rem);
  max-width: none;
  max-height: 92vh;
  max-height: 92dvh;
  padding: clamp(0.9rem, 2.4vw, 1.6rem);
  border: 0;
  border-radius: var(--r-2);
  background: var(--mat);
  color: var(--ink);                         /* pair: --ink on --mat */
  box-shadow: 0 40px 80px -30px rgb(74 30 40 / 0.55);
  overflow-x: hidden;
  overflow-y: auto;
}
.lightbox::backdrop { background: rgb(59 28 36 / 0.72); backdrop-filter: blur(3px); }
.lightbox[open] { display: grid; gap: var(--space-3); }

.lightbox__stage {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 0;
}
.lightbox__media { display: grid; place-items: center; width: 100%; min-height: 0; }
.lightbox__media img, .lightbox__media video {
  display: block;
  max-width: 100%;
  max-height: min(68vh, 42rem);
  width: auto;
  height: auto;
  border-radius: 2px;
  background: var(--blush);
}

.lightbox__story {
  margin: 0;
  max-width: var(--measure);
  justify-self: center;
  text-align: center;
  font-size: var(--text-sm);
  color: var(--ink-soft);                    /* pair: --ink-soft on --mat */
}
.lightbox__story:empty { display: none }

/* the two controls, in the brand's own shapes */
.lightbox__close, .lightbox__step {
  position: absolute;
  z-index: 2;
  display: grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  border: 0;
  border-radius: 50%;
  background: var(--mat);
  color: var(--ink);
  box-shadow: var(--shadow-sm), var(--shadow-contact);
  cursor: pointer;
  transition: scale var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.lightbox__close { position: fixed; top: env(safe-area-inset-top, 0px); right: env(safe-area-inset-right, 0px); margin: clamp(0.6rem, 2vw, 1.2rem); }
.lightbox__close svg, .lightbox__step svg {
  width: 45%; height: 45%; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round;
}
.lightbox__close:hover, .lightbox__step:hover { scale: 1.06; box-shadow: var(--shadow), var(--shadow-contact); }
.lightbox__close:active, .lightbox__step:active { scale: 0.97 }
.lightbox__step--prev { left: 0 }
.lightbox__step--next { right: 0 }
.lightbox__step[hidden] { display: none }

/* the strip: only ever seen once a piece holds more than one thing */
.lightbox__thumbs { display: flex; gap: var(--space-2); justify-content: center; flex-wrap: wrap; }
.lightbox__thumbs[hidden] { display: none }
.lightbox__thumb {
  width: 3.25rem; height: 3.25rem; padding: 0;
  border: 0; border-radius: 3px; overflow: hidden;
  background: var(--blush); cursor: pointer; opacity: 0.55;
  transition: opacity var(--dur-fast) var(--ease-out), outline-color var(--dur-fast) var(--ease-out);
  outline: 2px solid transparent; outline-offset: 2px;
}
.lightbox__thumb img { width: 100%; height: 100%; object-fit: cover; display: block }
.lightbox__thumb:hover { opacity: 0.85 }
.lightbox__thumb[aria-current="true"] { opacity: 1; outline-color: var(--red) }

/* it arrives the way a print is set down, and only where motion is welcome */
@keyframes ls-lightbox-in { from { opacity: 0; translate: 0 12px; scale: 0.985 } }
.lightbox[open] { animation: ls-lightbox-in 260ms var(--ease-out) }
@media (prefers-reduced-motion: reduce) {
  .lightbox[open] { animation: none }
  .lightbox::backdrop { backdrop-filter: none }
}

/* A client highlight opens as a small editorial, using the same gallery door.
   The feature owns its scroll area so its header and close control stay in
   place; ordinary photograph galleries keep their existing layout. */
html.work-modal-open { overflow: hidden; }
.lightbox__stage[hidden], .lightbox__story[hidden], .lightbox__feature[hidden] { display: none; }
.lightbox.lightbox--feature {
  width: min(1080px, calc(100vw - 2rem));
  height: calc(100vh - 2rem);
  height: calc(100dvh - 2rem);
  max-height: none;
  padding: 0;
  overflow: clip; /* hidden still lets WebKit scroll the close control away when a child receives focus */
  background: var(--mat);
}
.lightbox.lightbox--feature[open] { display: block; }
.lightbox--feature > :is(.lightbox__stage, .lightbox__story, .lightbox__thumbs) { display: none; }
.lightbox--feature .lightbox__feature {
  width: 100%;
  height: 100%;
  min-height: 0;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  scroll-padding-block-start: 5.5rem;
  scrollbar-color: var(--line-strong) var(--mat);
}
.lightbox--feature .lightbox__close {
  position: absolute;
  z-index: 6;
  inset: 1rem 1.25rem auto auto;
  margin: 0;
  width: 44px;
  height: 44px;
  background: var(--mat);
}
.case-study {
  --case-gutter: clamp(1.25rem, 5vw, 3.5rem);
  --peg-1: #C9A57A;
  --peg-2: #8F6B45;
  --peg-3: #D8B78C;
  --spring: #8C8C8C;
  min-width: 0;
  color: var(--ink);
  font-family: var(--font-text);
  font-size: var(--text-base);
  line-height: 1.65;
}
.case-study :is(h2, h3, h4, p, ul, dl, dd) { margin: 0; }
.case-study :is(h2, h3, h4) {
  font-family: var(--font-display);
  font-weight: 500;
  font-variation-settings: "SOFT" 100, "WONK" 1;
  letter-spacing: var(--tracking-display);
  line-height: 1.08;
  text-wrap: balance;
  overflow-wrap: break-word;
}
.case-study h2 { font-size: clamp(2.8rem, 6vw, 4.5rem); }
.case-study h3 { font-size: clamp(1.8rem, 3.8vw, 2.65rem); }
.case-study h4 { font-size: clamp(1.4rem, 2.5vw, 1.8rem); }
.case-study__bar {
  position: sticky;
  top: 0;
  z-index: 4;
  display: flex;
  align-items: center;
  min-height: 76px;
  padding: 1rem 5.75rem 1rem var(--case-gutter);
  border-block-end: 1px solid var(--line);
  background: var(--mat);
  color: var(--ink-soft);
  font-family: var(--font-mono);
  font-size: .72rem;
  line-height: 1.6;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.case-study__body { padding: 3.25rem var(--case-gutter) 0; }
.case-study__intro {
  display: grid;
  grid-template-columns: minmax(0, .78fr) minmax(0, 1.15fr);
  align-items: center;
  gap: clamp(1.75rem, 5vw, 3.5rem);
}
.case-study__portrait {
  position: relative;
  width: 100%;
  max-width: 340px;
  margin: .5rem auto;
  padding: 11px 11px 24px;
  border-radius: var(--r-print, 3px);
  background: var(--mat);
  box-shadow: var(--shadow), var(--shadow-contact);
  rotate: -1.5deg;
}
.case-study__portrait > img {
  display: block;
  width: 100%;
  height: auto;
  object-fit: contain;
}
.case-study :is(.case-study__portrait, .case-post__visual) > .peg {
  top: -22px;
  width: 12px;
  height: 31px;
  pointer-events: none;
}
.case-study .case-study__eyebrow,
.case-study .eyebrow {
  color: var(--ink-soft);
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: .72rem;
  letter-spacing: .08em;
  line-height: 1.55;
  text-transform: uppercase;
}
.case-study__lead > .case-study__eyebrow { margin-block-end: .9rem; }
.case-study__handle { margin-block-start: .55rem !important; }
.case-study__handle a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--red-ink);
  font-size: .95rem;
  text-underline-offset: .2em;
  text-decoration-thickness: 1px;
}
.case-study__lead > .case-study__dek {
  margin-block-start: 1.25rem;
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 2.9vw, 1.75rem);
  font-style: italic;
  line-height: 1.35;
  letter-spacing: -.015em;
}
.case-study__lead > p + p:not(.case-study__dek, .case-study__handle) { margin-block-start: .9rem; }
.case-study__lead > p:not(.case-study__eyebrow, .case-study__handle, .case-study__dek) {
  max-width: 47ch;
  color: var(--ink-soft);
  font-size: 1rem;
  text-wrap: pretty;
}
.case-study__approach {
  margin-block: clamp(2.75rem, 6vw, 4rem);
  padding: clamp(1.4rem, 4vw, 2.25rem);
  border: 1px solid var(--line);
  border-radius: 16px;
  background: var(--sky);
}
.case-study__approach h3 { max-width: 24ch; margin-block-start: .7rem; }
.case-study__approach h3 + p { max-width: 67ch; margin-block-start: 1rem; color: var(--ink-soft); }
.case-study .case-study__services {
  display: flex;
  gap: .55rem;
  flex-wrap: wrap;
  padding: 0;
  margin-block-start: 1.35rem;
  list-style: none;
}
.case-study__services li {
  padding: .4rem .8rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  color: var(--ink);
  background: var(--mat);
  font-size: .82rem;
  line-height: 1.45;
}
.case-study__section-head {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: 1rem 2rem;
  margin-block-end: 2.75rem;
}
.case-study__section-head h3 { max-width: 22ch; margin-block-start: .7rem; }
.case-study__section-head > p { max-width: 22ch; color: var(--ink-soft); font-size: .88rem; }
.case-study__posts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: stretch;
  gap: clamp(1rem, 2.5vw, 1.6rem);
}
.case-post { display: flex; flex-direction: column; min-width: 0; }
.case-post__visual {
  position: relative;
  padding: 8px 8px 17px;
  margin-block-end: 1.45rem;
  border-radius: var(--r-print, 3px);
  background: var(--mat);
  box-shadow: var(--shadow-sm), var(--shadow-contact);
}
.case-post__visual > :is(img, video) {
  display: block;
  width: 100%;
  height: auto;
  object-fit: contain;
  border-radius: 1px;
  background: var(--mat);
}
.case-post__visual > video { background: var(--ink); }
.case-post__body { display: flex; flex: 1; flex-direction: column; align-items: start; min-width: 0; }
.case-post__body > h4 { margin-block-start: .55rem; }
.case-post__body > h4 + p { margin-block-start: .8rem; margin-block-end: 1rem; color: var(--ink-soft); font-size: .92rem; }
.case-post__metrics {
  display: flex;
  flex-wrap: wrap;
  gap: .7rem 1.5rem;
  align-self: stretch;
  margin-block-start: auto !important;
  padding-block: .9rem;
  border-block: 1px solid var(--line);
}
.case-post__metrics dt { color: var(--ink-soft); font-size: .72rem; line-height: 1.4; }
.case-post__metrics dd { margin-block-start: .2rem; color: var(--ink); font-size: 1.15rem; font-variant-numeric: tabular-nums; }
.case-post__link {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  min-height: 44px;
  max-width: 100%;
  margin-block-start: .45rem;
  color: var(--red-ink);
  font-size: .83rem;
  line-height: 1.4;
  text-underline-offset: .2em;
  text-decoration-thickness: 1px;
}
.case-study .case-study__source { margin-block-start: 1.5rem; color: var(--ink-soft); font-size: .76rem; line-height: 1.55; }
.case-study__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem 2rem;
  margin: 2.75rem calc(-1 * var(--case-gutter)) 0;
  padding: clamp(1.75rem, 4vw, 3rem) var(--case-gutter);
  background: var(--paper);
  border-block-start: 1px solid var(--line);
}
.case-study__footer h3 { max-width: 22ch; margin-block-start: .65rem; font-size: clamp(1.7rem, 3.4vw, 2.25rem); }
.case-study__footer .btn {
  flex: none;
  min-height: 48px;
  max-width: 100%;
  color: var(--mat);
  background: var(--red-ink);
  border-color: var(--red-ink);
  font-size: 1rem;
  line-height: 1.35;
  text-align: center;
  white-space: normal;
}
.case-study__footer .btn:hover { color: var(--mat); background: var(--ink); border-color: var(--ink); }
@media (max-width: 800px) {
  .case-study__intro { grid-template-columns: minmax(0, .75fr) minmax(0, 1fr); gap: 1.75rem; }
  .case-study h2 { font-size: clamp(2.5rem, 6vw, 3.5rem); }
  .case-study__section-head, .case-study__footer { align-items: start; flex-direction: column; }
  .case-study__section-head > p { max-width: none; }
  .case-study__posts { grid-template-columns: minmax(0, 1fr); gap: 2.75rem; }
  .case-post { display: grid; grid-template-columns: minmax(0, .8fr) minmax(0, 1fr); gap: 1.5rem; align-items: start; }
  .case-post__visual { margin-block-end: 0; }
  .case-post__metrics { margin-block-start: .1rem !important; }
}
@media (max-width: 600px) {
  .lightbox.lightbox--feature {
    width: 100vw;
    height: 100vh;
    height: 100dvh;
    max-width: 100vw;
    max-height: none;
    inset: 0;
    margin: 0;
    border-radius: 0;
  }
  .lightbox--feature .lightbox__close {
    top: max(.8rem, env(safe-area-inset-top, 0px));
    right: max(.9rem, env(safe-area-inset-right, 0px));
  }
  .case-study { --case-gutter: max(1.25rem, env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px)); }
  .case-study__bar { min-height: 70px; padding-block: max(.75rem, env(safe-area-inset-top, 0px)) .75rem; padding-inline-end: 5rem; font-size: .66rem; }
  .case-study__body { padding-block-start: 2.5rem; }
  .case-study__intro { grid-template-columns: minmax(0, 1fr); gap: 2rem; }
  .case-study__portrait { width: min(75%, 270px); padding: 10px 10px 22px; }
  .case-study__lead > p:not(.case-study__eyebrow, .case-study__handle, .case-study__dek) { max-width: none; }
  .case-study h2 { font-size: clamp(2.7rem, 9vw, 3.5rem); }
  .case-study__lead > .case-study__dek { margin-block-start: .9rem; }
  .case-study__approach { padding: 1.25rem; margin-block: 2.25rem; }
  .case-study__approach h3 + p { font-size: 1rem; }
  .case-study__section-head { margin-block-end: 2.25rem; }
  .case-study__posts { gap: 2.75rem; }
  .case-post { display: flex; gap: 0; }
  .case-post__visual { width: 100%; margin-block-end: 1.35rem; padding: 10px 10px 20px; }
  .case-post__body > h4 + p { font-size: 1rem; }
  .case-post__link { font-size: .9rem; }
  .case-study__footer { margin-block-start: 2.25rem; padding-block-end: max(2rem, env(safe-area-inset-bottom, 0px)); }
  .case-study__footer .btn { width: 100%; }
}
@media (max-height: 450px) and (min-width: 601px) {
  .lightbox.lightbox--feature { height: calc(100vh - 1rem); height: calc(100dvh - 1rem); }
  .case-study__bar { min-height: 64px; padding-block: .6rem; }
  .lightbox--feature .lightbox__close { top: .65rem; }
  .case-study__body { padding-block-start: 2.5rem; }
}

/* Photographs share the clothesline treatment. The original frame remains
   the layout/semantic container; only its paper and peg move about the pin. */
.frame.photo-mount {
  --wire: #B8946A;
  --peg-1: #C9A57A;
  --peg-2: #8F6B45;
  --peg-3: #D8B78C;
  --spring: #8C8C8C;
  padding: 0;
  overflow: visible;
  background: none;
  box-shadow: none;
  rotate: none;
  perspective: 1100px;
}
.frame.photo-mount:hover { rotate: none; box-shadow: none; }
.photo-mount:not(.frame--print) { --mat-w: clamp(6px, 3.5%, 10px); }

.photo-paper {
  position: absolute;
  inset: 0;
  box-sizing: border-box;
  padding: var(--mat-w);
  background: var(--mat, #fff8f4);
  border-radius: var(--r-print, 2px);
  rotate: var(--tilt, 0deg);
  transform-origin: 50% -21px;
  isolation: isolate;
}
/* Keep the shared Safari mat fix: media fills the border box and its padding
   creates the mat. No percentage height against an in-flow content box. */
.photo-paper > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  box-sizing: border-box;
  padding: inherit;
  object-fit: cover;
  object-position: var(--focus, 50% 50%);
  border-radius: 1px;
}
.photo-paper > .peg {
  position: absolute;
  top: -30px;
  left: 50%;
  translate: -50% 0;
  z-index: 2;
  width: 13px;
  height: 34px;
  border-radius: 6px 6px 3px 3px / 8px 8px 3px 3px;
  background: linear-gradient(to right, var(--peg-1) 0 47%, var(--peg-2) 47% 53%, var(--peg-3) 53%);
  box-shadow: 0 2px 4px rgb(74 30 40 / 0.35);
  pointer-events: none;
}
.photo-paper > .peg::after {
  content: "";
  position: absolute;
  inset: 13px -2px auto;
  height: 4px;
  border-radius: 2px;
  background: var(--spring);
  box-shadow: 0 1px 1px rgb(0 0 0 / 0.35);
}
.photo-paper::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  box-shadow: var(--shadow), var(--shadow-contact);
  transform-origin: 50% 0;
  transform: translateY(calc(var(--lift, 0) * 14px)) scale(calc(1 + var(--lift, 0) * 0.03));
  opacity: calc(1 - var(--lift, 0) * 0.4);
  pointer-events: none;
}
/* Contact-sheet rows reserve room for pegs above each photograph. */
.sheet > figure:has(.photo-mount) { padding-block-start: 30px; }
.photo-mount > .mark, .photo-mount > .notation--placeholder { display: none; }
@media (prefers-reduced-motion: no-preference) {
  .photo-paper.is-moving { will-change: transform; }
  .photo-paper.is-moving::after { will-change: transform, opacity; }
}
@media (prefers-reduced-motion: reduce) {
  .photo-paper { transform: none !important; }
  .photo-paper::after { transform: none; opacity: 1; }
}

/* Whitefish: an independent studio concept, alongside the existing work. */
