/* ============================================================
   Sami Faust — portfolio (home page rebuild, 2026-07-29)
   Palette source: ColorScheme.png (Kuma Studio)
   Fonts: --font-display is the Japanese-text font (DotGothic16, a
   Google Font — loaded via the <link> in <head>, not the Typekit
   kit below), --font-sans is the English-text font (Montserrat) —
   split by LANGUAGE, not by role, per Chloe's 2026-07-29 instruction.
   Montserrat is served through Adobe Fonts kit xab6xhn (the Typekit
   embed script in <head>) — confirmed it's on Adobe Fonts, but
   Sami/Chloe still need to add it to that kit via the Adobe Fonts
   project dashboard if it isn't in it already, since Claude has no
   access to that account. Until then, or if either font fails to
   load, these fall back to the stacks below. (DotGothic16 replaced
   AB Kokikaku on 2026-07-29 — a deliberate pixel/dot-matrix look for
   Japanese text, not just a swap for consistency.)

   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:   #FCFBF8;
  --blue:    #0000FE;
  --blush:   #E9DEE2;
  --lilac:   #C382FC;
  --red:     #FD3302;
  --ink:     #0C0D0E;

  /* Japanese text only (wordmark-jp, hero-jp, .packages-title .jp) */
  --font-display: "DotGothic16", "Hiragino Kaku Gothic ProN", "Noto Sans JP", sans-serif;
  /* All English/Latin text (body copy, headings, UI) */
  --font-sans: "montserrat", "Helvetica Neue", "Hiragino Sans", "Noto Sans JP", Arial, sans-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-sans);
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; }

/* ---------- custom cursor (index.html only — scoped to .home, not the
   bare body/html selector, so it doesn't leak into kuma-studio.html via
   this shared stylesheet). Source: misc assets/riceball.png, the simple
   outline onigiri icon (not the detailed KumaRiceball.png illustration,
   which is too fine-lined to read at cursor size), cropped to content,
   rotated 45°, and downscaled to 64x64 — see
   site/assets/images/cursor-riceball.png. Hotspot centered (32 32) since
   there's no obvious "tip" on a rotated blob shape the way an arrow has one.
   Links/buttons get a UA-default pointer cursor that would otherwise
   override this on hover, so it's reset back to inherit here too. */
.home {
  cursor: url("../assets/images/cursor-riceball.png") 32 32, auto;
}

.home a,
.home 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-sans);
  font-weight: 900;
  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 ---------- */

.site-header {
  position: sticky;
  top: 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: var(--blue);
}

.wordmark {
  text-decoration: none;
  color: var(--cream);
  display: flex;
  align-items: baseline;
  gap: 0.55em;
  font-weight: 800;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.wordmark-jp {
  font-family: var(--font-display);
  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 ---------- */

.hero {
  display: flex;
  min-height: 100svh;
  align-items: stretch;
}

.hero-panel {
  flex: 1 1 50%;
  min-width: 20rem;
  background: var(--red);
  color: var(--cream);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1.25rem;
  padding: clamp(2rem, 6vw, 5rem) var(--edge);
}

.hero-jp {
  display: flex;
  flex-direction: column;
  font-family: var(--font-display);
  font-size: 2.2rem;
  line-height: 1.3;
  letter-spacing: 0.05em;
  opacity: 0.85;
  margin-bottom: 0.5rem;
}

.hero-name {
  margin: 0;
  font-family: var(--font-sans);
  /* Montserrat Black */
  font-weight: 900;
  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 text — Montserrat via --font-sans, not --font-display (that's
     Japanese-only now). A light weight + a little letter-spacing keeps it
     reading as a quieter subheading under the bold SAMI FAUST above,
     since it's no longer a separate serif face doing that job. */
  font-family: var(--font-sans);
  font-weight: 300;
  letter-spacing: 0.01em;
  font-size: clamp(1.8rem, 4vw, 2.75rem);
  line-height: 1.15;
}

.hero-sub {
  margin: 0;
  max-width: 26rem;
  font-size: 0.95rem;
  font-weight: 700;
  line-height: 1.5;
}

.hero-photo {
  flex: 1 1 50%;
  min-width: 20rem;
  margin: 0;
}

.hero-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  display: block;
}

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

.social-media {
  background: var(--lilac);
  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-sans);
  font-weight: 900;
  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(--cream);
}

.social-media-title span { display: block; }

.social-media-blurb {
  margin: 1.5rem auto 0;
  font-size: 0.9rem;
  font-weight: 600;
  line-height: 1.65;
  color: var(--ink);
  opacity: 0.7;
}

/* Deliberately NOT the site's cloud-button component — a plain outlined
   pill instead, so the purple 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. */
.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(--cream);
  border-radius: 999px;
  color: var(--cream);
  text-decoration: none;
  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(--cream);
  color: var(--ink);
}

