/* ---------- hero ----------
   Three tiers, three genuinely different layouts (not just scaled
   versions of each other):

   XL-L (1200px+, node 332:909) and M (980–1199px, node 332:1055)
   both live INSIDE the site's real 12-column page grid (.container
   padding + repeat(12,1fr), same tokens as grid.css), with the
   avatar sitting mid-sentence in the heading via CSS Grid placement.
   Their column map is identical — only type scale/avatar size/
   spacing are each tier's own numbers (heading 48px→32px, body
   20/28→16/20, avatar 216×217→162×162px):
     XL-L (frame 1760px, margin 80/gutter 20):
       row 1 — eyebrow + first 2 heading lines   col 2 / span 10
       row 2 — avatar cluster                    col 3 / span 3
               closing heading line + paragraph  col 6 / span 7
     M (frame 900px, margin 40/gutter 20): identical column map to XL-L.
   XL-L additionally caps at a 1760px max-width (its frame's own
   width) and centers past that, unlike every other section on the
   page (which keep stretching indefinitely) and unlike M.

   S (max-width: 979px, node 332:1074 — also standing in for XS,
   which has no spec of its own yet) drops the grid entirely: the
   heading is ONE continuous, naturally-wrapping paragraph (no avatar
   embedded mid-sentence, no forced line breaks), stacked in a plain
   flex column above a second flex row that holds the avatar cluster
   (fixed 173px width) next to the paragraph (flex:1). This is a
   real layout change, not a smaller version of the grid tiers.

   Markup structure supporting all three: .hero-row1 wraps the
   eyebrow + <h1>; .hero-row2 wraps the avatar cluster + paragraph.
   The <h1> itself always contains just its two text fragments
   (heading-line1, heading-line2) — never the avatar — so at every
   tier it reads as one continuous heading to assistive tech.
     - At S, .hero-row1/.hero-row2 are real flex containers, and the
       <h1>'s two fragments flow together as continuous text (their
       manual <br> tags are display:none'd so only natural wrapping
       applies); the avatar cluster is a plain flex item in row 2,
       positioned via ordinary layout (no absolute positioning needed
       — align-items:center on row 2 handles the vertical alignment).
     - At 980px+, .hero-row1/.hero-row2 AND the <h1> are all
       display:contents, so every one of their children (eyebrow,
       heading-line1, avatar cluster, heading-line2, paragraph)
       becomes an independent item of the single .hero-grid — nested
       display:contents chains freely, so this still reads as one
       flat grid from .hero-grid's perspective. The manual <br> tags
       are shown (real forced 2-line breaks match each tier's Figma
       frame), and the avatar cluster is positioned absolutely inside
       its own grid cell to sit mid-sentence.
   The avatar's click-to-swap-photo interaction (markup, JS,
   animations) is identical across every tier — only the resize/
   layout behavior differs. */

.hero{
  padding: 32px 0; /* S/XS reference (node 332:1074) */
}

.hero-grid{
  display: flex;
  flex-direction: column; /* row1, row2 stacked with 0 gap, matching the Figma frame */
}

/* row 1 (eyebrow + heading) and row 2 (avatar + paragraph) as real
   flex containers at S/XS; at 980px+ they become display:contents so
   their children flatten straight into .hero-grid as grid items —
   see the file-level comment above. */
