/* ============================================================
   Sami Masaki — portfolio (V2 redesign kickoff, 2026-08-13)
   Palette: locked to 5 colors per Chloe's V2 brief — pure black, pure
   white, EB1C24 (red), 3262AE (blue), EDA7CB (pink). --cream/--ink/
   --red/--blue keep their V1 NAMES (renaming them everywhere across 3
   CSS files was unnecessary risk for a same-day change) but now hold
   the new values — --cream is literally pure white, --ink literally
   pure black. --lilac/--blush are the one exception: no equivalent in
   the 5-color palette, left as-is on purpose (still used by the opening
   splash's pastel clouds — .social-media moved off --lilac to --cream
   on 2026-08-13, the first section actually redesigned against
   V2EverythingReference.png) until the remaining old-palette spots get
   redesigned too — see Project Status doc.

   Fonts: now split by ROLE for English text — --font-display
   (Widescreen Ex) for headings, --font-copy (ITC Avant Garde Gothic
   Pro) for body/UI copy — replacing the old single --font-sans
   (Montserrat), which was split by LANGUAGE instead. Japanese text
   (any role) moved from DotGothic16 to --font-jp (TA Mincho GF 01),
   replacing the old --font-display (which meant "the Japanese font"
   under the V1 naming — note the meaning of --font-display itself has
   flipped from "Japanese" to "English display type"). All three new
   families are already active in the same Adobe Fonts kit (xab6xhn)
   already embedded in <head> — confirmed via the kit's own wf-*
   loading classes in a live page, not guessed. Weight availability is
   a real constraint, not a preference: Widescreen Ex and ITC Avant
   Garde Gothic Pro each only ship 400/700 (no 300/600/800/900), and TA
   Mincho GF 01 only ships 400 — every font-weight below is snapped to
   whatever that specific family actually has, even where the V1 value
   (900, 800, 300, 600...) doesn't survive the swap. NOTE: Adobe Fonts
   kits are typically domain-locked, so these fonts may not actually
   render on localhost during dev preview even though the CSS is
   correct — falls back to the stacks below until deployed (or the kit's
   domain list includes a local test domain).

   Rebuilt against Chloe's MainPaigeLayoutReference.png mockup —
   the previous magazine-spread build is preserved as-is in
   magazine.html / magazine.css / magazine.js (shelved, unlinked,
   frozen so this file's ongoing rewrite can't break it).
   ============================================================ */

:root {
  --cream:   #FFFFFF;
  --blue:    #3262AE;
  --blush:   #E9DEE2;
  --lilac:   #C382FC;
  --red:     #EB1C24;
  --ink:     #000000;
  --pink:    #EDA7CB;

  /* English headings/display type */
  --font-display: "widescreen-ex", "Helvetica Neue", Arial, sans-serif;
  /* English body copy + UI labels (buttons, cloud labels, etc.) */
  --font-copy: "itc-avant-garde-gothic-pro", "Helvetica Neue", Arial, sans-serif;
  /* Japanese text, any role — weight 400 only, always set explicitly
     rather than inherited, since nothing else in this family exists */
  --font-jp: "ta-mincho-gf-01", "Hiragino Mincho ProN", "Noto Serif JP", serif;

  --edge: clamp(1.25rem, 4vw, 3.5rem);
  --mag-edge: clamp(2rem, 14vw, 12rem);
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--cream);
  color: var(--ink);
  font-family: var(--font-copy);
  /* base weight — ITC Avant Garde Gothic Pro only ships 300/700, so this
     is the lightest real weight available, not a stylistic pick of
     "normal". Anything that needs to stand out sets 700 explicitly. */
  font-weight: 300;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; }

/* ---------- custom cursor (site-wide, 2026-08-18 — was scoped to just
   .home/index.html; now applies everywhere this stylesheet loads, per
   Chloe). SVG cursors (misc assets/onigiricursordefault.svg +
   onigiricursorbite.svg, copied into site/assets/icons/ with explicit
   width/height added — cursor sizing is unreliable on an SVG that only
   has a viewBox) rather than a rasterized PNG, so it matches whatever
   the real onigiri artwork looks like without needing a rasterizer (none
   available in this environment). Falls back to the normal system
   cursor (auto) on any browser that doesn't support SVG cursor images —
   no visual regression, just loses the custom shape.

   The bite variant swaps in briefly when the user clicks near Sami's
   face in the hero photo (index.html only, obviously — see
   js/main.js's hero-photo click handler, which toggles .is-biting on
   <html>) rather than being a separate cursor rule of its own.

   2026-08-18: doubled to 128x128 and tilted 45deg (both baked into the
   SVGs themselves — see their own comments — since CSS can't rotate a
   cursor image).

   2026-08-18 (later same day): hotspot moved from dead-center (64 64) to
   the tip at the top of the (intact) riceball — (68 39), found by
   rendering the SVG to a canvas and scanning for the first opaque pixel
   row from the top (widens symmetrically from x:63-72 at y:39 down to
   x:37-86 by y:46, confirming a clean single apex, not an artifact of
   antialiasing). Both cursors share this same hotspot rather than each
   using their own top point — the bite variant's top is a ragged
   fragment of the bite-notch itself, not a clean tip, and using it would
   make the cursor visibly jump position on screen when it swaps in. */
body {
  cursor: url("../assets/icons/cursor-riceball.svg") 68 39, auto;
}

html.is-biting body {
  cursor: url("../assets/icons/cursor-riceball-bite.svg") 68 39, auto;
}

a,
button {
  cursor: inherit;
}

/* ---------- clouds (decorative, animated — reused wherever .cloud is placed:
   footer social links, the cloud transition into Packages) ---------- */

.cloud {
  position: absolute;
  top: var(--cloud-top, 20%);
  left: 0;
  width: var(--cloud-size, 2.5rem);
  aspect-ratio: 3 / 2;
  opacity: var(--cloud-opacity, 0.5);
  fill: var(--cloud-color, var(--blush));
  overflow: visible;
  animation: cloud-drift linear infinite;
  animation-duration: var(--cloud-duration, 40s);
  will-change: transform;
}

/* fully off-screen at both ends (sized to each cloud's own width) so the
   infinite loop's reset is invisible — see Website Building Brief's
   looping-animation notes. */
@keyframes cloud-drift {
  from { transform: translateX(calc(-1 * var(--cloud-size, 20rem) - 8vw)); }
  to   { transform: translateX(calc(100vw + var(--cloud-size, 20rem))); }
}

/* ---------- opening splash ----------
   Storyboard from Chloe: clouds gathered off-center on solid blue, spread
   toward the edges as the headline rises center-screen, hold, then the
   clouds keep parting under their own motion until they've fully cleared
   the frame (not a fade — they physically travel past the viewport edges),
   and ONLY once they're gone does the panel's HEIGHT collapse from 100vh
   down to .site-header's own height (measured in JS) — since both use the
   same --blue token, this reads as the header itself rising up into place
   rather than the splash just disappearing. Runs once per session; see the
   skip-script in index.html's <head> and the timeline in js/main.js.

   --cx0/--cy0 (gathered) and --cxout (fully departed — calc()'d off each
   cloud's own --csize/vw so it clears the viewport regardless of screen
   width) are per-cloud instance variables set inline on each <svg
   class="splash-cloud">, so one shared ruleset drives every cloud's whole
   journey without five near-duplicate rules.

   The cloud motion is ONE continuous transition straight from --cx0 to
   --cxout (triggered by .is-parting) — per Chloe, the parting motion must
   never pause partway. The headline's hold/exit is a separate, independent
   timeline (.is-spread / .is-departing, text-only) layered on top of it. */
