/* ==========================================================================
   Kodiyo — home.css
   Homepage sections only. Shared pieces live in base.css / components.css.

   Every section is authored mobile-first and stacked; the scroll-driven
   desktop behaviour is layered on top inside min-width media queries, so the
   page is complete and readable with no JS and on any screen.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Hero
   The card stack is sized against viewport HEIGHT as well as width, so it can
   never push the copy out of the first screen on a short laptop display.
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  display: flex;
  align-items: center;
  min-height: calc(100svh - var(--header-h));
  padding-block: clamp(1.5rem, 5vh, 4rem);
  overflow: hidden;
}

.hero__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
  gap: clamp(var(--space-lg), 4vh, var(--space-2xl));
  width: 100%;
}

.hero__title {
  font-size: var(--fs-display);
  line-height: 1.06;
  margin-bottom: var(--space-lg);
}

/* "fast" always carries the italic face, so the line never re-flows. JS
   counter-skews it upright and then releases the skew, which reads as the
   letters tilting into place. Transform-only — no layout cost. */
.hero__italic {
  display: inline-block;
  font-style: italic;
  will-change: transform;
}

.hero__lead {
  font-size: var(--fs-lead);
  line-height: 1.5;
  color: var(--color-fg);
  max-width: 32ch;
  margin-bottom: var(--space-xl);
}

.hero__cta {
  margin-top: var(--space-2xs);
}

/* Shuffling stack of email screenshots ------------------------------------
   Tilted in 3D: the stack faces slightly left, cards step forward one at a
   time, straighten at the front, then slide up and fade to the back. */
.hero__stack {
  --card-ratio: 1.75;          /* 1085 / 620 */

  position: relative;
  width: min(100%, 360px, calc((100svh - var(--header-h) - 9rem) / var(--card-ratio)));
  margin-inline: auto;
  aspect-ratio: 620 / 1085;
  perspective: 1500px;
  perspective-origin: 65% 50%;
}

.hero__card {
  position: absolute;
  inset: 0;
  border-radius: 5px;
  overflow: hidden;
  background-color: var(--color-surface);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
  transform-origin: center center;
  will-change: transform, opacity;
}

.hero__card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}

/* Static 3D fan for the no-JS / reduced-motion case ONLY.
   These must not apply when GSAP is driving: it seeds its transform state by
   decomposing whatever matrix is already on the element, and a compound
   rotateY/translateX/scale does not decompose back cleanly — the leftovers
   show up as every card sitting a fraction of a slot out of place. */
html:not(.js) .hero__card:nth-child(1) { transform: rotateY(-16deg) translateX(0%)  scale(1);    z-index: 5; }
html:not(.js) .hero__card:nth-child(2) { transform: rotateY(-16deg) translateX(9%)  scale(0.96); z-index: 4; }
html:not(.js) .hero__card:nth-child(3) { transform: rotateY(-16deg) translateX(18%) scale(0.92); z-index: 3; }
html:not(.js) .hero__card:nth-child(4) { transform: rotateY(-16deg) translateX(27%) scale(0.88); z-index: 2; }
html:not(.js) .hero__card:nth-child(5) { transform: rotateY(-16deg) translateX(36%) scale(0.84); z-index: 1; }

@media (min-width: 900px) {
  .hero__inner {
    /* The copy column carries a long headline; the stack is capped at 360px
       and never needs the slack. */
    grid-template-columns: minmax(0, 1.12fr) minmax(0, 0.88fr);
    gap: var(--space-lg);
  }

  /* The copy sits inset from the container edge, matching the other two-column
     sections. Above ~1440px that inset has to come back off again: the
     container is capped at 1240px and --fs-display tops out at 4.5rem, so the
     headline stops growing while the layout stays frozen — the copy just sits
     there drifting toward the stack with nothing to balance it. Easing the
     inset to 0 walks the block back to the left instead.

     min() so this can only ever REDUCE the inset — below 1440px the normal
     --content-inset still wins and nothing about the mid-size layout moves.
     The stack is untouched. */
  .hero__copy {
    padding-inline-start: min(
      var(--content-inset),
      clamp(0px, 172px - 8.96vw, 3.25rem)
    );
  }

  /* Centred in its column rather than jammed against the right edge — that
     right-alignment is what opened the wide gap between copy and cards. */
  .hero__stack {
    margin-inline: auto;
  }
}