.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(12, 13, 14, 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(12,13,14,0.06) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(12,13,14,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-sans);
  font-weight: 900;
  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(12,13,14,0.2);
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.social-tile:hover {
  transform: translateY(-6px);
  box-shadow: 0 28px 48px rgba(12,13,14,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(12,13,14,0.92) 45%, rgba(12,13,14,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;
}

/* ---------- brand partnerships ---------- */

.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(12,13,14,0.06) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(12,13,14,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;
  font-family: var(--font-sans);
  font-weight: 900;
  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 the actual rendered width of "BRAND
     PARTNERSHIPS" at this weight (was overflowing past the viewport
     edge at 10.6vw), not a round number. */
  font-size: 8vw;
  line-height: 0.85;
  letter-spacing: -0.01em;
  white-space: nowrap;
  text-align: center;
  color: var(--ink);
}

.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(12, 13, 14, 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-sans);
  font-weight: 800;
  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 {
  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-display);
  font-size: clamp(1.8rem, 4.2vw, 3.25rem);
  letter-spacing: 0.04em;
  color: var(--cream);
  text-shadow: 0 4px 18px rgba(12, 13, 14, 0.6);
  white-space: nowrap;
}

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

/* 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);
}

.packages-tagline strong { font-weight: 800; }

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

.packages-title {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin: 0 0 1.5rem;
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: clamp(2.5rem, 6vw, 4rem);
}

.packages-title .jp {
  font-family: var(--font-display);
  font-size: 0.5em;
  font-weight: 400;
  opacity: 0.8;
}

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

.package-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.75rem;
}

.package-card {
  position: relative;
  background: var(--cream);
  color: var(--ink);
  border-radius: 0.5rem;
  padding: 2rem 1.75rem;
  box-shadow: 0 20px 40px rgba(12,13,14,0.2);
  /* clips the ribbon lines below to exactly this card's edge — see
     .package-ribbon-h — and keeps the bow fully on-card */
  overflow: hidden;
}

.package-card h3 {
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: 1.5rem;
  /* clears the ribbon-h line (sits at 2.75rem from the card's top edge,
     see .package-ribbon-h below) now that .package-number no longer
     occupies that space above the heading */
  margin: 1.25rem 0 0.75rem;
}

.package-card p {
  font-size: 0.9rem;
  line-height: 1.55;
  opacity: 0.75;
  margin: 0;
  /* keep text clear of the ribbon running down the card's right side */
  max-width: 82%;
}

/* Ribbon + bow: a vertical and horizontal line crossing near the card's
   top-right corner, with the bow sitting at their intersection. Both
   lines run an oversized length past the card, then get cropped flush by
   .package-card's overflow:hidden — so they always reach exactly to the
   card's own edge regardless of the grid column's rendered width,
   instead of stopping short or spilling past it. The bow itself is
   nudged down from that crossing point so its full circular shape
   stays on-card rather than getting clipped. Color is --red (the
   palette's orange-red), matching the cloud-button hover color. */
.package-ribbon {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 2.5rem;
  width: 0;
  pointer-events: none;
}

.package-ribbon-v {
  position: absolute;
  inset: 0;
  left: -1px;
  width: 2px;
  background: var(--red);
}

.package-ribbon-h {
  position: absolute;
  top: 2.75rem;
  left: -100rem;
  right: -100rem;
  height: 2px;
  background: var(--red);
}

.package-bow {
  position: absolute;
  top: 2.75rem;
  left: 0;
  transform: translate(-50%, -50%);
  width: 3.25rem;
  height: auto;
  aspect-ratio: 1 / 1;
  fill: var(--red);
}

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

.site-footer {
  background: var(--red);
  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-sans);
  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-spotify .footer-cloud-label { transform: translate(5px, 7px); }
.footer-cloud-pottery .footer-cloud-label { transform: translate(4px, 6px); }

.footer-bottom {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
  padding: 1.75rem var(--edge) 2.5rem;
  border-top: 2px solid var(--ink);
}

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

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

.footer-wordmark-latin {
  font-family: var(--font-sans);
  font-weight: 900;
  font-size: 1.6rem;
  letter-spacing: -0.01em;
}

/* Credit line, deliberately set in --font-display (DotGothic16) even
   though it's English text — a one-off exception to the usual
   language-based font split, per Chloe's direct request. */
.footer-credit {
  font-family: var(--font-display);
  font-size: 1.1rem;
  color: var(--ink);
}

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

@media (max-width: 860px) {
  .hero { flex-direction: column; }
  .hero-panel,
  .hero-photo { flex: 1 1 auto; min-height: 50vh; }
  .social-tiles { gap: 1.25rem; }
  .social-tile { width: min(46%, 14rem); }
  .partner-stage { flex-direction: column; height: auto; }
  .partner-info { flex: 0 0 auto; }
  .partner-photos.is-active { min-height: 22rem; }
  .package-grid { grid-template-columns: 1fr; }
  .packages-tagline { text-align: left; }

  /* .partner-stage stacks photo-over-info at this width (above), so the
     flavor text's straddle-the-seam positioning (style.css, tuned for
     the side-by-side desktop layout) no longer makes sense — simplest
     fix is to just not show it once there's no seam to straddle. */
  .partner-flavor-layer { display: none; }

  /* .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%); }
}