.opening-splash {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 100vh;
  overflow: hidden;
  background: var(--blue);
  z-index: 200;
  transition: height 1300ms cubic-bezier(0.65, 0, 0.35, 1);
}

html.splash-active,
html.splash-active body {
  overflow: hidden;
}

html.no-splash .opening-splash {
  display: none;
}

.splash-clouds {
  position: absolute;
  inset: 0;
}

.splash-cloud {
  position: absolute;
  left: var(--cx0);
  top: var(--cy0);
  width: var(--csize, 18rem);
  fill: var(--ccolor, var(--cream));
}

/* single unbroken transition, gathered pose all the way to fully off-frame
   — no intermediate resting state, so the parting motion never pauses.
   x-axis only, per Chloe — clouds hold their vertical position and just
   drift apart horizontally, never rise or fall. */
.opening-splash.is-parting .splash-cloud {
  left: var(--cxout);
  transition: left 4500ms cubic-bezier(0.45, 0, 0.55, 1);
}

.splash-cloud-a { --csize: 50rem; aspect-ratio: 2.2 / 1; --ccolor: #E7DBFB; }
.splash-cloud-b { --csize: 62rem; aspect-ratio: 2.1 / 1; --ccolor: #FFF3B0; }
.splash-cloud-c { --csize: 51rem; aspect-ratio: 2.1 / 1; --ccolor: #C9FBF0; }
.splash-cloud-e { --csize: 36rem; aspect-ratio: 2.2 / 1; --ccolor: var(--lilac); }
.splash-cloud-f { --csize: 88rem; aspect-ratio: 2.2 / 1; --ccolor: #C7E8FB; }

/* the outline cloud from frame 3 — stroke only, no fill, matches --red.
   stroke-width scales with --csize (3x sizing here) so the line doesn't
   read as a thin hairline stretched over a much bigger shape. */
.splash-cloud-d {
  --csize: 69rem;
  aspect-ratio: 2.1 / 1;
  fill: none;
  stroke: var(--red);
  stroke-width: 66px;
}

.splash-text {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, calc(-50% + 1.5rem));
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2rem, 6vw, 4rem);
  line-height: 1.05;
  text-align: center;
  color: var(--cream);
  opacity: 0;
  transition: opacity 1500ms ease, transform 1500ms ease;
  transition-delay: 1000ms;
}

.opening-splash.is-spread .splash-text {
  opacity: 1;
  transform: translate(-50%, -50%);
}

.opening-splash.is-departing .splash-text {
  opacity: 0;
  transform: translate(-50%, calc(-50% - 2.5rem));
  transition-delay: 0ms;
  transition-duration: 800ms;
}

/* ---------- header ----------
   2026-08-18 (Chloe): no longer a permanent bar — off-screen by default,
   only slides in once the user scrolls back UP (see js/header.js), as a
   floating overlay rather than something that reserves layout space. Uses
   fixed (not sticky) positioning so it can translate fully off-frame; the
   blurred/darkened background (--ink at reduced opacity + backdrop-filter)
   replaces the old solid --blue fill so it reads correctly over whatever
   content is currently scrolled underneath it. Case study pages opt out
   of all of this — see .cs-chrome .site-header in case-study.css, which
   pins it back to a plain in-flow bar. */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 30;
  /* sized by its own content (padding), not a fixed height — the cloud
     button's padding is generous enough that a fixed height clipped it */
  padding-top: 0.85rem;
  padding-bottom: 0.85rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--edge);
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  transform: translateY(-100%);
  transition: transform 320ms ease;
}

.site-header.is-visible {
  transform: translateY(0);
}

.wordmark {
  text-decoration: none;
  color: var(--cream);
  display: flex;
  align-items: baseline;
  gap: 0.55em;
  /* .wordmark-latin has no rule of its own — inherits font-family
     (--font-copy, via body) and this weight. Small nav-scale label, not
     a display headline, so --font-copy fits; --font-display is reserved
     for the larger hero/footer wordmark treatment. */
  font-weight: 700;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.wordmark-jp {
  font-family: var(--font-jp);
  font-weight: 400;
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  text-transform: none;
}

/* cloud-puff-2's traced outline is lumpy, not a rectangle — its true
   widest point sits at the button's own right edge (confirmed via
   DevTools measurement), but that widest point is a narrow bump rather
   than a broad edge, so by eye the button reads as sitting short of the
   header's right margin even though its box isn't. Nudging it past the
   box edge by a bit makes the cloud's visual mass actually reach the
   margin the way the wordmark does on the left. */
.header-contact {
  margin-right: -4rem;
}

/* ---------- cloud button (reusable component) ----------
   Background is one of the cloud-puff-1/2/3 SVG symbols (defined in the
   inline <svg> defs at the top of the page), not a CSS shape — so it can
   only be recolored via the symbol's fill, controlled here through
   --cloud-btn-bg / --cloud-btn-color. Defaults to a white cloud with
   black text; hover expands the cloud and shifts the text to the
   palette's orange-red. Built for the header Contact button but meant
   to be reused anywhere a "cloud button" fits (e.g. footer links) —
   just repeat the .cloud-button-bg/.cloud-button-label markup pattern
   and swap the <use href>. */

.cloud-button {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  --cloud-btn-bg: var(--cream);
  --cloud-btn-color: var(--ink);
  color: var(--cloud-btn-color);
  /* symmetric top/bottom — the cloud-button-bg SVG fills the full
     border-box (position:absolute; inset:0) ignoring padding, so any
     top/bottom asymmetry here shifts the flex-centered text away from
     the box's true center, which is where the cloud shape's own visual
     bulk sits (confirmed via getBBox: cloud-puff-2's path is vertically
     centered in its viewBox almost exactly, y 308-894 of 1200). */
  padding: 1.9em 4.8em;
  transition: color 0.25s ease;
}

.cloud-button-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  fill: var(--cloud-btn-bg);
  transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), fill 0.25s ease;
}

.cloud-button-label {
  position: relative;
  top: 4px;
  z-index: 1;
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  white-space: nowrap;
}

.cloud-button:hover {
  color: var(--red);
}

.cloud-button:hover .cloud-button-bg {
  transform: scale(1.12);
}

/* Pixel-push: Contact label sat 2px left of dead-center in the cloud
   shape by eye — nudged right to match. */
.header-contact .cloud-button-label {
  transform: translate(2px, 0);
}

/* ---------- hero ----------
   2026-08-18 (Chloe): rebuilt against V2EverythingReference.png — see
   index.html's comment above <section class="hero"> for the full
   rationale. aspect-ratio (not min-height:100svh, the old V1 approach)
   sizes the band, matched to the reference art's own 1936x1088
   proportions — this is what lets .hero-photo's width and this
   aspect-ratio both be expressed as fractions of the same section width
   and stay in the same crop ratio at any viewport size. */
.hero {
  position: relative;
  overflow: hidden;
  aspect-ratio: 1936 / 1088;
  display: flex;
  align-items: center;
  background: var(--cream);
  /* faint blue graph-paper grid, matching the reference's background
     texture — two repeating 1px line gradients rather than an image
     asset, sized in vw so the cell count (not just the cells'
     on-screen size) stays constant across viewport widths */
  background-image:
    linear-gradient(to right, rgba(50, 98, 174, 0.14) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(50, 98, 174, 0.14) 1px, transparent 1px);
  background-size: 5.5vw 5.5vw;
}

