/* Halfsecond — hero section only. Extracted from site.css.
   Embedded in the Astra site: every global rule is scoped to .film. */
/* Space Grotesk, self-hosted (was Google Fonts) */
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:300 700;font-display:swap;src:url(/assets/hero/fonts/space-grotesk-latin-ext.woff2) format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:300 700;font-display:swap;src:url(/assets/hero/fonts/space-grotesk-latin.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}


/* Halfsecond — site styles.
   One ground (#fff, the exact white of the footage, so the hand never sits in
   a box), one ink, one sans. Archivo and Newsreader only ever appear inside
   the demo sites, never in Halfsecond's own voice. */
.film {
  --paper: #ffffff;
  --ink: #17150f;
  --ink-2: rgba(20, 19, 15, .7);
  --ink-3: rgba(20, 19, 15, .62);   /* 5:1 on white — AA for the small caps lines */
  --line: rgba(20, 19, 15, .12);
  --sage: #aeb29b;
  --sage-ink: #8f9479;
  --cream: #f2f0e9;
  --gutter: clamp(20px, 3vw, 40px);
  --header-h: 96px;
  --ease: cubic-bezier(.2, .7, .1, 1);
  --ease-expo: cubic-bezier(.19, 1, .22, 1);
  --sans: 'Space Grotesk', system-ui, -apple-system, sans-serif;
}

.film, .film *, .film *::before, .film *::after { box-sizing: border-box; }

.film {
  font-family: var(--sans);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

:where(.film) a { color: inherit; text-decoration: none; }

:where(.film) :focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

/* solid pill that inverts to an outline on hover */
.btn-solid {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  border-radius: 999px;
  background: var(--ink);
  color: var(--cream);
  font-weight: 500;
  letter-spacing: -0.01em;
  white-space: nowrap;
  box-shadow: inset 0 0 0 1px rgba(20, 19, 15, 0);
  transition: background .22s, color .22s, box-shadow .22s;
}

/* Hover, after 21st.dev's Interactive Hover Button (dillionverma): a sage
   circle swells out of one point until it owns the pill, while the label
   slides out one side and its twin slides in from the other. */
.fx-btn {
  --fx-x: 50%;                           /* hero ctas: the fill grows from the middle */
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

.fx-btn::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--sage);
  clip-path: circle(0 at var(--fx-x) 50%);
  transition: clip-path .55s var(--ease-expo);
}

.fx-roll {
  display: inline-grid;
  overflow: hidden;
  padding: .1em .2em;                    /* glyph overhang stays inside the mask */
  margin: -.1em -.2em;
}

.fx-roll > span {
  grid-area: 1 / 1;
  transition: transform .5s var(--ease-expo), opacity .35s var(--ease);
}

.fx-roll > span + span { transform: translateX(-60%); opacity: 0; }

@media (hover: hover) {
  .fx-btn:hover::before { clip-path: circle(150% at var(--fx-x) 50%); }
  .fx-btn:hover .fx-roll > span { transform: translateX(60%); opacity: 0; }
  .fx-btn:hover .fx-roll > span + span { transform: none; opacity: 1; }
  .fx-btn:hover > i { transform: translateY(3px); }
}

.fx-btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

.fx-btn:focus-visible::before { clip-path: circle(150% at var(--fx-x) 50%); }

.fx-btn:focus-visible { color: var(--ink); }

/* The runway. The stage is pinned by CSS sticky; the extra height is scroll
   distance the film is read against — as a request, never as a playhead. */
.film {
  position: relative;
  height: 340svh;      /* 240svh of runway */
  background: var(--paper);
}

.film__stage {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  background: var(--paper);
}

.film__frame { position: absolute; inset: 0; }

.film__media { position: absolute; inset: 0; }

/* The footage enters from the right of the frame (GSAP, in initFilm). It
   waits off to the right from first paint so it never flashes in place first;
   if the script cannot run, this releases it after 3s so the hero is never
   left empty. */
.js:not(.is-reduced) .film__media {
  transform: translate3d(100%, 0, 0);
  animation: hs-media-safe 0s 3s forwards;
}

@keyframes hs-media-safe { to { transform: none; } }

.film__video,
.film__still {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 50%;
  background: var(--paper);
}

.film__video { z-index: 2; }