.hero-row1{
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.hero-row2{
  display: flex;
  align-items: center;
  gap: 20px;
}

@media (min-width: 980px){
  .hero-row1,
  .hero-row2{
    display: contents;
  }
}

@media (min-width: 980px) and (max-width: 1199px){
  .hero{
    padding: 80px 0;
  }
  .hero-grid{
    display: grid;
    grid-template-columns: repeat(var(--grid-columns), 1fr);
    /* row 3 fixed at 86px (the M frame's "into clear and cohesive /
       digital experiences." text height, node 332:1047) — same
       reasoning as XL-L's 130px below: keeps row 3 immune to
       avatar-col's spanned height demand, so row 4 (hero-desc) is the
       only track that has to grow for it. */
    grid-template-rows: auto auto 86px auto;
    column-gap: var(--grid-gutter);
  }
}

@media (min-width: 1200px){
  .hero{
    padding: 200px 0;
  }
  .hero-grid{
    display: grid;
    grid-template-columns: repeat(var(--grid-columns), 1fr);
    grid-template-rows: auto auto 130px auto; /* row 3 fixed — see .hero-avatar-col */
    column-gap: var(--grid-gutter);
    /* the Figma frame this layout is built from is 1760px wide (its
       1920px reference viewport minus 2×80px margin) — unlike the
       rest of the page, the hero doesn't keep stretching past that:
       it caps at 1760px and stays centered in the page, with the
       extra space (if the viewport grows past ~1920px) appearing as
       equal whitespace on both sides instead of widening the columns
       further. Other sections have no such cap and keep stretching. */
    max-width: 1760px;
    margin-left: auto;
    margin-right: auto;
  }
}

.hero-eyebrow{
  margin: 0 0 2px;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 20px;
  line-height: 1.35;
  letter-spacing: 0.8px;
  color: var(--color-body-text);
}

.hero-heading{
  margin: 0;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 48px;
  line-height: 1.35;
  color: var(--color-body-text);
  -webkit-text-stroke: var(--hero-heading-stroke-width) var(--color-body-text);
}

/* Both heading fragments are display:inline by default, so at S/XS
   they flow together as ONE continuous paragraph inside the <h1>
   (matching node 332:1074, where there's no visual break between
   them at all — their manual <br>s are hidden below, and the
   fragments just wrap naturally). At 980px+, becoming a grid item
   (via the <h1>'s display:contents) automatically "blockifies" each
   fragment regardless of this inline value — see the CSS Display
   spec's blockification rules — so this same base rule works for
   the grid tiers too without needing an override there. */
.hero-heading-line1{
  display: inline;
}
.hero-heading-line2{
  display: inline;
}

@media (max-width: 979px){
  /* the forced 2-line breaks only apply at 980px+, where each
     fragment is a fixed-size, fixed-width grid cell that needs an
     exact break point; at S/XS the heading is fluid and just wraps
     on its own at whatever width is available. */
  .hero-heading-line1 br,
  .hero-heading-line2 br{
    display: none;
  }
}

/* supporting paragraph — stretches to fill the full 7-column text
   area at both grid tiers (not tied to .hero-heading-line2's
   narrower shrink-to-fit width). This base rule has to come BEFORE
   the @media blocks below: it and the media queries both set
   .hero-desc's margin, and with matching specificity the later
   declaration in source order wins regardless of which media query
   matched — so if this sat after the @media blocks (as it briefly
   did), it would silently override their 12px margin with this
   rule's 16px at 980px+ too. */
.hero-desc{
  margin: 16px 0 0;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 20px;
  line-height: 28px;
  color: var(--color-body-text);
}

/* S/XS type scale (node 332:1074) — smaller than both grid tiers on
   every text element, including the eyebrow (which otherwise stays
   20px unchanged between M and XL-L, so it has no override there). */
@media (max-width: 979px){
  .hero-eyebrow{
    font-size: 14px;
    letter-spacing: 0.56px;
  }
  .hero-heading{
    font-size: 20px;
  }
  .hero-desc{
    /* no top margin here — at S, hero-desc sits BESIDE the avatar in
       a horizontal flex row (row2's align-items:center), not stacked
       below it, so the base rule's 16px top margin (leftover from
       the old stacked-mobile assumption) would just misalign it. */
    margin: 0;
    font-size: 14px;
    line-height: 16px;
  }
}

@media (min-width: 980px) and (max-width: 1199px){
  .hero-eyebrow{
    grid-column: 2 / span 10;
    grid-row: 1;
    margin: 0;
  }
  .hero-heading{
    display: contents;
    font-size: 32px; /* M's own type scale (node 332:1034/332:1047), not just a scaled-down 48px */
  }
  .hero-heading-line1{
    display: block;
    grid-column: 2 / span 10; /* same 1-column inset as XL-L */
    grid-row: 2;
    margin-top: 2px;
    position: relative;
  }
  .hero-avatar-col{
    grid-column: 3 / span 3;
    grid-row: 3 / span 2;
  }
  .hero-heading-line2{
    grid-column: 6 / span 7;
    grid-row: 3;
    justify-self: start;
    width: max-content;
    margin: 0;
  }
  .hero-desc{
    grid-column: 6 / span 7;
    grid-row: 4;
    margin: 12px 0 0;
    font-size: 16px;
    line-height: 20px;
  }
}

@media (min-width: 1200px){
  .hero-eyebrow{
    grid-column: 2 / span 10;
    grid-row: 1;
    margin: 0;
  }
  .hero-heading{
    display: contents;
  }
  .hero-heading-line1{
    display: block;
    grid-column: 2 / span 10;
    grid-row: 2;
    margin-top: 2px; /* matches the 2px gap between eyebrow/heading in the Figma frame */
    position: relative;
  }
  /* row 3 is pinned to a fixed 130px (the Figma height of the 2-line
     "into clear and cohesive / digital experiences." text, node
     214:433) rather than left as an auto track. Leaving it auto and
     letting avatar-col span rows 3+2 relies on the browser's
     "distribute extra space across spanned auto tracks" algorithm,
     which doesn't reliably hand the deficit to row 4 alone — it can
     inflate row 3 too, shoving hero-desc down with a huge gap. With
     row 3 fixed, only row 4 (still auto) is left to absorb whatever
     height avatar-col's span still needs, so hero-desc lands exactly
     12px under the heading text every time. */
  .hero-avatar-col{
    grid-column: 3 / span 3;
    grid-row: 3 / span 2;
  }
  /* shrink-to-fit instead of stretching across all 7 columns: its
     own box becomes exactly as wide as its widest forced line
     ("into clear and cohesive", before the <br>) — Satoshi 48px is a
     fixed size at this tier, not a fluid one, so this width doesn't
     depend on viewport width, only on the text itself. */
  .hero-heading-line2{
    grid-column: 6 / span 7;
    grid-row: 3;
    justify-self: start;
    width: max-content;
    margin: 0;
  }
  .hero-desc{
    grid-column: 6 / span 7;
    grid-row: 4;
    margin: 12px 0 0; /* matches the 12px gap in the Figma "text-2" column */
  }
}

/* the little curved arrow right after "...interaction", pointing
   down toward the avatar/"into" text — flows inline with the text
   (rather than being pixel-positioned) so it stays put next to the
   fixed-size heading text at any given tier's viewport width. Hidden
   below 980px, same as before. */
.hero-arrow-top{
  display: none;
}

@media (min-width: 980px) and (max-width: 1199px){
  .hero-arrow-top{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 29px;
    height: 24px;
    margin-left: 6px;
    transform: translateY(6px);
    vertical-align: middle;
  }
  .hero-arrow-top img{
    display: block;
    width: 100%;
    height: 100%;
  }
}

@media (min-width: 1200px){
  .hero-arrow-top{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 37px;
    height: 30px;
    margin-left: 8px;
    transform: translateY(8px);
    vertical-align: middle;
  }
  .hero-arrow-top img{
    display: block;
    width: 100%;
    height: 100%;
  }
}

/* avatar cluster: button + burst rays + "click" hint. At S/XS it's a
   fixed-width flex item sitting next to the paragraph in row 2
   (row2's align-items:center handles vertical alignment — no manual
   offsets needed, unlike the grid tiers below); at 980px+ it's the
   grid's col-3/span-3 cell, right-anchored inside (avatar + click
   hint keep a fixed pixel relationship to each other and to the
   right edge — matching each tier's own Figma frame at its own
   reference width — while any extra space the column gains as the
   viewport grows shows up as whitespace to their left). */
.hero-avatar-col{
  position: relative;
  flex: 0 0 173px; /* fixed width (node 321:514) — doesn't grow/shrink with row2 */
  height: 81px;
}

@media (min-width: 980px) and (max-width: 1199px){
  .hero-avatar-col{
    height: 180px; /* M "image" frame height, node 332:1037 */
  }
}

@media (min-width: 1200px){
  .hero-avatar-col{
    height: 226px; /* Figma "image" frame height, node 214:435 */
  }
}

.hero-avatar-wrap{
  position: absolute;
  top: 0; /* flush with avatar-col's top (node 321:515) — no vertical centering math needed at S */
  right: 0;
  width: var(--hero-avatar-width-s);
  height: var(--hero-avatar-height-s);
}

@media (min-width: 980px) and (max-width: 1199px){
  .hero-avatar-wrap{
    position: absolute;
    right: 0;
    /* fixed offset, not top:50%+translateY — same rounding-jitter
       rationale as XL-L below. (180px avatar-col − 162px avatar) / 2 */
    top: 9px;
    width: var(--hero-avatar-width-m);
    height: var(--hero-avatar-height-m);
    margin: 0;
  }
}

@media (min-width: 1200px){
  .hero-avatar-wrap{
    position: absolute;
    right: 0;
    /* vertically centered in avatar-col via a plain fixed offset
       (226px column height, 217px avatar height — both constants, not
       viewport-dependent) rather than top:50% + translateY(-50%). The
       percentage/transform version put the avatar's true position at
       a fractional pixel (avatar-col's own width — and so the layout
       math feeding the translate — comes from the fluid 12-column
       grid, e.g. 245.667px), and a transform-driven descendant
       (.hero-avatar-btn's pop animation) starting/stopping nearby
       could get composited on its own layer and rounded to a
       different whole pixel than the static layout position,
       producing a visible left/right jump once the animation ended.
       A constant integer top avoids that fractional base entirely. */
    top: 4.5px;
    width: var(--hero-avatar-width);
    height: var(--hero-avatar-height);
    margin: 0;
  }
}

/* the avatar is a real button — clicking it "pops" the illustration
   away and swaps in a real photo (js/app.js), matching the "click"
   label's promise. The pop scales the WHOLE circle (this button,
   clip mask included) rather than just the image inside a
   static-size window, so the circle itself visibly shrinks/grows. */
.hero-avatar-btn{
  position: static;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  overflow: hidden;
  border: none;
  padding: 0;
  margin: 0;
  background: var(--hero-avatar-bg); /* shows while the image loads */
  cursor: pointer;
  transform: scale(1); /* always present, matching the pop animations'
    resting value, so the element never switches between "transformed"
    and "not transformed" — that switch changes which pixel-rounding
    pass the browser paints it with, causing a ~1px drift right after
    the animation class is removed */
  transform-origin: center;
  /* keeps this element on its own compositing layer permanently
     (animating or not), instead of only while .is-popping's keyframe
     animation is running. Promoting/demoting a layer right as the
     animation starts/stops is a separate, larger source of the same
     kind of drift transform:scale(1) above already guards against —
     without this, the browser can round the button to a different
     whole device pixel once the layer is torn down post-animation,
     which reads as the whole circle (and the photo/illustration
     inside it) visibly hopping sideways right when the swap settles. */
  will-change: transform;
  z-index: 2;
}
.hero-avatar-btn:focus-visible{
  outline: 2px solid var(--hero-click-color);
  outline-offset: 2px;
}
.hero-avatar{
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* burst rays: sits OUTSIDE the button (which clips to a circle via
   overflow:hidden) so the rays are free to fly past the circle's
   edge instead of getting cut off. Same position/size as the wrap so
   its center lines up with the avatar's center. Shown at every tier
   — the burst is part of the click INTERACTION (js/app.js), not a
   piece of static Figma decoration, so it stays regardless of which
   layout is active. */
.hero-avatar-burst{
  display: block;
  position: absolute;
  top: 0;
  right: 0;
  width: var(--hero-avatar-width-s);
  height: var(--hero-avatar-height-s);
  pointer-events: none;
  z-index: 3;
}

@media (min-width: 980px) and (max-width: 1199px){
  .hero-avatar-burst{
    display: block;
    position: absolute;
    right: 0;
    top: 9px; /* same fixed offset as .hero-avatar-wrap, see there */
    width: var(--hero-avatar-width-m);
    height: var(--hero-avatar-height-m);
  }
}

@media (min-width: 1200px){
  .hero-avatar-burst{
    display: block;
    position: absolute;
    right: 0;
    top: 4.5px; /* same fixed offset as .hero-avatar-wrap, see there */
    width: var(--hero-avatar-width);
    height: var(--hero-avatar-height);
  }
}

.hero-avatar-ray{
  position: absolute;
  top: 50%;
  left: 50%;
  width: 3px;
  height: 14px;
  border-radius: 1.5px;
  background: var(--hero-click-color);
  opacity: 0;
  transform: translate(-50%, -50%) rotate(var(--ray-angle)) translateY(-72px) scaleY(0.6);
}
.hero-avatar-burst.is-bursting .hero-avatar-ray{
  animation: hero-avatar-ray-burst var(--hero-burst-duration) var(--hero-burst-ease);
}
.hero-avatar-burst.is-bursting .hero-avatar-ray:nth-child(odd){
  height: 18px;
}

/* smaller, tighter range than a first pass — rays stay close to the
   circle instead of flying far past it */
@keyframes hero-avatar-ray-burst{
  0%{ opacity: 0; transform: translate(-50%, -50%) rotate(var(--ray-angle)) translateY(-72px) scaleY(0.6); }
  35%{ opacity: 1; transform: translate(-50%, -50%) rotate(var(--ray-angle)) translateY(-82px) scaleY(1); }
  100%{ opacity: 0; transform: translate(-50%, -50%) rotate(var(--ray-angle)) translateY(-102px) scaleY(1.2); }
}

/* pop-out: a light anticipation dip, then vanishes as the rays burst
   past it — subtler than before, no big dramatic shrink */
@keyframes hero-avatar-pop-out{
  0%{ transform: scale(1); opacity: 1; }
  40%{ transform: scale(0.97); opacity: 1; }
  100%{ transform: scale(0); opacity: 0; }
}
/* pop-in: eases straight up to full size — no overshoot past 100% */
@keyframes hero-avatar-pop-in{
  0%{ transform: scale(0); opacity: 0; }
  100%{ transform: scale(1); opacity: 1; }
}
/* Both halves run as ONE chained animation on a single class instead of
   two classes swapped mid-flight by JS. Swapping classes at the
   pop-out/pop-in boundary left a one-frame gap where neither class
   was applied, so the browser briefly rendered the button's default
   (unanimated, scale(1)/opacity:1) state — a visible jitter right at
   the image swap. Chaining via animation-delay removes that gap
   entirely: the timeline is continuous from click to settle. */
.hero-avatar-btn.is-popping{
  animation:
    hero-avatar-pop-out var(--hero-pop-out-duration) var(--hero-pop-out-ease) forwards,
    hero-avatar-pop-in var(--hero-pop-in-duration) var(--hero-pop-in-ease) var(--hero-pop-out-duration) forwards;
}

@media (prefers-reduced-motion: reduce){
  .hero-avatar-btn.is-popping{
    animation: none;
  }
  .hero-avatar-burst.is-bursting .hero-avatar-ray{
    animation: none;
  }
}

/* "click" hint — a single exported vector (img/hero-click.svg),
   sitting to the lower-left of the avatar at every tier. Positioned
   relative to .hero-avatar-wrap's own box (right-anchored inside it)
   rather than pixel offsets from avatar-col's edge — so it tracks
   the avatar exactly at any viewport width within a tier. bottom:0
   flush-aligns its bottom edge with the avatar's at every tier.

   Sizing differs by tier: M and XL-L share the same 82x52 "sticker"
   size regardless of the smaller M avatar (a fixed size, per the M
   Figma frame's own node) — only the gap to the avatar's left edge
   shrinks (M: 4px; XL-L: 8px). S DOES scale the same vector down
   (48x30.44, node 332:1066 — exactly 0.585x the 82x52 original, a
   uniform scale, so it's the same asset file at a smaller size, not
   a new export) and the gap becomes a small NEGATIVE margin: at S
   the click asset's arrow tip tucks 4px under the avatar's edge
   rather than stopping short of it. */
.hero-click-group{
  display: block;
  position: absolute;
  right: 100%;
  bottom: 0;
  margin-right: -4px; /* negative — the arrow tip overlaps 4px under the avatar, per node 332:1066 */
  width: 48px;
  height: 30.44px;
  pointer-events: none;
  z-index: 2;
}

@media (min-width: 980px) and (max-width: 1199px){
  .hero-click-group{
    display: block;
    position: absolute;
    right: 100%;
    bottom: 0;
    margin-right: 4px;
    width: 82px;
    height: 52px;
    pointer-events: none;
    z-index: 2;
  }
}

@media (min-width: 1200px){
  .hero-click-group{
    display: block;
    position: absolute;
    right: 100%;
    bottom: 0;
    margin-right: 8px;
    width: 82px;
    height: 52px;
    pointer-events: none;
    z-index: 2;
  }
}

.hero-click-group img{
  display: block;
  width: 100%;
  height: 100%;
}