/* ---------- hero decorative clouds ----------
   Both purely ornamental (aria-hidden) and z-index:0, i.e. behind both
   the photo and the text — reusing the existing #cloud-puff-3/#cloud-round
   symbols (defined once in index.html's inline <svg><defs>, shared with
   the splash/footer/packages clouds) rather than new traced assets. */
.hero-cloud {
  position: absolute;
  z-index: 0;
  pointer-events: none;
}

.hero-cloud svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* Stroke-only, matching the opening splash's .splash-cloud-d technique
   (same #cloud-puff-3 symbol, fill:none + stroke:var(--red) instead of
   a solid fill) — percentage stroke-width (not a fixed px value like
   the splash uses) so it scales with the hero band instead of staying
   a fixed size against a shrinking viewport. */
.hero-cloud-outline {
  top: -14%;
  right: -8%;
  width: 46%;
  aspect-ratio: 1 / 1;
}

.hero-cloud-outline svg {
  fill: none;
  stroke: var(--red);
  stroke-width: 2.4%;
}

.hero-cloud-solid {
  left: -12%;
  bottom: -20%;
  width: 42%;
  aspect-ratio: 1557.656593 / 1041.059410;
}

.hero-cloud-solid svg {
  fill: var(--blue);
  opacity: 0.16;
}

/* Absolutely positioned overlay (not a flex sibling like the old V1
   photo panel) bled off the top/right, deliberately taller than the
   hero band so its own bottom portion is cropped by .hero's
   overflow:hidden — see the section comment in index.html. No
   object-fit needed: sami-mask.png is a transparent cutout, not a
   rectangular photo, so it's sized by its own natural aspect ratio via
   height:auto rather than force-cropped to fill a box. */
.hero-photo {
  position: absolute;
  z-index: 1;
  top: 0;
  right: 0;
  width: 48%;
  margin: 0;
}

.hero-photo img {
  display: block;
  width: 100%;
  height: auto;
}

.hero-panel {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: flex-start;
  gap: clamp(0.5rem, 1.5vw, 1.25rem);
  max-width: 54%;
  padding: 0 var(--edge);
  color: var(--ink);
}

.hero-jp {
  display: flex;
  flex-direction: column;
  font-family: var(--font-jp);
  font-weight: 400;
  font-size: clamp(1.6rem, 3vw, 2.6rem);
  line-height: 1.15;
  letter-spacing: 0.02em;
}

