/*
  Kinetic Signal: Home (index.html).
  Sections: #signal hero (SM1, SM5, SM3, pointer calm), #manifesto (SM2), #capabilities
  word wall (SM3), #services, #systems, #proof amplitude stack, #record (SM5), #start.
  Shared components and every token come from base.css.
*/

/* ==========================================================================
   Hero #signal: status, name, role, lede and CTAs, the field, the numbers
   ========================================================================== */

/*
  The whole hero, numbers included, fits the first screen from 360x740 and 1280x720 up
  (DESIGN.md Build notes): the field row takes the height that is left (5rem at least), and
  the name and the numbers have height caps (svh). The name cap only acts on screens wider
  than 16:9, so the name still fills the measure on 16:9 and 16:10 laptops.
  On a short screen (760px or less, see the media queries) the text gives height back to the
  field, so the field never becomes a thin strip.
  One minmax(0, 1fr) column: the content never sets the page width (320px screens, 400% zoom).
*/
.hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto auto minmax(5rem, 1fr) auto;
  min-height: 100vh;
  min-height: 100svh;
  padding-top: calc(var(--header-offset) + var(--s-4));
}

.hero-status {
  margin-bottom: var(--s-4);
}

.hero-title {
  padding-inline: var(--margin);
}

/* The name: two block lines at base (sized by SAKR), one nowrap line from 768px. */
.hero-name .fit {
  --fit: 3.77;
}

.hero-name .fit-word {
  font-size: min(clamp(4rem, calc(100cqi / var(--fit)), 16rem), 23svh);
  line-height: 0.82;
}

/* 112% / 700 sits between the name (125 / 900) and the lede (100 / 500): one step each way. */
.hero-role {
  display: block;
  margin-top: var(--s-4);
  font-size: var(--fs-role);
  font-stretch: 112%;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

.hero-act {
  display: grid;
  gap: var(--s-5);
  margin-top: var(--s-6);
  align-items: end;
}

/*
  Side by side from 340px, so the numbers fit in the first screen. Each button grows from its
  own content width, so "Start a project" fits at 360px. Below 340px the buttons stack.
*/
.hero-ctas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
}

.hero-ctas .btn {
  flex: 1 1 auto;
  padding-inline: var(--s-3);
}

/* The field row: its own row, never under text. Poster first, canvas over it. */
.hero-field {
  position: relative;
  margin-top: var(--s-5);
  overflow: hidden;
}

.hero-field-poster,
.hero-field-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.hero-field-poster path {
  fill: var(--blue);
  stroke: var(--paper);
  stroke-width: 1.25;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.hero-field-poster .pf {
  stroke: none;
}

.hero-field-poster .ps {
  fill: none;
}

/* 4px, like the WebGL tracer: the thickest line on screen. */
.hero-field-poster .poster-tracer {
  fill: none;
  stroke: var(--tracer);
  stroke-width: 4;
  stroke-opacity: 1;
}

.hero-field-canvas {
  opacity: 0;
  transition: opacity var(--dur-reveal) linear;
  pointer-events: none;
}

.hero-field.is-live .hero-field-canvas {
  opacity: 1;
}

/* After the 600ms crossfade the poster stops painting (it comes back at once if WebGL is lost). */
.hero-field.is-live .hero-field-poster {
  visibility: hidden;
  transition: visibility 0s linear var(--dur-reveal);
}

/* The numbers: a data strip under a hairline, 4 across at every width. */
.hero-readouts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  padding-inline: var(--margin);
  border-top: 1px solid var(--line);
}

.hero-readouts .readout {
  padding-block: var(--s-3) var(--s-4);
}

.hero-readouts .readout:nth-child(n + 2) {
  padding-inline-start: var(--s-2);
  border-inline-start: 1px solid var(--line);
}

.hero-readouts .readout-value {
  font-size: min(clamp(2.25rem, 1.2rem + 4vw, 3.5rem), 12svh);
}

/* ==========================================================================
   #manifesto: the rule that catches the tracer, and the thesis
   ========================================================================== */

.manifesto {
  padding-top: 0;
}

/* --s-8 below the rule, so the rule and the words share one view while SM2 plays. */
.manifesto-rule {
  height: 3px;
  margin-bottom: var(--s-8);
  background: var(--tracer);
  transform-origin: left center;
}

.manifesto .cols {
  row-gap: var(--s-6);
}

.manifesto-text {
  max-width: var(--measure);
  font-size: var(--fs-lead);
  font-weight: 500;
  line-height: 1.4;
}