@media (max-width: 899px) {
  .hero {
    min-height: 0;
  }

  /* Single column. Matching the lead→CTA and CTA→stack gaps keeps the three
     blocks evenly spaced instead of bunching the stack up under the button. */
  .hero__inner {
    gap: var(--space-xl);
  }

  .hero__lead {
    margin-bottom: var(--space-lg);
  }

  .hero__cta {
    margin-top: 0;
  }

  /* Much tighter on phones so the headline, lead, CTA and stack all land
     within the first screen. */
  .hero__stack {
    width: min(62%, 208px, calc((100svh - var(--header-h) - 26rem) / var(--card-ratio)));
  }

  .hero__lead {
    max-width: 34ch;
  }
}

/* --------------------------------------------------------------------------
   2. Scroll Worthy Emails

   Mobile/fallback: heading, the email inline at full length, then the closing
   line — all centred, and the email scrolls with the page rather than being a
   separate scrolling window.

   Desktop: the same three pieces are absolutely placed inside .sw__clip, which
   is exactly as wide as the site's content column. Because the clip — not the
   viewport — does the masking, elements enter and leave along the same
   alignment line as every other section.
   -------------------------------------------------------------------------- */
.sw {
  position: relative;
  padding-block: var(--section-py);
}

.sw__track {
  position: relative;
}

.sw__clip {
  width: 100%;
}

.sw__stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2xl);
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--container-pad);
}

.sw__heading,
.sw__outro {
  flex: 0 0 auto;
  text-align: center;
}

.sw__heading {
  font-size: var(--fs-h2);
}

.sw__outro {
  font-family: var(--font-heading);
  font-size: var(--fs-h2);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-tight);
}

/* The email preview.
   The frame renders at a fixed LOGICAL width and is scaled to fit its box, so
   a single calibrated --email-frame-height is correct at every breakpoint.
   js/home.js sets --email-scale from the box's measured width.

   To swap the sample: change the iframe src in index.html. If the new sample
   is a different length, retune --email-frame-height below (it is the height
   of the email when rendered at --email-logical-width). */
.sw__window {
  /* Phone/tablet: render at a phone width so the email uses its own mobile
     layout. Desktop overrides both values below. Heights are MEASURED at the
     matching logical width — see README before changing either. */
  --email-logical-width: 390px;
  --email-frame-height: 2686px;
  --email-scale: 1;
  /* The sample's own white margin either side of its content at the logical
     width above. The box is narrowed by it and the frame shifted across, so
     the email's content — not its margin — meets the preview's edge. */
  --email-gutter: 0px;

  position: relative;
  flex: 0 0 auto;
  /* The box tracks the email's CONTENT width, so the frame renders 1:1 and
     never leaves a margin beside itself. Only a viewport narrower than that
     scales it down. */
  width: min(100%, calc(var(--email-logical-width) - 2 * var(--email-gutter)));
  overflow: hidden;
  border: var(--border-hairline) solid var(--color-line-strong);
  border-radius: 6px;
  background-color: #ffffff;

  /* Mobile: full length inline, so the page's own scroll moves it. */
  height: calc(var(--email-frame-height) * var(--email-scale));
}

.sw__scroller {
  height: 100%;
  will-change: transform;
}

/* Tablet: wide enough for the email's full 600px content, still its stacked
   layout (which this sample uses below 625px). */
@media (min-width: 640px) {
  .sw__window {
    --email-logical-width: 600px;
    --email-frame-height: 2806px;
  }
}

.sw__frame {
  width: var(--email-logical-width);
  /* Overrides the global `iframe { max-width: 100% }` reset, which would
     otherwise clamp the frame to its box and defeat the fixed logical width. */
  max-width: none;
  height: var(--email-frame-height);
  border: 0;
  display: block;
  /* translateX runs INSIDE the scale, so the crop tracks the scale exactly. */
  transform: scale(var(--email-scale)) translateX(calc(-1 * var(--email-gutter)));
  transform-origin: top left;
  /* The preview must never swallow the page's wheel or touch scroll. */
  pointer-events: none;
}

/* Desktop / large tablet: horizontal sequence ------------------------------
   Gated on `.js`, because only GSAP can move the strip. Without JS — or under
   prefers-reduced-motion, where home.js drops the `js` flag — the stacked
   layout above stays in force, so the email preview is always reachable. */