.hero-copy {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.hero-name {
  margin: 0;
  font-family: var(--font-display);
  /* Widescreen Ex only ships 400/700 — 700 is the boldest real weight
     available, not true black/900 */
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(3.5rem, 9vw, 7rem);
  line-height: 0.92;
  letter-spacing: -0.01em;
}

.hero-name span { display: block; }

.hero-role {
  margin: 0;
  /* English body-scale text — --font-copy (ITC Avant Garde Gothic Pro),
     not --font-display (that's the display/heading face now). Uppercase
     + letter-spacing (was title-case, tighter, on the old V1 red panel)
     to match the reference's "CONTENT PRODUCER" treatment. */
  font-family: var(--font-copy);
  font-weight: 300;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: clamp(1.8rem, 4vw, 2.75rem);
  line-height: 1.15;
}

.hero-sub {
  margin: 0;
  max-width: 24rem;
  font-family: var(--font-copy);
  /* was 700 (bold) on the old cream-on-red V1 panel — the reference's
     tagline reads as light-weight, muted body copy instead */
  font-weight: 300;
  font-size: 0.95rem;
  line-height: 1.5;
  opacity: 0.65;
}

/* ---------- social media ---------- */

.social-media {
  background: var(--cream);
  padding: 7rem var(--edge);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

/* 3 fixed grid columns — video / type / video — so the headline+blurb
   can never overlap a clip (the old scatter needed z-index layering to
   stay legible over images; a real column each rules that out
   entirely). Sized by content, not a fixed section min-height, since 3
   stacked 9:16 clips per side already run tall. */
.social-columns {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(20rem, 32rem) minmax(0, 1fr);
  align-items: center;
  gap: 3rem;
  max-width: 100rem;
  width: 100%;
  margin: 0 auto;
}

.social-col-media {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.5rem;
}

.social-col-media:first-child { justify-self: end; }
.social-col-media:last-child { justify-self: start; }

.social-media-title {
  margin: 0;
  font-family: var(--font-display);
  /* Widescreen Ex tops out at 700 — no true Black/900 in the kit (see
     Chloe's 2026-08-13 call) */
  font-weight: 700;
  text-transform: uppercase;
  /* sized up well past the old 3.25rem cap for a more editorial, headline-
     scale feel — the 3-column grid (not the old absolute scatter) means
     there's no risk of this growing into an image regardless of size. */
  font-size: clamp(2.75rem, 6.5vw, 5.5rem);
  line-height: 1.02;
  letter-spacing: -0.01em;
  color: var(--ink);
}

.social-media-blurb {
  margin: 1.5rem auto 0;
  font-size: 0.9rem;
  /* --font-copy (ITC Avant Garde Gothic Pro) only ships 300/700 — no
     Medium/500. 300 matches the light weight used site-wide for body
     copy, per Chloe's 2026-08-13 call. */
  font-family: var(--font-copy);
  font-weight: 300;
  line-height: 1.65;
  color: var(--ink);
  opacity: 0.7;
}

/* Deliberately NOT the site's cloud-button component — a plain outlined
   pill instead, so this section has one quieter, text-forward CTA rather
   than another cloud shape competing with the scatter of photos around
   it. Inverts to a solid fill on hover; the arrow nudges right
   independently to read as "go" rather than just a color change. Black
   on the white background (not the white/--cream it used to be on
   --lilac) so it still pops — see hover below, inverted to match. */
.social-media-cta {
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  margin-top: 2rem;
  padding: 0.85em 1.75em;
  border: 1.5px solid var(--ink);
  border-radius: 999px;
  color: var(--ink);
  text-decoration: none;
  font-family: var(--font-display);
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  transition: background-color 0.25s ease, color 0.25s ease;
}

.social-media-cta-arrow {
  transition: transform 0.25s ease;
}

.social-media-cta:hover {
  background-color: var(--ink);
  color: var(--cream);
}

.social-media-cta:hover .social-media-cta-arrow {
  transform: translateX(3px);
}

/* Scroll-scrubbed reveal — each clip/the text block fades and settles
   into place as .social-media scrolls into view, driven directly by
   scroll position every frame via main.js setting --p (0-1) per element
   (deliberately no CSS transition on the driven properties, which would
   lag behind and fight the scrubbing). Each element's own --stagger
   (0-1, set inline) delays when it starts relative to the section's
   overall progress, so the 6 clips + text don't move in lockstep. */
.social-col-item {
  display: block;
  width: clamp(8rem, 11vw, 13rem);
  aspect-ratio: 9 / 16;
  border-radius: 0.85rem;
  overflow: hidden;
  box-shadow: 0 20px 45px rgba(0, 0, 0, 0.35);
  opacity: var(--p, 0);
  transform:
    translateY(calc((1 - var(--p, 0)) * 2.5rem))
    scale(calc(0.85 + var(--p, 0) * 0.15));
}

.social-col-item video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.social-col-text {
  max-width: 26rem;
  margin: 0 auto;
  opacity: var(--p, 0);
  transform: translateY(calc((1 - var(--p, 0)) * 1.5rem));
}

@media (max-width: 900px) {
  .social-columns {
    grid-template-columns: 1fr;
    gap: 2.5rem;
    max-width: 34rem;
  }
  .social-col-text { order: -1; }
  .social-col-media {
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    justify-self: center !important;
  }
  .social-col-item { width: clamp(6rem, 24vw, 9rem); }
}

/* prefers-reduced-motion handling lives in main.js — it sets --p to 1 on
   every element once, immediately, instead of scroll-linking it, so
   everything is shown fully settled and static rather than hidden
   (they're real links/copy, not pure decoration) */

.social-tiles-section {
  background: var(--cream);
  padding: 4rem var(--edge) 5rem;
  /* faint graph-paper texture, matching the reference mockup's background
     behind the tiles */
  background-image:
    linear-gradient(to right, rgba(0,0,0,0.06) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(0,0,0,0.06) 1px, transparent 1px);
  background-size: 48px 48px;
}

.social-tiles-title {
  position: relative;
  z-index: 1;
  margin: 0 0 3rem;
  text-align: center;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.6rem, 4vw, 2.75rem);
  line-height: 1.15;
  color: var(--ink);
}

/* Wraps the tile row plus its emoji layer so the emoji have something to
   anchor to (see .emoji-bubbles) without affecting the graph-paper
   background or title above. */
.social-tiles-stage {
  position: relative;
}

.social-tiles {
  max-width: 56rem;
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 2rem;
}

/* Reaction emoji, continuously spawned in main.js from random points along
   the baseline just under the tiles, rising and fading as they go — a
   fixed-height strip anchored to the stage's bottom edge (rather than
   the stage's full, very tall portrait-tile height) so every bubble
   finishes fading well inside the clipped area instead of getting cut
   off hard at the top. */
.emoji-bubbles {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 34rem;
  max-height: 100%;
  overflow: hidden;
  pointer-events: none;
}

.emoji-bubble {
  position: absolute;
  bottom: 0;
  line-height: 1;
  animation-name: emoji-rise;
  animation-timing-function: ease-out;
  animation-fill-mode: forwards;
}

@keyframes emoji-rise {
  0%   { transform: translate(0, 0); opacity: 0; }
  12%  { opacity: 1; }
  70%  { opacity: 1; }
  100% { transform: translate(var(--drift, 0px), calc(-1 * var(--rise, 20rem))); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .emoji-bubbles { display: none; }
}

/* Kara-world-style hover tile (karocrafts.com "Join Kara World") — the
   frames crossfade via JS while hovered (see main.js), reset to frame 1
   on mouseleave. Two tiles, one per content type (Photos / Reels), both
   link to social-grid.html (a placeholder page — see that file's own
   comment for why). */
.social-tile {
  position: relative;
  display: block;
  /* sized + shaped to read more like an actual phone screen (was 19rem
     wide at a 4:5 Instagram-post ratio) */
  width: min(46%, 24rem);
  aspect-ratio: 9 / 17.5;
  border-radius: 0.5rem;
  overflow: hidden;
  text-decoration: none;
  color: var(--cream);
  box-shadow: 0 20px 40px rgba(0,0,0,0.2);
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.social-tile:hover {
  transform: translateY(-6px);
  box-shadow: 0 28px 48px rgba(0,0,0,0.28);
}

.social-tile-frames {
  position: absolute;
  inset: 0;
  display: block;
}

.social-tile-frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.5s ease;
}

.social-tile-frame.is-active { opacity: 1; }

.social-tile-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.social-tile-caption {
  position: absolute;
  z-index: 2;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 1rem 1.1rem 1.1rem;
  /* solid near the very bottom (not just a soft fade) — the Reels tile's
     source footage has its own baked-in captions that can land anywhere
     in the lower third, so this needs to fully mask that band rather
     than just darken it, or the two caption styles collide/overlap */
  background: linear-gradient(to top, rgba(0,0,0,0.92) 45%, rgba(0,0,0,0) 100%);
}

.social-tile-label {
  display: block;
  font-weight: 700;
  font-size: 1rem;
  margin-bottom: 0.3rem;
}

.social-tile-tags {
  display: block;
  font-size: 0.7rem;
  opacity: 0.85;
}

/* ---------- value prop ----------
   Replaces .brand-partnerships on the live page as of 2026-08-13 (V2
   redesign) — see index.html's comment above <section class="value-prop">
   for the full story. The .brand-partnerships, .partner-*, and
   .riceball-* rules below this block are NOT dead code: archive-brand-
   partnerships.html still uses them, kept intentionally for the future
   case-study browsing page. */

.value-prop {
  position: relative;
  background: var(--ink);
  color: var(--cream);
  /* generous min-height so the absolutely-positioned photo collage has
     room to scatter without the section collapsing to just its text
     content's height */
  min-height: clamp(48rem, 85vw, 64rem);
  padding: clamp(3rem, 8vw, 6rem) var(--edge);
  overflow: hidden;
}

.value-prop-headline {
  /* full-bleed breakout (same technique as .brand-partnerships-title/
     .partner-stage elsewhere) so it spans the true viewport edge to
     edge, ignoring .value-prop's own side padding. z-index above
     .vp-photo (below) so the type reads on top of every photo, not
     tucked behind them. */
  position: relative;
  z-index: 2;
  width: 100vw;
  left: 50%;
  margin-left: -50vw;
  /* base Widescreen, NOT the Ex (expanded) cut used for --font-display
     site-wide — Chloe's specific call for this headline, 2026-08-13 */
  font-family: "widescreen", "Helvetica Neue", Arial, sans-serif;
  /* Widescreen tops out at 700 — no true Black/900 in the kit */
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(3.5rem, 9vw, 8rem);
  line-height: 1.05;
  letter-spacing: -0.03em;
}

/* text-align-last:justify on a single-line paragraph stretches that one
   line's words edge-to-edge via word-spacing — the same trick used for
   multi-line justified text, just forced onto a single "last" line by
   white-space:nowrap. Matches the reference's spread-out headline look
   without hardcoding per-line letter-spacing values.

   --vp-scale (set by js/main.js's value-prop fit routine) shrinks a line's
   font-size below the inherited clamp() size only when its own rendered
   width would otherwise overflow the viewport edge at the current width —
   confirmed this actually happens mid-range (nowrap + justify only grows
   spacing to fill the line, it never shrinks it back down on its own).
   Measuring the real rendered width in JS instead of hand-tuning the
   clamp()/letter-spacing numbers matters here specifically because the
   real Widescreen webfont doesn't render on localhost (Adobe Fonts is
   domain-locked) — any fixed numbers tuned against the fallback stack
   wouldn't necessarily hold once the real font loads on the deployed
   site. Defaults to 1 (no-op) via the fallback in the var(). */
.value-prop-line {
  margin: 0;
  text-align: justify;
  text-align-last: justify;
  white-space: nowrap;
  font-size: calc(var(--vp-scale, 1) * 1em);
}

/* "Presence" in the reference is NOT stretched full-width like the two
   lines above it — stays left-aligned and normal-spaced, so this one
   opts back out of the justify trick and allows normal wrapping if it
   ever needs to on a narrow screen. */
.value-prop-line-last {
  text-align: left;
  text-align-last: left;
  white-space: normal;
}

/* Decorative scattered collage, reusing existing case-study photos
   (not new assets) — a close approximation of the reference's layout,
   not a pixel match (per Chloe's call). Absolutely positioned so they
   layer over/around the headline text the way the reference's collage
   does, each with a slight rotation for an unpolished, collaged feel.
   z-index below both the headline and the copy block (see above/below)
   so the type always reads on top, per Chloe's 2026-08-13 call. */
.value-prop-photos {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.vp-photo {
  position: absolute;
  z-index: 1;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.45);
}

/* Wider size range than the first pass (was ~7-21rem, all fairly close
   together) — two much smaller accent shots and two much larger
   hero-scale ones for real contrast, per Chloe's "vary greater" call. */
.vp-photo-1 { top: 1%; left: 7%; width: clamp(9rem, 15vw, 14rem); transform: rotate(-2deg); }
.vp-photo-2 { top: 5%; left: 25%; width: clamp(6rem, 9vw, 8rem); transform: rotate(3deg); }
.vp-photo-3 { top: -1%; right: 3%; width: clamp(16rem, 26vw, 25rem); transform: rotate(1deg); }
.vp-photo-4 { top: 40%; left: 12%; width: clamp(8rem, 12vw, 11rem); transform: rotate(-1deg); }
.vp-photo-5 { top: 62%; left: 1%; width: clamp(11rem, 18vw, 17rem); transform: rotate(2deg); }
.vp-photo-6 { top: 44%; right: 5%; width: clamp(15rem, 24vw, 23rem); transform: rotate(-2deg); }

.value-prop-copy {
  position: relative;
  /* above .vp-photo (z-index:1), same as the headline — all type reads
     on top of the collage, per Chloe's 2026-08-13 call */
  z-index: 2;
  max-width: 30rem;
  margin: 5rem auto 0;
  text-align: center;
}

.value-prop-blurb {
  margin: 0;
  font-family: var(--font-copy);
  font-weight: 300;
  font-size: 1.15rem;
  line-height: 1.65;
  color: var(--cream);
  opacity: 0.8;
}

/* Pink border/text (was red) — Chloe's 2026-08-18 call. */
.value-prop-cta {
  display: inline-flex;
  align-items: center;
  margin-top: 2rem;
  padding: 0.85em 1.75em;
  border: 1.5px solid var(--pink);
  border-radius: 999px;
  color: var(--pink);
  text-decoration: none;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  transition: background-color 0.25s ease, color 0.25s ease;
}

.value-prop-cta:hover {
  background-color: var(--pink);
  color: var(--ink);
}

/* ---------- brand partnerships ----------
   Unused on the live page as of 2026-08-13 (see .value-prop above) —
   kept for archive-brand-partnerships.html, not dead code. */

.brand-partnerships {
  background: var(--cream);
  /* no bottom padding — .partner-transition is the last child and
     already fades all the way to --blue, flush with .packages right
     below it. A bottom padding here would leave a strip of this
     section's own cream/grid background showing between that gradient
     and the Packages section, breaking the blue from reading as
     continuous. */
  padding: 5rem var(--edge) 0;
  text-align: center;
  /* the riceballs sit off-screen (±130vw, see main.js) from page load
     until they roll in — without this, that translate genuinely widens
     the page's own scrollable area (confirmed via document.body.
     scrollWidth), not just a visual overflow, on every device, not only
     narrow ones. .partner-stage/.partner-transition's full-bleed
     100vw/-50vw breakout below already lands exactly flush with this
     section's own edges, so clipping here doesn't cut into them. */
  overflow: hidden;
  /* same graph-paper texture as .social-tiles-section just above it, so
     the grid reads as continuous behind the riceballs/title instead of
     stopping at the tiles section's edge. Only the top of this section
     (riceball + title) actually shows it — .partner-stage and
     .partner-transition below are full-bleed and opaque, so they cover
     the rest of this section's background regardless. */
  background-image:
    linear-gradient(to right, rgba(0,0,0,0.06) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(0,0,0,0.06) 1px, transparent 1px);
  background-size: 48px 48px;
}

/* Default state (no JS, or before scroll-trigger fires) is already the
   settled resting position — the animation just adds a dramatic entrance
   on top. That way a JS failure degrades to "icons sitting there", not
   "icons permanently off-screen". */
.riceball-collision {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 4.5rem;
  margin-bottom: 1rem;
}

.riceball {
  position: relative;
  width: 3.5rem;
  height: auto;
}

/* Scroll-scrubbed roll-in + bonk-stop + arch-out — replaces the old
   fixed-duration CSS keyframe entrance. --riceball-*-x/-y/-rot (set on
   .brand-partnerships by main.js every scroll frame, inherited down to
   these) are driven by scroll progress in two phases:
     1. Off-screen → center: slides in AND spins (rotate), reaching a
        clean 0deg exactly as it reaches center (x:0, the two icons'
        untransformed flex position, where they're already touching) —
        that's the bonk, and the spin stops dead right there.
     2. Center → resting: no more rotation; hops back out to the settled
        apart position (-1.4rem/1.4rem) along a small parabolic arch
        (translateY dips negative at the midpoint, back to 0 on landing).
   No CSS transition on the transform itself, which would lag behind and
   fight the scrubbing. The var(..., rest/0/0deg) fallbacks mean unset
   custom properties (JS never ran, or prefers-reduced-motion skipped it)
   render the settled resting position directly, same "degrade to icons
   sitting there" philosophy as before. */
.riceball-left {
  transform:
    translate(var(--riceball-left-x, -1.4rem), var(--riceball-left-y, 0px))
    rotate(var(--riceball-left-rot, 0deg));
}

.riceball-right {
  transform:
    translate(var(--riceball-right-x, 1.4rem), var(--riceball-right-y, 0px))
    rotate(var(--riceball-right-rot, 0deg));
}

/* Wafts up from the collision point once the bonk happens — main.js
   drives --heart-x/-y/-opacity off how far you've scrolled PAST the
   bonk (not the riceballs' own 0-1 progress, which is already spent by
   then), so it keeps rising/swaying/fading over further scrolling
   rather than finishing instantly. Opacity follows sin(pi * localT), so
   it fades in, peaks, and fades out again by the time it's fully risen
   — never a hard cut. var(--heart-opacity, 0) fallback keeps it hidden
   under prefers-reduced-motion/no-JS, same as the riceballs. */
.riceball-heart {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 1.5rem;
  height: auto;
  fill: var(--red);
  pointer-events: none;
  opacity: var(--heart-opacity, 0);
  transform:
    translate(-50%, -50%)
    translate(var(--heart-x, 0px), var(--heart-y, 0px));
}

.brand-partnerships-title {
  /* full-bleed breakout (same technique as .partner-stage right below
     it) so the font-size-as-vw sizing below actually spans the true
     viewport width, not just .brand-partnerships's own padded content
     box. z-index (kept for any future glyph/margin tweak that overlaps
     it into .partner-stage again) plus margin-bottom:0 puts the title's
     own bottom edge exactly flush with .partner-stage's top — no gap,
     no overlap. */
  position: relative;
  z-index: 2;
  width: 100vw;
  left: 50%;
  margin: 0.5rem 0 0 -50vw;
  /* black band (var(--ink), same as .partner-info's panel below) behind
     white text — only this title gets the black background, not the
     riceball area above it, which stays on the section's cream/grid
     backdrop per Chloe's call. padding-top only, so the bottom edge
     stays flush with .partner-stage (no padding-bottom, preserves the
     earlier "flush, not overlapping" alignment). */
  background: var(--ink);
  padding-top: 0.4em;
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  /* vw-only, deliberately uncapped by clamp()'s usual max — the point
     here is to always span the full viewport width on one line, not to
     stay within a tasteful ceiling like the rest of the page's display
     type. Tuned against Montserrat's rendered width for "BRAND
     PARTNERSHIPS" (was overflowing past the viewport edge at 10.6vw) —
     NOT re-verified against Widescreen Ex, which may render wider or
     narrower per character; re-check for overflow/nowrap once the font
     is actually loadable (domain-locked, doesn't render on localhost). */
  font-size: 8vw;
  line-height: 0.85;
  letter-spacing: -0.01em;
  white-space: nowrap;
  text-align: center;
  color: var(--cream);
}

.partner-stage {
  position: relative;
  display: flex;
  /* full-bleed: breaks out of .brand-partnerships's own side padding
     regardless of that padding's value, and regardless of a vertical
     scrollbar (the classic 100vw pitfall) — centers on the viewport via
     left:50% then shifts back by exactly half the true viewport width */
  width: 100vw;
  left: 50%;
  margin-left: -50vw;
  /* tall enough that the photos read as portrait crops, not a short
     landscape strip */
  height: clamp(32rem, 88vh, 56rem);
  overflow: hidden;
}

.partner-photos {
  position: relative;
  flex: 1 1 auto;
  display: none;
}

.partner-photos.is-active { display: block; }

.partner-photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.6s ease;
}

.partner-photo.is-active { opacity: 1; }

/* Dot preview — how many photos this project has, and which one is
   currently showing during the auto-rotation (see main.js's
   startPhotoRotation/selectProject, which keep these in lockstep with
   .partner-photo's own .is-active). Lives inside .partner-photos itself
   (already position:relative, already display:none unless .is-active)
   rather than one shared element in .partner-stage, so it's naturally
   scoped to the currently-visible project's photo count without any
   extra JS bookkeeping. */
.partner-dots {
  position: absolute;
  left: 50%;
  bottom: 1.5rem;
  z-index: 1;
  transform: translateX(-50%);
  display: flex;
  gap: 0.5rem;
}

.partner-dot {
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.45);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
  transition: background 0.25s ease, transform 0.25s ease;
}