/* Every word is an inline block, so SM2 can scatter it without SplitText. */
.manifesto-text .w {
  display: inline-block;
}

.manifesto-text .key {
  color: var(--accent);
  font-style: normal;
  font-weight: 700;
}

.manifesto-note {
  color: var(--fg-2);
}

/* ==========================================================================
   #capabilities: the word wall (shared .wordwall-list from base.css)
   ========================================================================== */

.wordwall {
  padding-bottom: var(--section-y);
}

/* ==========================================================================
   #services: six rows; a blue layer rises on hover and focus
   ========================================================================== */

.service-list {
  border-top: 1px solid var(--line);
}

.service {
  position: relative;
  isolation: isolate;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: var(--gutter);
  row-gap: var(--s-3);
  padding-block: var(--s-6);
  border-bottom: 1px solid var(--line);
}

/* The layer bleeds 1rem past the text column so the text never touches its edge. */
.service::before {
  content: "";
  position: absolute;
  inset: -1px calc(-1 * var(--s-4));
  z-index: -1;
  background: var(--blue);
  transform: scaleY(0);
  transform-origin: center bottom;
  transition: transform var(--dur-micro) var(--ease-lock);
}

.service:focus-within::before {
  transform: scaleY(1);
}

.service > * {
  grid-column: 1;
  transition: color var(--dur-micro) linear;
}

.service:focus-within > * {
  color: var(--paper);
}

.service-index {
  color: var(--fg-2);
}

.service:focus-within .service-index {
  color: var(--mist);
}

.service-title {
  font-size: var(--fs-d2);
}

.service-desc {
  max-width: 44ch;
  color: var(--fg-2);
}

.service:focus-within .service-desc {
  color: var(--mist);
}

/* The arrow is the row's link ("Start a project: <service>"); its hit area is the whole row. */
.service-link {
  grid-column: 2;
  grid-row: 2;
  align-self: center;
  display: inline-grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  margin-inline-end: -0.625rem; /* the arrow, not the 44px box, lines up with the column edge */
}

.service-link::after {
  content: "";
  position: absolute;
  inset: 0;
}

.service:focus-within .service-link:focus-visible {
  outline-color: var(--tracer);
}

.service-arrow {
  width: 1.5rem;
  height: 1.5rem;
  transition: transform var(--dur-micro) var(--ease-lock), color var(--dur-micro) linear;
}

.service:focus-within .service-arrow {
  transform: translateX(6px);
}

/* ==========================================================================
   #systems: three link rows to Work, each with a static signal glyph
   ========================================================================== */

.system-list {
  border-top: 1px solid var(--line);
}

.system {
  position: relative;
  isolation: isolate;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: var(--gutter);
  row-gap: var(--s-4);
  padding-block: var(--s-6) var(--s-7);
  border-bottom: 1px solid var(--line);
}

.system::before {
  content: "";
  position: absolute;
  inset: -1px calc(-1 * var(--s-4));
  z-index: -1;
  background: var(--blue-deep);
  opacity: 0;
  transition: opacity var(--dur-micro) linear;
}

/* The tracer rule sweeps under the row from the left. */
.system::after {
  content: "";
  position: absolute;
  right: 0;
  bottom: -2px;
  left: 0;
  height: 3px;
  background: var(--tracer);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 400ms var(--ease-lock);
}

.system:focus-within::before {
  opacity: 1;
}

.system:focus-within::after {
  transform: scaleX(1);
}

.system > * {
  grid-column: 1 / -1;
}

.system-num {
  color: var(--fg-2);
}