.film__video--intro { z-index: 3; }

.film__still { display: none; z-index: 1; }

/* A thick bank of mist along the bottom of the frame, between the footage
   and the copy. Pure CSS: it drifts on its own and is not tied to the film.

   Three layers of fractal noise — warm-grey billows far back, white veils in
   front, so it reads on white — carried by one wind: all drift the same way,
   the nearer the faster, so their overlap keeps changing shape. Each layer
   also breathes (rise, fall, thicken, thin) on its own prime-ish period, so
   the whole never visibly repeats. A wash under them keeps the copy legible.

   No tiling (tile seams show as vertical lines): each drift is one image, its
   mirror, then the image again — every join is seamless — and it loops by
   travelling two images, landing exactly where it started. Only transforms
   and opacity move. */
.mist {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 64%;
  z-index: 4;
  pointer-events: none;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to top, #000 45%, transparent);
          mask-image: linear-gradient(to top, #000 45%, transparent);
}

.mist__body {
  position: absolute;
  inset: 0;
  transform-origin: 50% 100%;
  will-change: transform, opacity;
}

.mist__body::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(255, 255, 255, .96), rgba(255, 255, 255, .78) 30%, rgba(255, 255, 255, .3) 65%, rgba(255, 255, 255, 0));
}

.mist__layer {
  position: absolute;
  left: 0; right: 0; bottom: -10%;       /* runs past the bottom: breathing never lifts an edge into view */
  height: 110%;
  will-change: transform, opacity;
  -webkit-mask-image: linear-gradient(to top, #000 55%, transparent);   /* no hard top edge */
          mask-image: linear-gradient(to top, #000 55%, transparent);
  animation: hs-breathe 17s ease-in-out infinite alternate;
}

.mist__drift {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  display: flex;
  width: calc(3 * max(100%, 1400px));  /* each image at least a viewport wide */
  will-change: transform;
  animation: hs-wind 150s linear infinite;
}

.mist__drift i {
  flex: 0 0 calc(100% / 3);
  background-repeat: no-repeat;
  background-size: 100% 100%;
}

.mist__drift i:nth-child(2) { transform: scaleX(-1); }

.mist__layer--far { --o: 1; }

.mist__layer--far i { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1400' height='420' viewBox='0 0 1400 420' preserveAspectRatio='none'%3E%3Cfilter id='n' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.0028 0.009' numOctaves='4' seed='7'/%3E%3CfeColorMatrix values='0 0 0 0 0.8  0 0 0 0 0.78  0 0 0 0 0.73  0 0 0 2.2 -0.72'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); }

.mist__layer--mid { --o: .75; height: 96%; animation-duration: 23s; animation-delay: -9s; }

.mist__layer--mid .mist__drift { animation-duration: 95s; animation-delay: -40s; }

.mist__layer--mid i { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1400' height='420' viewBox='0 0 1400 420' preserveAspectRatio='none'%3E%3Cfilter id='n' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.004 0.014' numOctaves='4' seed='23'/%3E%3CfeColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 2.6 -0.75'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); }

.mist__layer--near { --o: .7; height: 72%; animation-duration: 13s; animation-delay: -5s; }

.mist__layer--near .mist__drift { animation-duration: 60s; animation-delay: -22s; }

.mist__layer--near i { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1400' height='420' viewBox='0 0 1400 420' preserveAspectRatio='none'%3E%3Cfilter id='n' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.0022 0.02' numOctaves='4' seed='41'/%3E%3CfeColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 2.4 -0.8'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); }

/* The front wisps: the same wind, but sparse — only the peaks of the noise
   survive — and above the copy, so now and then a veil drifts across a word
   and the letters sit inside the mist rather than on top of it. Kept thin
   enough that the copy always reads. */
.mist--front {
  height: 46%;
  -webkit-mask-image: linear-gradient(to top, #000 60%, transparent);
          mask-image: linear-gradient(to top, #000 60%, transparent);
}

.mist--front .mist__body::before { display: none; }

.mist__layer--wisp { --o: .5; animation-duration: 19s; animation-delay: -7s; }

.mist__layer--wisp .mist__drift { animation-duration: 48s; animation-delay: -11s; }

.mist__layer--wisp i { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1400' height='420' viewBox='0 0 1400 420' preserveAspectRatio='none'%3E%3Cfilter id='n' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.0032 0.018' numOctaves='4' seed='59'/%3E%3CfeColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 3.4 -1.7'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); }

.mist__layer--wisp2 { --o: .38; height: 80%; animation-duration: 29s; animation-delay: -3s; }

.mist__layer--wisp2 .mist__drift { animation-duration: 72s; animation-delay: -50s; }

.mist__layer--wisp2 i { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1400' height='420' viewBox='0 0 1400 420' preserveAspectRatio='none'%3E%3Cfilter id='n' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.0045 0.026' numOctaves='4' seed='83'/%3E%3CfeColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 3.6 -1.9'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); }

@keyframes hs-wind { to { transform: translate3d(calc(-200% / 3), 0, 0); } }

@keyframes hs-breathe {
  0%   { opacity: var(--o); transform: translate3d(0, 0, 0) scaleY(1); }
  45%  { opacity: calc(var(--o) * .78); transform: translate3d(0, 3%, 0) scaleY(.94); }
  100% { opacity: var(--o); transform: translate3d(0, -4%, 0) scaleY(1.08); }
}

.mist__layer { transform-origin: 50% 100%; opacity: var(--o); }

.js:not(.is-reduced) .mist { animation: hs-fade 1.8s var(--ease) backwards; }

.is-reduced .mist__layer,
.is-reduced .mist__drift { animation: none; }

/* ------------------------------------------------------------------ hero */
.hero {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 4;
  padding: clamp(20px, 3.2vh, 40px) var(--gutter);
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: clamp(20px, 3vw, 48px);
}

.hero__copy {
  display: flex;
  flex-direction: column;
  gap: clamp(18px, 3vh, 32px);
  max-width: 840px;    /* the 700 weight runs wider */
}

.hero__title {
  margin: 0;
  font-size: clamp(34px, min(6vw, 9vh), 86px);
  line-height: 0.93;
  letter-spacing: -0.03em;
  font-weight: 700;                      /* the family's heaviest */
  text-transform: uppercase;
  padding-bottom: clamp(2px, .8vh, 10px);     /* the title sits a little above its subtitle */
}

.hero__lede {
  margin: 0;
  max-width: 640px;
  font-size: clamp(16px, min(1.55vw, 2.5vh), 23px);
  line-height: 1.45;
  color: var(--ink-2);
  text-wrap: pretty;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
}

.hero__cta {
  align-self: flex-start;
  padding: clamp(10px, 1.6vh, 15px) clamp(18px, 2vw, 27px);
  font-size: clamp(14px, min(1.1vw, 1.9vh), 16px);
}

.hero__chrono {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: clamp(8px, 1.2vh, 14px);
  flex: none;
  padding-bottom: 4px;
}

/* The copy floats in the mist: a soft warm shadow lifts every hero line off
   the bank below it (the nav keeps none). */
.chrono__value {
  text-shadow:
    0 2px 3px rgba(20, 19, 15, .10),
    0 14px 30px rgba(20, 19, 15, .22),
    0 30px 60px rgba(20, 19, 15, .12);
}

/* the title's lines sit in reveal masks that would clip a text-shadow into
   boxes: the shadow is cast by the whole title instead, after the masks */
.hero__title {
  filter: drop-shadow(0 2px 2px rgba(20, 19, 15, .10)) drop-shadow(0 14px 22px rgba(20, 19, 15, .20));
}

.hero__lede,
.chrono__micro,
.chrono__unit {
  text-shadow:
    0 1px 2px rgba(20, 19, 15, .12),
    0 6px 16px rgba(20, 19, 15, .16);
}

.hero__cta {
  box-shadow: 0 10px 28px rgba(20, 19, 15, .24), 0 2px 5px rgba(20, 19, 15, .14);
}

/* Entrance, after 21st.dev's Text Reveal (Mask) (soralabs): each headline
   line rises out of its own mask, then the lede, the cta and the header
   follow. Pure CSS from first paint, so nothing waits on script or on the
   intro film — it is all readable within ~1.3s. */
.hero__title .mask {
  display: block;
  overflow: hidden;
  padding-bottom: .1em;                  /* room for descenders inside the mask */
  margin-bottom: -.1em;
}

.hero__title .mask > span { display: block; }

/* the second cta: the same pill, outlined, so the first stays the lead */
.hero__cta.btn-ghost {
  background: rgba(255, 255, 255, .6);
  color: var(--ink);
  box-shadow: inset 0 0 0 1.5px rgba(20, 19, 15, .55);
}

@keyframes hs-rise { from { transform: translateY(110%); } }

@keyframes hs-fade-up { from { opacity: 0; transform: translateY(16px); filter: blur(6px); } }

@keyframes hs-fade-down { from { opacity: 0; transform: translateY(-10px); } }

@keyframes hs-fade { from { opacity: 0; } }

.js:not(.is-reduced) .hero__title .mask > span { animation: hs-rise 1.1s var(--ease-expo) .15s both; }

.js:not(.is-reduced) .hero__title .mask + .mask > span { animation-delay: .27s; }

.js:not(.is-reduced) .hero__lede { animation: hs-fade-up 1s var(--ease-expo) .5s both; }

.js:not(.is-reduced) .hero__cta { animation: hs-fade-up 1s var(--ease-expo) .64s both; }

.js:not(.is-reduced) .hero__cta + .hero__cta { animation-delay: .74s; }

.js:not(.is-reduced) .chrono__readout { animation: hs-fade 1s var(--ease) .1s both; }

/* Two lines, two moments: the first while the clock crawls, the second the
   instant it stops. Driven by the intro's own clock from JS. */
.chrono__micro {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  font-size: clamp(12px, 1.5vh, 13px);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 440;
  white-space: nowrap;
}

.chrono__line { display: block; color: var(--ink-3); }

.js:not(.is-reduced) .chrono__line { opacity: 0; transform: translateY(6px); transition: opacity .6s var(--ease), transform .6s var(--ease); }

.js:not(.is-reduced) .chrono__line.is-on { opacity: 1; transform: none; }

.chrono__readout {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-variant-numeric: tabular-nums;
}

.chrono__value {
  font-size: clamp(40px, min(7.6vw, 12vh), 116px);
  line-height: 0.86;
  font-weight: 500;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

.chrono__unit {
  font-size: clamp(16px, min(2.1vw, 3.4vh), 34px);
  font-weight: 440;
  color: var(--ink-3);
}

/* Not a shrunk desktop. The hand enters from the right edge and spans most
   of the frame, so a 9:16 crop would amputate the fingers. Instead the shot
   keeps its 16:9 and sits as a band on the page: the footage's ground is the
   page's ground (#fff), so the band has no visible edge. */
@media (max-aspect-ratio: 4/5) {
  .film__video,
  .film__still {
    object-fit: contain;
    object-position: 50% 44%;
  }
  .hero {
    top: 0;                      /* so the clock can sit under the header */
    flex-direction: column;
    justify-content: flex-end;
    align-items: stretch;
    gap: 22px;
    padding-bottom: max(24px, env(safe-area-inset-bottom));
  }
  .hero__title { font-size: clamp(34px, 10.4vw, 52px); }
  .hero__lede { font-size: 17px; }
  .hero__chrono {
    position: absolute;
    top: calc(var(--header-h) - 14px);
    left: var(--gutter);
    right: var(--gutter);
    flex-direction: row-reverse;
    justify-content: space-between;
    align-items: flex-end;
  }
  .chrono__micro { align-items: flex-end; font-size: 10px; }
  .chrono__value { font-size: 44px; }
  .chrono__unit { font-size: 18px; }
}

@media (max-width: 820px) {
  .film { --header-h: 76px; }
}

/* The film becomes two still pages: the open palm with the whole hero, then
   the collection in normal flow. Nothing is pinned, nothing plays, nothing
   is hidden. */
html:not(.js) .film,
.is-reduced .film { height: auto; }

html:not(.js) .film__stage,
.is-reduced .film__stage { position: relative; height: auto; overflow: visible; }

html:not(.js) .film__frame,
.is-reduced .film__frame { position: relative; height: 100svh; }

html:not(.js) .film__video,
.is-reduced .film__video { display: none; }

html:not(.js) .film__still,
.is-reduced .film__still { display: block; }

/* standalone: the hero is the whole page, not a pinned runway */
.film { height: 100svh; }

/* guard against the host page's resets */
:where(.film) :is(h1, p) { font-family: var(--sans); }