.partner-dot.is-active {
  background: var(--cream);
  transform: scale(1.35);
}

.partner-info {
  position: relative;
  flex: 0 0 min(38%, 28rem);
  background: var(--ink);
  color: var(--cream);
  display: flex;
  align-items: flex-end;
  padding: 2.5rem;
  text-align: left;
}

.partner-copy { display: none; }
.partner-copy.is-active { display: block; }

.partner-eyebrow {
  display: block;
  font-size: 0.8rem;
  opacity: 0.7;
  margin-bottom: 0.4rem;
}

.partner-copy h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.4rem, 6vw, 4.5rem);
  line-height: 0.98;
  margin: 0 0 0.6rem;
}

.partner-copy p {
  font-size: 0.85rem;
  line-height: 1.5;
  opacity: 0.8;
  margin: 0;
}

/* Japanese flavor text, one short line per project — decorative only, not
   a translation of .partner-copy. Positioned to straddle the seam between
   the photo and the black .partner-info column: left is tuned so the
   string's own rendered width (nowrap) starts over the photo and carries
   into the black panel at typical stage widths (verified via
   getBoundingClientRect against both .partner-photos and .partner-info's
   own rects, not just eyeballed). Sits in its own absolutely-positioned
   layer over the whole stage rather than inside either child, so it can
   paint above both regardless of which one it's currently crossing. */