/* Between --fs-d2 and --fs-d1: each title sets in two lines at 1440, so the glyph stays close. */
.system-title {
  font-size: clamp(2.25rem, 1rem + 3.2vw, 4.25rem);
  font-stretch: 125%;
  font-weight: 800;
  line-height: 0.92;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

.system-link {
  display: inline-block;
}

.system-link::after {
  content: "";
  position: absolute;
  inset: 0;
}

.system-glyph {
  width: min(12rem, 60%);
  height: auto;
  overflow: visible;
  fill: none;
  stroke: var(--tracer);
  stroke-width: 3;
  stroke-linecap: square;
  stroke-linejoin: round;
}

.system-tags {
  grid-column: 1;
  color: var(--fg-2);
}

/* A tag never breaks inside itself (Multi-Pair stays whole). */
.system-tags span {
  white-space: nowrap;
}

.system-arrow {
  grid-column: 2;
  align-self: end;
  width: 1.5rem;
  height: 1.5rem;
  transition: transform var(--dur-micro) var(--ease-lock);
}

.system:focus-within .system-arrow {
  transform: translateX(6px);
}

/* ==========================================================================
   #proof: the amplitude stack and the featured review
   ========================================================================== */

.amp-list {
  container-type: inline-size;
  border-top: 1px solid var(--line);
}

.amp {
  display: grid;
  gap: var(--s-3);
  padding-block: clamp(1.25rem, 2.5vw, 2.25rem);
  border-bottom: 1px solid var(--line);
}

/* Size, weight, width, and leading all follow the excerpt length (--chars). */
.amp blockquote {
  max-width: 26em;
  /* The gap grows a little with the quote but stays close, so the text edges line up. */
  padding-inline-start: clamp(0.75rem, 0.25em, 1.5rem);
  border-inline-start: 0.375rem solid var(--accent);
  font-size: clamp(1.25rem, calc(0.75rem + 180cqi / var(--chars)), 6.5rem);
  font-stretch: clamp(100%, calc(131% - var(--chars) * 0.26%), 125%);
  font-weight: clamp(500, calc(1000 - var(--chars) * 4), 850);
  line-height: clamp(1, calc(0.92 + var(--chars) * 0.0028), 1.25);
  letter-spacing: -0.01em;
  text-wrap: pretty;
}

.amp figcaption {
  color: var(--fg-2);
}

.proof-feature {
  row-gap: var(--s-6);
  margin-top: var(--s-8);
}

.proof-quote {
  display: grid;
  gap: var(--s-5);
}

.proof-quote blockquote {
  max-width: 34em;
  font-size: var(--fs-quote);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -0.01em;
  text-wrap: pretty;
}

.proof-quote figcaption {
  color: var(--fg-2);
}

.proof-more {
  font-size: var(--fs-lead);
  font-weight: 600;
}

/* ==========================================================================
   #record: two platform panels, the return rate, the proof line
   ========================================================================== */

.record {
  padding-top: 0;
}

.record-grid {
  row-gap: var(--gutter);
}

/* No side inset: panel content starts on the same edge as the section head and the 60%+ line. */
.record-panel {
  display: grid;
  align-content: start;
  gap: var(--s-6);
  padding-inline: 0;
}

.record-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3) var(--s-5);
}

.record-platform {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-size: clamp(1.5rem, 1.2rem + 1vw, 2.25rem);
  font-stretch: 125%;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.015em;
}

.record-platform .icon {
  width: 0.9em;
  height: 0.9em;
  color: var(--accent);
}

.record-readouts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-6) var(--s-8);
}

.record-link {
  justify-self: start;
}

/* The return rate: one wide readout that reads as a sentence. */
.record-return {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--s-3) var(--s-6);
  padding-block: var(--s-6);
  border-top: 2px solid var(--fg);
}

.record-return .readout-label {
  max-width: 20ch;
  padding-bottom: 0.35em;
  color: var(--fg);
  font-size: var(--fs-lead);
  font-stretch: 100%;
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: 0;
  text-transform: none;
}

.record-proof {
  margin-top: var(--s-6);
  color: var(--fg-2);
}

/* ==========================================================================
   SM1 entrance: CSS only, so it never waits for a deferred script.
   Hidden states follow the base.css rule (motion on, JS on, intro not done). The
   entrance plays once per session: on a return to Home the head snippet adds
   intro-seen, and the hero is already in place when the page slide lands.
   The role and the lede rise without a fade, so they paint at first paint and the
   largest text (the lede) is never held back: LCP equals FCP.
   ========================================================================== */

@keyframes text-rise {
  from { transform: translateY(1rem); }
}

@keyframes tracer-draw {
  to { stroke-dashoffset: 0; }
}