@media (min-width: 1024px) {
  .js .sw {
    padding-block: 0;
  }

  .js .sw__track {
    /* The scroll distance the sequence plays over. */
    min-height: 420vh;
  }

  .js .sw__viewport {
    position: sticky;
    top: var(--header-h);
    height: calc(100svh - var(--header-h));
    display: flex;
    align-items: center;
  }

  /* The masking box IS the content column. */
  .js .sw__clip {
    position: relative;
    width: min(100% - 2 * var(--container-pad),
               var(--container-max) - 2 * var(--container-pad));
    height: min(74vh, 680px);
    margin-inline: auto;
    overflow: hidden;
  }

  .js .sw__stage {
    display: block;
    height: 100%;
    max-width: none;
    padding-inline: 0;
  }

  .js .sw__heading,
  .js .sw__outro,
  .js .sw__window {
    position: absolute;
    left: 0;
    top: 0;
    height: 100%;
    margin: 0;
    will-change: transform;
  }

  .js .sw__heading,
  .js .sw__outro {
    display: flex;
    align-items: center;
    width: max-content;
    max-width: 46%;
    text-align: left;
  }

  .js .sw__window {
    /* This sample switches to its desktop layout at exactly 625px, and its
       content caps at 600px — so 632 is the narrowest width that gets the
       desktop layout with almost no white gutter either side. The box is that
       same width, so the frame renders 1:1 with no downscaling. */
    --email-logical-width: 632px;
    --email-frame-height: 2660px;
    /* Measured: at 632px this sample's content is exactly 600px, centred. */
    --email-gutter: 16px;

    max-width: none;
  }

  .js .sw__scroller {
    height: calc(var(--email-frame-height) * var(--email-scale));
  }
}

/* --------------------------------------------------------------------------
   3. Photoshoot-like AI imagery
   Three plain/styled pairs on a self-running loop — no scroll coupling. Each
   shot keeps its own proportions and scales as a whole, so there is no static
   frame for them to zoom inside of.
   -------------------------------------------------------------------------- */
.imagery__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2xl);
  align-items: center;
}

.imagery__stage {
  position: relative;
  display: grid;
  /* An explicit, definite row: without it the row is auto-sized and the shots'
     percentage height cannot resolve, so they blow past the stage. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  place-items: center;
  height: clamp(300px, 46vh, 420px);
}

/* All shots share one grid cell, so they overlap without absolute positioning
   and each keeps its intrinsic aspect ratio. */
.imagery__shot {
  grid-area: 1 / 1;
  height: 100%;
  width: auto;
  max-width: 100%;
  object-fit: contain;
  border-radius: var(--radius-card);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.4);
  will-change: transform, opacity;
}

/* Only the first shot shows until JS starts the loop. */
.js .imagery__shot + .imagery__shot {
  opacity: 0;
}

html:not(.js) .imagery__shot + .imagery__shot {
  display: none;
}

.imagery__title {
  font-size: var(--fs-h2);
  margin-bottom: var(--space-md);
}

.imagery__cta {
  margin-top: var(--space-lg);
}

@media (min-width: 900px) {
  .imagery__inner {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: clamp(var(--space-2xl), 6vw, var(--space-4xl));
  }

  .imagery__stage {
    height: clamp(420px, 60vh, 580px);
  }
}

/* --------------------------------------------------------------------------
   4. Personalization Pro Max
   The cards fan down-and-right with the FIRST card in front. That order is
   deliberate: each recipient's name sits at the bottom of its card, and this
   is the only arrangement where every name stays visible.
   -------------------------------------------------------------------------- */
.personalization__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-3xl);
  align-items: center;
}

.personalization__title {
  font-size: var(--fs-h2);
  margin-bottom: var(--space-md);
}

.personalization__cta {
  margin-top: var(--space-lg);
}

.personalization__stack {
  --card-w: 72%;

  position: relative;
  width: min(100%, 340px);
  margin-inline: auto;
  /* Two cards on phones (the third is hidden below) — three would make the
     section far too long at this width. */
  aspect-ratio: 1 / 1.35;
}

.personalization__card {
  position: absolute;
  width: var(--card-w);
  border-radius: 5px;
  overflow: hidden;
  background-color: #ffffff;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
  will-change: transform;
}

.personalization__card img {
  width: 100%;
  height: auto;
}

/* Steps are a share of the stack width, which keeps the fan proportional.
   The FIRST card is in front: each card behind is lower and to the right, so
   the plate — and the name being engraved on it — stays visible on every one. */
.personalization__card:nth-child(1) { top: 0;   left: 0;   z-index: 3; }
.personalization__card:nth-child(2) { top: 30%; left: 22%; z-index: 2; }

.personalization__card:nth-child(3) {
  display: none;
}