.partner-flavor-layer {
  /* hidden site-wide per Chloe's call (2026-08-03) — markup/JS toggling
     (main.js's selectProject()) left intact in case it comes back. */
  display: none;
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
}

.partner-flavor {
  display: none;
  position: absolute;
  /* anchored from the right using .partner-info's own flex-basis formula
     (min(38%, 28rem)), not a guessed left percentage — guarantees the
     text's right edge always lands a fixed 3rem INSIDE the black panel
     regardless of viewport width, so its left side (extending further
     left by its own rendered width) reliably falls over the photo. */
  right: calc(min(38%, 28rem) - 3rem);
  bottom: 3rem;
  font-family: var(--font-jp);
  font-weight: 400;
  font-size: clamp(1.8rem, 4.2vw, 3.25rem);
  letter-spacing: 0.04em;
  color: var(--cream);
  text-shadow: 0 4px 18px rgba(0, 0, 0, 0.6);
  white-space: nowrap;
}

.partner-flavor.is-active { display: block; }

/* Manual prev/next controls for the photo carousel — sit within the photo
   area only, not spanning the black .partner-info column beside it (same
   min(38%,28rem) flex-basis formula used elsewhere anchors the right
   button to that column's own edge). z-index above the flavor layer (3)
   and the (position:relative but z-index:auto) photos/info, so they're
   always clickable regardless of which project/photo is active. */
.partner-nav {
  position: absolute;
  top: 50%;
  z-index: 4;
  transform: translateY(-50%);
  width: 2.75rem;
  height: 2.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.35);
  color: var(--cream);
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.partner-nav:hover { background-color: rgba(0, 0, 0, 0.65); }

.partner-nav svg {
  width: 1.1rem;
  height: 1.1rem;
}

.partner-nav-prev { left: 1.5rem; }
.partner-nav-next { right: calc(min(38%, 28rem) + 1.5rem); }

/* Gradient bridge below the full-bleed stage — black (echoing
   .partner-info just above) fading down to --blue (the Packages block's
   color, once that section is placed below this one), so the two flow
   together instead of cutting sharply from dark to cream. Also
   full-bleed, same breakout technique as .partner-stage. */
.partner-transition {
  width: 100vw;
  left: 50%;
  margin-left: -50vw;
  position: relative;
  background: linear-gradient(to bottom, var(--ink) 0%, var(--blue) 100%);
  /* no horizontal padding — .partner-thumbs needs to reach the true
     screen edges below */
  padding: 2.5rem 0 3rem;
}

/* Full width control strip — each thumbnail is an equal 1/4 share of
   the whole viewport width, not a small centered cluster. */
.partner-thumbs {
  display: flex;
  gap: 0.4rem;
  width: 100%;
}

.partner-thumb {
  flex: 1 1 0;
  width: auto;
  aspect-ratio: 16 / 10;
  border-radius: 0;
  overflow: hidden;
  border: 3px solid transparent;
  padding: 0;
  background: none;
  cursor: pointer;
  opacity: 0.55;
  transition: opacity 0.2s ease, border-color 0.2s ease;
}

.partner-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.partner-thumb:hover { opacity: 0.8; }

/* --cream instead of --blue for the active ring — this row sits on the
   black-to-blue gradient now, where a blue ring would lose contrast
   against the gradient's own blue end */
.partner-thumb.is-active {
  opacity: 1;
  border-color: var(--cream);
}

/* ---------- packages ---------- */

.packages {
  position: relative;
  background: var(--blue);
  color: var(--cream);
  padding: 4.5rem var(--edge) 5.5rem;
  overflow: hidden;
}

/* Single traced-photo cloud cluster (misc assets/"Cloud Group.png"), not
   the scattered inline SVG puffs used elsewhere — sits in normal flow as
   a banner between the tagline and the title/cards, not layered behind
   them, so nothing needs to fight it for legibility. Full-bleed (same
   100vw/left:50%/margin-left:-50vw breakout as .partner-stage/
   .partner-transition) so it runs edge-to-edge with no side margins,
   ignoring .packages's own --edge padding. */