@media (prefers-reduced-motion: no-preference) {
  html.js:not(.motion-off):not(.intro-done):not(.intro-seen) .hero-name .ch {
    animation: char-rise var(--dur-hero) var(--ease-lock) calc(100ms + var(--i, 0) * 80ms) backwards;
  }

  html.js:not(.motion-off):not(.intro-done):not(.intro-seen) .hero-status {
    animation: fade-rise var(--dur-reveal) var(--ease-lock) 100ms backwards;
  }

  html.js:not(.motion-off):not(.intro-done):not(.intro-seen) .hero-role {
    animation: text-rise var(--dur-reveal) var(--ease-lock) 150ms backwards;
  }

  html.js:not(.motion-off):not(.intro-done):not(.intro-seen) .hero-lede {
    animation: text-rise var(--dur-reveal) var(--ease-lock) 250ms backwards;
  }

  html.js:not(.motion-off):not(.intro-done):not(.intro-seen) .hero-ctas .btn {
    animation: fade-rise var(--dur-reveal) var(--ease-lock) 450ms backwards;
  }

  html.js:not(.motion-off):not(.intro-done):not(.intro-seen) .hero-ctas .btn + .btn {
    animation-delay: 530ms;
  }

  html.js:not(.motion-off):not(.intro-done):not(.intro-seen) .hero-readouts .readout-label {
    animation: fade-rise var(--dur-reveal) var(--ease-lock) 1050ms backwards;
  }

  html.js:not(.motion-off):not(.intro-done):not(.intro-seen) .hero-readouts .readout:nth-child(2) .readout-label {
    animation-delay: 1130ms;
  }

  html.js:not(.motion-off):not(.intro-done):not(.intro-seen) .hero-readouts .readout:nth-child(3) .readout-label {
    animation-delay: 1210ms;
  }

  html.js:not(.motion-off):not(.intro-done):not(.intro-seen) .hero-readouts .readout:nth-child(4) .readout-label {
    animation-delay: 1290ms;
  }

  /*
    The poster tracer draws at first paint + 900ms (TRACER_AT_MS in home.js), on the same
    clock as the entrance above, so a late script cannot move it. When the WebGL field is
    live in time, .is-live drops this animation and the field draws the line (home.js).
    home.js adds .is-drawn at the end; without home.js the animation holds the drawn line.
  */
  html.js:not(.motion-off):not(.intro-seen) .poster-tracer:not(.is-drawn) {
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
  }

  html.js:not(.motion-off):not(.intro-seen) .hero-field:not(.is-live) .poster-tracer:not(.is-drawn) {
    animation: tracer-draw var(--dur-draw) var(--ease-sweep) 900ms forwards;
  }
}

/* ==========================================================================
   Media queries
   ========================================================================== */

/* Row hovers only where a pointer can hover, so a tap never leaves a row filled. */
@media (hover: hover) {
  .service:hover::before {
    transform: scaleY(1);
  }

  .service:hover > * {
    color: var(--paper);
  }

  .service:hover .service-index,
  .service:hover .service-desc {
    color: var(--mist);
  }

  .service:hover .service-arrow,
  .system:hover .system-arrow {
    transform: translateX(6px);
  }

  .system:hover::before {
    opacity: 1;
  }

  .system:hover::after {
    transform: scaleX(1);
  }
}

/*
  Short screens (a 1366x768 laptop with browser chrome is about 657px high): the name, the
  numbers, and the gaps step down, and the field row keeps 7.5rem at least.
*/
@media (max-height: 760px) {
  .hero {
    grid-template-rows: auto auto auto minmax(7.5rem, 1fr) auto;
  }

  .hero-name .fit-word {
    font-size: min(clamp(4rem, calc(100cqi / var(--fit)), 16rem), 19svh);
  }

  .hero-act {
    margin-top: var(--s-5);
  }

  .hero-field {
    margin-top: var(--s-3);
  }

  .hero-readouts .readout-value {
    font-size: min(clamp(2.25rem, 1.2rem + 4vw, 3.5rem), 10svh);
  }
}

/*
  Short phones (360x640, 375x667): the name, the role, the lede, and the gaps step down once
  more, so the hero is one screen high and the four numbers stay in the first screen.
*/
@media (max-width: 767px) and (max-height: 720px) {
  .hero {
    grid-template-rows: auto auto auto minmax(5rem, 1fr) auto;
    padding-top: calc(var(--header-offset) + var(--s-2));
  }

  .hero-status {
    margin-bottom: var(--s-3);
  }

  .hero-name .fit-word {
    font-size: min(clamp(4rem, calc(100cqi / var(--fit)), 16rem), 12svh);
  }

  .hero-role {
    margin-top: var(--s-3);
    font-size: 1.125rem;
  }

  .hero-act {
    gap: var(--s-4);
    margin-top: var(--s-4);
  }

  .hero-lede {
    font-size: 1.125rem;
  }

  .hero-field {
    margin-top: var(--s-2);
  }

  .hero-readouts .readout {
    gap: var(--s-2);
    padding-block: var(--s-2) var(--s-3);
  }
}

@media (min-width: 480px) {
  /* Natural width and the standard button padding (base.css .btn). */
  .hero-ctas .btn {
    flex: none;
    padding-inline: 1.75rem;
  }
}