@media (min-width: 900px) {
  .personalization__inner {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: clamp(var(--space-2xl), 6vw, var(--space-4xl));
  }

  .personalization__copy {
    padding-inline-start: var(--content-inset);
  }

  /* Desktop: all three, fanned mostly sideways. The step is a quarter of the
     card, so a good slice of each plate behind stays in view — that is what
     shows three different names being engraved. Far shorter than a cascade. */
  /* Desktop: the mockup's own arrangement — two cards up top, the third
     brought forward and down between them. */
  .personalization__stack {
    --card-w: 53%;

    width: min(100%, 520px);
    aspect-ratio: 1 / 1.12;
    margin-inline: auto;
  }

  .personalization__card:nth-child(1) { top: 0;   left: 0;   z-index: 1; }
  .personalization__card:nth-child(2) { top: 9%;  left: 46%; z-index: 2; }

  .personalization__card:nth-child(3) {
    display: block;
    top: 38%;
    left: 14%;
    z-index: 3;
  }
}

/* --------------------------------------------------------------------------
   5. Fundamentals on-point
   -------------------------------------------------------------------------- */
.fundamentals__title {
  text-align: center;
  margin-bottom: var(--space-3xl);
}

.fundamentals__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2xl);
}

.fundamental {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
}

.fundamental__icon {
  height: 68px;
  width: auto;
  opacity: 0.95;
}

.fundamental__name {
  font-family: var(--font-sub);
  font-weight: var(--weight-sub);
  font-size: 1.25rem;
  letter-spacing: 0;
}

.fundamental__text {
  color: var(--color-muted);
  max-width: 24ch;
  line-height: 1.55;
}

@media (min-width: 760px) {
  .fundamentals__grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-xl);
  }
}

/* --------------------------------------------------------------------------
   6. Testimonial
   -------------------------------------------------------------------------- */
.testimonial__title {
  text-align: center;
  margin-bottom: var(--space-3xl);
}

.quote {
  position: relative;
  max-width: 620px;
  margin-inline: auto;
  padding: clamp(2.5rem, 4vw, 3.25rem) clamp(1.75rem, 3.5vw, 2.75rem) clamp(1.75rem, 2.6vw, 2.25rem);
}

/* The drawn border. JS sizes the viewBox to the box so the corner radius stays
   true at any width; if JS never runs, .quote gets a plain border instead. */
.quote__outline {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}

.quote__outline rect {
  fill: none;
  stroke: var(--color-line-strong);
  stroke-width: 1;
}

html:not(.js) .quote {
  border: var(--border-hairline) solid var(--color-line-strong);
  border-radius: var(--radius-quote);
}

html:not(.js) .quote__outline {
  display: none;
}

/* Sits astride the top-left corner. The icon's own dark stroke knocks the
   border out behind it, so no background patch is needed. */
.quote__mark {
  position: absolute;
  top: 0;
  left: clamp(1.5rem, 4vw, 2.5rem);
  width: clamp(46px, 5.5vw, 62px);
  height: auto;
  transform: translateY(-46%);
  pointer-events: none;
}

.quote__body {
  font-size: clamp(1.0625rem, 1.55vw, 1.25rem);
  line-height: 1.6;
  font-style: italic;
  text-align: center;
  color: var(--color-fg);
}

/* Word-level fade so the line "types" itself in without reflowing. */
.quote__body .word {
  display: inline-block;
  white-space: pre;
}

.js .quote__body .word {
  opacity: 0;
}

.quote__attrib {
  margin-top: var(--space-md);
  text-align: center;
  font-style: normal;
  line-height: 1.45;
}

.quote__name {
  display: block;
  font-family: var(--font-sub);
  font-weight: var(--weight-sub);
  font-style: italic;
  font-size: var(--fs-lead);
  margin-bottom: var(--space-3xs);
}

.quote__role {
  display: block;
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--color-muted);
}

/* --------------------------------------------------------------------------
   7. Contact
   -------------------------------------------------------------------------- */
.contact__header {
  text-align: center;
  margin-bottom: var(--space-2xl);
}

.contact__title {
  margin-bottom: var(--space-md);
}

.contact__lead {
  max-width: 56ch;
  margin-inline: auto;
  color: var(--color-muted);
  font-size: var(--fs-lead);
  line-height: 1.5;
}

.contact__form {
  max-width: 680px;
  margin-inline: auto;
  display: grid;
  gap: var(--space-md);
}

.contact__actions {
  margin-top: var(--space-lg);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
}

/* Honeypot — a bot fills this, a person never sees it. */
.contact__trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.contact__status {
  margin-top: var(--space-md);
  text-align: center;
  font-size: var(--fs-sm);
  min-height: 1.5em;
}

.contact__status[data-state='error'] {
  color: #FFB4A6;
}