.packages-clouds {
  width: 100vw;
  position: relative;
  left: 50%;
  margin-left: -50vw;
  margin-bottom: 2.5rem;
}

.packages-clouds img {
  display: block;
  width: 100%;
  height: auto;
}

.packages-tagline {
  position: relative;
  z-index: 1;
  text-align: center;
  max-width: 68rem;
  margin: 0 auto 2rem;
  font-size: clamp(1.4rem, 2.8vw, 2rem);
}

/* 800 isn't available in --font-copy (only 300/700) */
.packages-tagline strong { font-weight: 700; }

.packages-inner {
  position: relative;
  z-index: 1;
  max-width: 68rem;
  margin: 0 auto;
}

.packages-title {
  margin: 0 0 1.5rem;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.5rem, 6vw, 4rem);
}

.packages-note {
  max-width: 34rem;
  font-size: 0.95rem;
  line-height: 1.65;
  opacity: 0.9;
  margin: 0 0 3rem;
}

/* V2 redesign (2026-08-13, revised 2026-08-17): 3 irregular cloud-shaped
   PNGs (Chloe's Clouds for Packages/Vector*.png) replace the rectangular
   ribbon cards entirely — see index.html's comment above
   .package-clouds-stack. Stacked one per row, each touching exactly ONE
   viewport edge (alternating right/left/right) — NOT both, per Chloe's
   2026-08-17 correction to the earlier full-bleed-both-sides pass.
   .package-cloud is a 100vw-wide flex "anchor" (same proven left:50%/
   -50vw breakout as .value-prop-headline, math verified via
   getBoundingClientRect to land exactly at the true viewport edges
   regardless of .packages-inner's centered max-width) — it's invisible
   itself, existing only so .package-cloud-frame (the actually-sized,
   actually-visible cloud) can be pushed flush against ONE of its edges
   with justify-content, something a single element can't do while also
   being centered inside a narrower, centered ancestor. */
.package-clouds-stack {
  display: flex;
  flex-direction: column;
  gap: clamp(2rem, 7vw, 5rem);
  margin-top: 1rem;
}

.package-cloud {
  position: relative;
  width: 100vw;
  left: 50%;
  margin-left: -50vw;
  display: flex;
}

.package-cloud-1,
.package-cloud-3 { justify-content: flex-end; }
.package-cloud-2 { justify-content: flex-start; }

.package-cloud-frame {
  position: relative;
  width: clamp(58rem, 115vw, 86rem);
}

/* Each frame's aspect-ratio matches its source PNG exactly (1899x1122,
   1809x1701, 2595x1548) so .package-cloud-bg fills it with no
   letterboxing or distortion — see the file dimensions noted alongside
   each. Golden Hour's frame is narrower since its PNG is nearly square,
   so it doesn't read as huge next to the two wider clouds. */
.package-cloud-1 .package-cloud-frame { aspect-ratio: 1899 / 1122; }
.package-cloud-2 .package-cloud-frame { aspect-ratio: 1809 / 1701; width: clamp(47rem, 92vw, 68rem); }
.package-cloud-3 .package-cloud-frame { aspect-ratio: 2595 / 1548; }

.package-cloud-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
}

/* Positioned independently of .package-cloud-frame on purpose (sibling,
   not child) — per Chloe's 2026-08-17 call to grow the clouds for more
   framing WITHOUT dragging the text along with them. left/right/width
   are percentages of .package-cloud (the always-100vw anchor, not the
   frame), so they scale with viewport width but stay put regardless of
   how big .package-cloud-frame gets. top is a fixed rem measured from
   the frame's own top edge (which doesn't move when width/height grow)
   at the values this text already sat at before this change — so this
   redesign step itself causes zero visible shift, only more cloud
   canvas appears around the now-fixed text block. Values were captured
   via getBoundingClientRect at 1600px viewport, then left/right/width
   converted to %(of 1600px) so they hold up at other widths too. Below
   860px the frame flex-shrinks to fill the viewport (see .package-cloud-
   frame/.package-cloud comments), so percentages-of-frame make more
   sense there — see the mobile override in the responsive section. */
.package-cloud-copy {
  position: absolute;
  z-index: 1;
  color: var(--ink);
}

/* 2026-08-19 (Chloe): nudged down from the original 14rem/18.25rem so
   the text sits more centrally in each cloud's irregular silhouette —
   first pass by 50px (3.125rem), then another 50px on top of that per
   her follow-up, landing at 20.25rem/24.5rem. Desktop only — the mobile
   override below already vertically centers via flexbox
   (justify-content:center), so it has no fixed top to drift off-center
   in the first place. */
.package-cloud-1 .package-cloud-copy,
.package-cloud-3 .package-cloud-copy { top: 20.25rem; left: 39.25%; width: 36%; }
.package-cloud-2 .package-cloud-copy { top: 24.5rem; right: 53%; width: 28%; }

/* Fluid (vw-scaled) rather than fixed rem — .package-cloud-frame's
   height is locked to its own aspect-ratio, so at narrow viewports
   (where the frame flex-shrinks to fill the screen, see the mobile
   override below) short/wide clouds like Silver Lining and Cloud Nine
   get quite short. Fixed-size text didn't shrink to match and badly
   overflowed above/below the cloud at ~320-360px — confirmed via
   screenshot, not just measurement — so these scale down with vw below
   their normal desktop size instead of staying fixed. Caps out at
   exactly the old fixed values at typical desktop widths. */
.package-tier {
  display: block;
  font-size: clamp(0.6rem, 2.3vw, 0.95rem);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  opacity: 0.55;
}

.package-cloud-copy h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.1rem, 5.2vw, 2.1rem);
  margin: 0.3rem 0 0.5rem;
  line-height: 1.1;
}

.package-cloud-copy p {
  font-size: clamp(0.72rem, 2.9vw, 1.15rem);
  line-height: 1.35;
  opacity: 0.75;
  margin: 0;
}

/* ---------- footer ---------- */

.site-footer {
  /* Pink per Chloe's 2026-08-18 call (was --red) — applies site-wide,
     every page using .site-footer picks this up automatically. */
  background: var(--pink);
  color: var(--ink);
  overflow: hidden;
}

/* Drift lane for the social-link clouds — same infinite cloud-drift
   keyframes used elsewhere (see .cloud, top of this file), just applied
   to a linked, text-bearing cloud instead of a plain decorative shape.
   Generous height so four clouds at different --cloud-top values don't
   collide, and overflow stays hidden on the parent .site-footer so the
   off-screen start/end of each loop is never visible. */
.footer-clouds {
  position: relative;
  height: 26rem;
}

.footer-cloud {
  position: absolute;
  top: var(--cloud-top, 20%);
  left: 0;
  width: var(--cloud-size, 8rem);
  aspect-ratio: 2.2 / 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  color: var(--ink);
  animation: cloud-drift linear infinite;
  animation-duration: var(--cloud-duration, 40s);
  animation-delay: var(--cloud-delay, 0s);
  will-change: transform;
}

.footer-cloud-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  fill: var(--cream);
  transition: fill 0.2s ease;
}

.footer-cloud-label {
  position: relative;
  z-index: 1;
  font-family: var(--font-copy);
  font-weight: 700;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
  transition: color 0.2s ease;
}

/* hover goes to --ink, not --red — the footer background is --red now,
   so a red hover fill would just vanish into it */
.footer-cloud:hover .footer-cloud-bg { fill: var(--ink); }
.footer-cloud:hover .footer-cloud-label { color: var(--cream); }