@media (min-width: 768px) {
  .hero-name .fit {
    --fit: 7.13;
    white-space: nowrap;
  }

  .hero-name .fit-word {
    display: inline;
  }

  .hero-readouts .readout {
    padding-block: var(--s-4) var(--s-5);
  }

  .hero-readouts .readout:nth-child(n + 2) {
    padding-inline-start: var(--gutter);
  }

  .hero-readouts .readout-value {
    font-size: min(var(--fs-readout), 12svh);
  }

  .service {
    grid-template-columns: 4rem minmax(0, 1fr) auto;
  }

  .service-index {
    grid-row: 1 / span 2;
    padding-top: 0.6em;
  }

  .service-title,
  .service-desc {
    grid-column: 2;
  }

  .service-link {
    grid-column: 3;
    grid-row: 1 / span 2;
  }

  .system {
    grid-template-columns: 4rem minmax(0, 1fr) auto;
    align-items: start;
  }

  .system-num {
    grid-column: 1;
    grid-row: 1 / span 3;
    padding-top: 0.9em;
  }

  .system-title,
  .system-glyph,
  .system-tags {
    grid-column: 2;
  }

  .system-arrow {
    grid-column: 3;
    grid-row: 1 / span 3;
    align-self: center;
  }

  .record-return .readout-value {
    font-size: clamp(5rem, 2rem + 9vw, 11rem);
  }
}

@media (min-width: 1024px) {
  .hero-act {
    grid-template-columns: repeat(12, minmax(0, 1fr));
    column-gap: var(--gutter);
  }

  .hero-lede {
    grid-column: 1 / 7;
  }

  .hero-ctas {
    grid-column: 7 / -1;
    justify-content: flex-start;
  }

  .manifesto-label {
    grid-column: 1 / 4;
  }

  .manifesto-title {
    grid-column: 1 / 9;
  }

  .manifesto-text,
  .manifesto-note {
    grid-column: 4 / 12;
  }

  .service {
    grid-template-columns: repeat(12, minmax(0, 1fr));
    align-items: center;
    min-height: 7.5rem;
    padding-block: var(--s-5);
  }

  .service-index {
    grid-column: 1;
    grid-row: 1;
    align-self: center;
    padding-top: 0;
  }

  .service-title {
    grid-column: 2 / 8;
    grid-row: 1;
  }

  .service-desc {
    grid-column: 8 / 12;
    grid-row: 1;
  }

  .service-link {
    grid-column: 12;
    grid-row: 1;
    justify-self: end;
  }

  /* Title on the left; on the right the glyph, then the tags and the arrow under it. */
  .system {
    grid-template-columns: repeat(12, minmax(0, 1fr));
    grid-template-rows: auto 1fr;
    align-items: start;
    row-gap: var(--s-5);
    padding-block: var(--s-7);
  }

  .system-num {
    grid-column: 1;
    grid-row: 1;
    padding-top: 0.6em;
  }

  .system-title {
    grid-column: 2 / 9;
    grid-row: 1 / span 2;
  }

  .system-glyph {
    grid-column: 9 / -1;
    grid-row: 1;
    width: 100%;
    max-width: 21rem;
    margin-top: 0.75rem;
  }

  .system-tags {
    grid-column: 9 / 12;
    grid-row: 2;
    align-self: end;
  }

  .system-arrow {
    grid-column: 12;
    grid-row: 2;
    justify-self: end;
    align-self: end;
  }

  .amp {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
    column-gap: var(--s-8);
  }

  .amp figcaption {
    padding-bottom: 0.4em;
    text-align: end;
  }

  .proof-quote {
    grid-column: 2 / 10;
  }

  .proof-more {
    grid-column: 10 / -1;
    align-self: end;
    justify-self: end;
  }

  .record-panel--primary {
    grid-column: 1 / 8;
  }

  .record-panel:not(.record-panel--primary) {
    grid-column: 8 / -1;
  }

  .record-return {
    margin-top: var(--s-6);
  }
}

@media (min-width: 1280px) {
  .hero-ctas {
    grid-column: 8 / -1;
    justify-content: flex-end;
  }
}

@media (min-width: 768px) and (max-height: 760px) {
  .hero-readouts .readout-value {
    font-size: min(var(--fs-readout), 10svh);
  }
}

/* Short and wide: the lede takes 7 columns, so it sets in two lines and the field gets the third. */
@media (min-width: 1024px) and (max-height: 760px) {
  .hero-lede {
    grid-column: 1 / 8;
    max-width: none;
  }

  .hero-ctas {
    grid-column: 8 / -1;
  }
}