/* Pixel-push: per-label nudges inside each footer cloud shape. */
.footer-cloud-youtube .footer-cloud-label { transform: translate(1px, 2px); }
.footer-cloud-instagram .footer-cloud-label { transform: translate(4px, 8px); }
.footer-cloud-tiktok .footer-cloud-label { transform: translate(4px, 6px); }
.footer-cloud-pottery .footer-cloud-label { transform: translate(4px, 6px); }

/* Black bar, white text — deliberately its own solid field rather than
   the --red the rest of .site-footer sits on, so the credit line reads
   as a distinct closing strip rather than blending into the cloud lane
   above. No border needed now; the red-to-black background change
   already draws the line between the two. */
.footer-bottom {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
  padding: 1.75rem var(--edge) 2.5rem;
  background: var(--ink);
  color: var(--cream);
}

.footer-wordmark {
  display: flex;
  align-items: baseline;
  gap: 0.6em;
  text-decoration: none;
  color: var(--cream);
}

.footer-wordmark .wordmark-jp {
  font-size: 1.6rem;
}

.footer-wordmark-latin {
  font-family: var(--font-display);
  /* Widescreen Ex tops out at 700 (no true 900/black) */
  font-weight: 700;
  font-size: 1.6rem;
  letter-spacing: -0.01em;
}

/* Was deliberately set in the OLD --font-display (DotGothic16, the
   Japanese font under the V1 naming) as a one-off stylistic exception —
   that doesn't carry over cleanly now that fonts split by role instead
   of language, so this normalizes to plain body copy like any other
   small English text. */
.footer-credit {
  font-family: var(--font-copy);
  font-weight: 300;
  font-size: 1.1rem;
  color: var(--cream);
}

/* ---------- responsive ---------- */

@media (max-width: 860px) {
  /* Text above, photo below, in normal flow — the desktop treatment
     (fixed aspect-ratio band + photo bled off as an absolute overlay)
     assumes room for a side-by-side layout that isn't there once
     .hero-panel's max-width:54% would otherwise squeeze both the
     headline and the photo into an uncomfortably narrow column. */
  .hero {
    aspect-ratio: auto;
    flex-direction: column;
    align-items: stretch;
    padding: clamp(2rem, 8vw, 3rem) 0;
  }
  .hero-panel { max-width: none; order: 1; }
  .hero-photo {
    position: static;
    order: 2;
    width: min(70%, 22rem);
    margin: 1.5rem auto 0;
  }
  .social-tiles { gap: 1.25rem; }
  .social-tile { width: min(46%, 14rem); }

  /* .package-cloud-copy's desktop positioning (fixed rem top + %-of-
     anchor left/right/width) assumes a frame that's meaningfully
     narrower than the viewport. Below this breakpoint the frame
     flex-shrinks to fill the full 100vw anchor (same width as it), so
     the fixed rem top would land close to or past the now-much-shorter
     frame's own bottom edge. Switch back to positioning relative to the
     frame itself (which now equals the anchor's bounds) via inset:0 +
     flex-centering + percentage padding, same approach as the pre-
     2026-08-17 version. */
  .package-cloud-copy {
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    width: auto;
    height: auto;
    display: flex;
    flex-direction: column;
    justify-content: center;
    /* top/bottom cut down from 12%/12% — Silver Lining/Cloud Nine's
       wide aspect-ratio means a short frame at narrow viewports, and
       12% of an already-short height left too little vertical room
       (confirmed via screenshot: text spilled above/below the cloud at
       320-360px even after the fluid font-size fix above). */
    padding: 3% 18% 3% 14%;
  }
  /* Matches the desktop per-cloud selectors' specificity (0,2,0) so
     these actually win the cascade instead of losing to the desktop
     top/left/width rule despite coming later in the file — equal
     specificity, later wins; LOWER specificity loses regardless of
     source order, which is what broke this the first time. */
  /* Left inset bumped from an earlier 8% — confirmed via zoomed
     screenshot (not just bounding-box math) that Cloud Nine's actual
     irregular silhouette has a notch on its left side at this width,
     letting text spill onto the blue background even though it stayed
     inside the frame's rectangular bounds. 20% clears it. */
  .package-cloud-1 .package-cloud-copy,
  .package-cloud-3 .package-cloud-copy {
    top: 0; left: 0; right: 0; bottom: 0; width: auto;
    padding-left: 20%; padding-right: 20%;
  }
  .package-cloud-2 .package-cloud-copy {
    top: 0; left: 0; right: 0; bottom: 0; width: auto;
    padding-left: 26%; padding-right: 10%;
  }

  /* The desktop scattered-collage/stretched-headline treatment doesn't
     hold up at this width — word-spacing on the justified lines would
     get absurd with so few words per line at a readable font-size, and
     the absolutely-positioned photos would overlap the text badly. Both
     fall back to a plain stacked layout instead. */
  .value-prop { min-height: 0; }
  .value-prop-line,
  .value-prop-line-last { text-align: left; text-align-last: left; white-space: normal; }
  .value-prop-photos {
    position: static;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.75rem;
    margin-top: 2rem;
  }
  .vp-photo { position: static; width: 100%; transform: none; box-shadow: none; }
  .value-prop-copy { margin-top: 2.5rem; }

  .partner-stage { flex-direction: column; height: auto; }
  .partner-info { flex: 0 0 auto; }
  .partner-photos.is-active { min-height: 22rem; }
  /* .partner-nav's top:50%/right offset (above) assume the desktop
     side-by-side layout, where .partner-stage's own height IS the photo's
     height. Stacked here, the stage is taller than just the photo (photo
     + info below it), so re-anchor to the photo's own 22rem min-height
     instead, and drop the .partner-info offset since it's below, not
     beside, the photo now. */
  .partner-nav { top: 11rem; }
  .partner-nav-next { right: 1.5rem; }
  .packages-tagline { text-align: left; }

  /* .header-contact's -4rem margin-right (see its own rule, above) is
     tuned for desktop, where the header has plenty of spare width for
     the cloud's visual mass to push past its box edge into. At mobile
     widths that same fixed -4rem instead pushes the button (and its
     padding) partly off the actual screen — confirmed via
     document.body.scrollWidth, not just eyeballed. Pulled back to a much
     smaller nudge here, and sized up from the old cramped mobile
     version (was 1em/2.2em padding, 0.55rem label) since there's no
     overflow risk left to design around. */
  .header-contact {
    margin-right: -1rem;
    padding: 2.1em 4.6em;
  }
  .header-contact .cloud-button-label { font-size: 0.68rem; }
}

@media (max-width: 560px) {
  .social-tiles { flex-direction: column; align-items: center; }
  .social-tile { width: min(80%, 20rem); }
  .footer-clouds { height: 20rem; }
  .footer-bottom { flex-direction: column; align-items: flex-start; gap: 1rem; }
}

@media (max-width: 520px) {
  .wordmark-jp { display: none; }
  .site-header { padding: 0.65rem 1rem; }
  .wordmark { font-size: 0.62rem; }
  .header-contact { padding: 1.7em 3.6em; }
  .header-contact .cloud-button-label { font-size: 0.64rem; letter-spacing: 0.03em; }
}

/* ---------- reduced motion ---------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .opening-splash { display: none; }
  .cloud { animation: none; left: var(--cloud-rest, 10%); }
  .footer-cloud { animation: none; left: var(--cloud-rest, 10%); }
}
