/* sunnysdk.com landing — same "Gallery — porcelain & ultramarine" system as
   the app (apps/web/app/globals.css): porcelain paper + grain, near-black ink,
   ultramarine accent, TikTok Sans body / Monaspace Neon Var details. The
   keynote pass (Sept 2026) adds the deck language: heavy condensed headline,
   Sunny Marker wordmark, a live room embed, a drifting row of request chips,
   and old way / new way rows. The message pass (Sept 27) adds one column per
   step Sunny takes and a closing line; the copy follows THESIS.md. The Sept
   28 pass puts the logo in a top-left nav, brings the headline down from
   keynote scale to three lines, and makes the live room the hero's picture:
   large, off the right edge, turning a little as the page scrolls. */

@font-face {
  font-family: "TikTok Sans";
  src: url("/fonts/TikTokSans-Variable-latin.woff2") format("woff2");
  font-weight: 300 900;
  font-display: swap;
}

@font-face {
  font-family: "Monaspace Neon Var";
  src: url("/fonts/MonaspaceNeon-Variable-latin.woff2") format("woff2");
  font-weight: 200 800;
  font-stretch: 100% 125%;
  font-display: swap;
}

@font-face {
  font-family: "Sunny Marker";
  src: url("/fonts/sunny-marker/webfonts/SunnyMarker-Variable-1.313.woff2") format("woff2");
  font-weight: 300 700;
  font-stretch: 75% 125%;
  font-display: swap;
}

:root {
  --paper: #f1f2f4;
  --paper-2: #e8eaee;
  --card: #fbfcfe;
  --ink: #16181d;
  --ink-soft: #565c66;
  --line: #d4d8de;
  --accent: oklch(0.49 0.16 266);
  --accent-deep: oklch(0.39 0.16 266);
  --sun: #fff0ab;
  --love: oklch(0.58 0.2 22);
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  min-height: 100%;
}

html {
  /* No Tailwind preflight on this page, so nothing otherwise stops iOS from
     inflating body text in landscape. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;

  /* Same variable-axis defaults as the app (globals.css) — `opsz` and `slnt`
     deliberately absent there and here, so `font-optical-sizing: auto` can
     track opsz to each element's size and `font-style` can reach the slant
     axis (an explicit axis here silently overrides the CSS property that
     drives it). */
  font-variation-settings: "wdth" 100;
  font-feature-settings:
    "cv04" 1,
    "zero" 0;
}

body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  /* Flat paper: the room embed paints this exact color, so it sits on the
     page with no visible edge. */
  background-color: var(--paper);
  color: var(--ink);
  font-family: "TikTok Sans", system-ui, sans-serif;
  text-align: center;
  overflow-x: hidden;
}

main {
  flex: 1;
}

/* ---------- Nav ---------- */

/* The logo sits top left, like every site's: Bright and the wordmark, on
   the same gutter as the hero copy. */
.site-nav,
.hero {
  --gutter: 1.25rem;
}

.site-nav {
  --bright: 46px;
  display: flex;
  align-items: center;
  height: var(--nav-h, 4.5rem);
  padding: 0 var(--gutter);
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.15rem;
  /* The corona is wider than the orb: pull the orb back onto the gutter. */
  margin-left: calc(var(--bright) * -0.27);
  color: var(--ink);
  text-decoration: none;
  border-radius: 999px;
}

.brand:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.site-links {
  margin-inline-start: auto;
  font-size: 0.875rem;
  font-weight: 500;
}

.site-links a,
.foot a {
  color: var(--ink-soft);
  text-decoration: none;
  text-underline-offset: 0.2em;
}

.site-links a:hover,
.foot a:hover {
  color: var(--accent);
  text-decoration: underline;
}

.site-links a:focus-visible,
.foot a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: 2px;
}

/* ---------- Hero ---------- */

/* One stage, two actors: the headline and the live room. Narrow screens
   stack them; from 900px they share one row. */
.hero {
  --pad-top: clamp(0.5rem, 2vh, 1.25rem);
  /* Headline size: one phrase to a line on a phone. */
  --hl: min(9.6vw, 2.5rem);
  position: relative;
  text-align: left;
  /* The room runs past the screen's edge; cut it there without making the
     page scroll sideways (clip, unlike hidden, adds no scroll container). */
  overflow-x: clip;
}

.hero-row {
  padding: var(--pad-top) var(--gutter) 0;
}

.hero-copy {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.bright {
  width: var(--bright);
  aspect-ratio: 1;
  animation: floaty 5.2s ease-in-out infinite;
}

.bright svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.wordmark {
  font-family: "Sunny Marker", "TikTok Sans", cursive;
  font-weight: 400;
  font-size: calc(var(--bright) * 0.5);
  line-height: 1;
}

@keyframes floaty {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-2px);
  }
}

/* Ray pulse: phase-offset opacity breathing per star. */
.ray {
  animation: ray-pulse 6s ease-in-out infinite;
}
.ray-b {
  animation-duration: 7.4s;
  animation-delay: -2.2s;
}
.ray-c {
  animation-duration: 8.8s;
  animation-delay: -4.4s;
}
@keyframes ray-pulse {
  0%,
  100% {
    opacity: 0.85;
  }
  50% {
    opacity: 0.55;
  }
}

.eye {
  transition: transform 120ms ease-out;
  transform-box: fill-box;
  transform-origin: center;
}

.headline,
.beats h2,
.shift h2,
.close h2 {
  margin: 0;
  font-weight: 900;
  font-variation-settings:
    "wdth" 85,
    "opsz" 36;
  font-optical-sizing: none;
  letter-spacing: -0.02em;
  line-height: 0.95;
  text-wrap: balance;
}

/* The headline only breaks between its phrases, and its measure sets the
   lines: "Sunny turns your catalog / into furnished rooms, / clean orders,
   faster quotes." on a wide screen (the last is the widest, 11.1em), one
   phrase a line on a phone. */
.headline {
  max-width: 11.3em;
  font-size: var(--hl);
  line-height: 1;
  letter-spacing: -0.02em;
}

.headline .line {
  display: inline-block;
  white-space: nowrap;
}

.lede {
  max-width: 34rem;
  margin: clamp(1rem, 2.4vh, 1.4rem) 0 0;
  font-size: clamp(1.05rem, 0.95rem + 0.3vw, 1.2rem);
  line-height: 1.42;
  color: var(--ink-soft);
  text-wrap: balance;
}

/* ---------- Request chips: two rows drifting in opposite directions ---------- */

.asks {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin: clamp(3rem, 9vh, 5rem) 0 0;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 10%, #000 90%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 10%, #000 90%, transparent 100%);
}

.asks .eyebrow {
  margin-bottom: 0.25rem;
}

.track {
  display: flex;
  width: max-content;
  animation: drift 70s linear infinite;
}

.track.reverse {
  animation-direction: reverse;
  animation-duration: 78s;
}

.asks:hover .track {
  animation-play-state: paused;
}

.set {
  display: flex;
  gap: 0.75rem;
  margin: 0;
  padding: 0 0.75rem 0 0;
  list-style: none;
}

/* Things a visitor might say, not controls: one quiet outline, no fills, so
   the signup button stays the only filled shape that reads as clickable. */
.chip {
  padding: 0.75em 1.35em;
  font-size: clamp(0.95rem, 1.6vw, 1.15rem);
  font-weight: 500;
  line-height: 1.1;
  white-space: nowrap;
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 999px;
}

@keyframes drift {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

/* ---------- Email capture ---------- */

.capture {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  gap: 0.6rem;
  width: 100%;
  max-width: 30rem;
  margin: clamp(1.25rem, 3.4vh, 2rem) 0 0;
}

.capture input {
  flex: 1 1 13rem;
  padding: 0.95rem 1.2rem;
  font: inherit;
  font-size: 1.05rem;
  color: var(--ink);
  background: rgba(251, 252, 254, 0.88);
  border: 1px solid var(--line);
  border-radius: 999px;
  outline: none;
  transition: border-color 150ms ease;
}

.capture input:focus {
  border-color: var(--accent);
}

.capture input::placeholder {
  color: var(--ink-soft);
}

.capture button {
  padding: 0.95rem 1.65rem;
  font: inherit;
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--paper);
  background: var(--accent);
  border: none;
  border-radius: 999px;
  cursor: pointer;
  transition: background-color 150ms ease;
}

.capture button:hover {
  background: var(--accent-deep);
}

.capture button:focus-visible,
.capture input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.capture button:disabled {
  opacity: 0.6;
  cursor: default;
}

.form-note {
  flex-basis: 100%;
  min-height: 1.2em;
  margin: 0.35rem 0 0;
  font-family: "Monaspace Neon Var", ui-monospace, monospace;
  font-size: 0.8rem;
  color: var(--ink-soft);
}

.form-note.error {
  color: var(--love);
}

/* ---------- Live room ---------- */

/* The room is the hero's picture: on a phone it runs past both edges of the
   screen, and from 900px it takes everything right of the copy and runs off
   the right edge. */
.room {
  position: relative;
  z-index: 1;
  margin: 1rem calc(-1 * var(--gutter) - 8vw) 0;
}

/* No card: the stage is a clipping box on the page's own paper, its edges
   faded out so the embed's floor glow never ends in a hard line. The frame
   and poster run 72px past the bottom so the embed's reset button (its only
   chrome under ?controls=min) is clipped away (#2593 adds a real switch). */
.room-stage {
  --fade:
    linear-gradient(to right, transparent, #000 12%, #000 88%, transparent),
    linear-gradient(to bottom, transparent, #000 10%, #000 86%, transparent);
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  -webkit-mask-image: var(--fade);
  -webkit-mask-composite: source-in;
  mask-image: var(--fade);
  mask-composite: intersect;
}

.room-poster,
.room-frame {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: calc(100% + 72px);
}

.room-cover {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* The poster sits over the frame and fades away once the embed reports its
   models have loaded (main.js). The frame loads underneath it, visible: a
   hidden cross-origin frame is paused by Chrome and never finishes loading,
   so the poster would stay up for good. */
.room-poster {
  z-index: 1;
  object-fit: cover;
  transition:
    opacity 700ms ease,
    visibility 0s linear 700ms;
}

.room-frame {
  border: 0;
}

.room.loaded .room-poster {
  opacity: 0;
  visibility: hidden;
}

/* Until someone clicks in, the cover takes the pointer so the wheel and
   touch scrolling stay with the page instead of zooming the room. */
.room-cover {
  padding: 0;
  background: transparent;
  border: 0;
  cursor: grab;
}

.room-cover:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.room.live .room-cover {
  display: none;
}

@media (max-width: 600px) {
  .room-stage {
    aspect-ratio: 1;
  }
}

@media (max-width: 479px) {
  /* The button drops under the field; let it take the whole row. */
  .capture button {
    flex: 1 1 100%;
  }
}

/* Side by side. The copy column is as wide as the headline's widest line;
   the room takes the rest of the row and runs off the right edge, as tall
   as the first screen allows under the nav. The headline tops out at
   3.5rem. */
@media (min-width: 900px) {
  .site-nav,
  .hero {
    --gutter: max(clamp(1.5rem, 5.5vw, 5rem), calc((100% - 100rem) / 2));
  }
  .site-nav {
    --bright: 52px;
    --nav-h: 5rem;
  }
  .hero {
    --hl: max(2.5rem, min(3.1vw, 3.5rem));
  }
  .hero-row {
    display: grid;
    /* 29.5rem keeps the eyebrow on one line when the headline is small. */
    grid-template-columns: var(--gutter) max(29.5rem, calc(var(--hl) * 11.3)) minmax(0, 1fr);
    align-items: center;
    padding: var(--pad-top) 0 0;
  }
  .hero-copy {
    grid-column: 2;
    grid-row: 1;
  }
  /* Wider than its column, so it runs off the right edge; a little under
     the copy on the left, where the stage's fade makes it disappear. The
     height cap keeps the whole room on the first screen. */
  .room {
    grid-column: 3;
    grid-row: 1;
    justify-self: start;
    width: min(100% + 16vw, (100vh - 5rem - var(--pad-top)) * 1.2);
    margin: 0 0 0 -2rem;
  }
  .room-stage {
    aspect-ratio: 6 / 5;
  }
}

/* ---------- What Sunny does: one column per step ---------- */

.beats {
  max-width: 76rem;
  margin: clamp(3.5rem, 10vh, 6rem) auto 0;
  padding: 0 1.5rem;
}

.beats h2,
.close h2 {
  font-size: clamp(2.2rem, 5vw, 3.75rem);
}

.beat-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11.5rem, 1fr));
  gap: 2rem 1.75rem;
  margin: clamp(1.75rem, 4vh, 2.75rem) 0 0;
  padding: 0;
  list-style: none;
  text-align: left;
}

.beat {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding-top: 1rem;
  border-top: 2px solid var(--ink);
}

.beat h3 {
  margin: 0;
  font-size: clamp(1.2rem, 1.8vw, 1.4rem);
  font-weight: 800;
  font-variation-settings: "wdth" 90;
  letter-spacing: -0.01em;
  line-height: 1.1;
}

.beat p {
  margin: 0.5rem 0 0;
  font-size: 1.02rem;
  line-height: 1.4;
  color: var(--ink-soft);
}

.beat .tag {
  margin-top: 0.75rem;
}

/* ---------- Old way / new way ---------- */

.shift {
  max-width: 64rem;
  margin: clamp(4rem, 12vh, 7rem) auto 0;
  padding: 0 1.5rem;
}

.eyebrow {
  margin: 0 0 0.75rem;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
}

.shift h2 {
  font-size: clamp(2.5rem, 6vw, 4.75rem);
}

.shift-rows {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin: clamp(1.75rem, 4vh, 2.5rem) 0 0;
  padding: 0;
  list-style: none;
  text-align: left;
}

.shift-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1.15fr) 7.5rem;
  align-items: center;
  gap: 1.25rem;
  padding: 1rem 1.5rem;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 1.25rem;
}

.old {
  font-size: 1.05rem;
  line-height: 1.35;
  color: var(--ink-soft);
}

.arrow {
  color: #9aa0b4;
  font-size: 1.2rem;
}

.new {
  font-size: 1.1rem;
  font-weight: 700;
  line-height: 1.35;
}

/* A status label, not a pill: a dot and a word, so it can't pass for a button. */
.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  justify-self: end;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
}

.tag::before {
  content: "";
  width: 0.55em;
  height: 0.55em;
  border-radius: 50%;
}

.tag.live {
  color: var(--accent);
}

.tag.live::before {
  background: var(--accent);
}

/* Pilot: half-filled dot, between live and coming next. */
.tag.pilot {
  color: var(--ink);
}

.tag.pilot::before {
  background: linear-gradient(90deg, var(--ink) 50%, transparent 50%);
  border: 1.5px solid var(--ink);
}

.tag.next {
  color: var(--ink-soft);
}

.tag.next::before {
  border: 1.5px solid var(--ink-soft);
}

@media (max-width: 640px) {
  .shift-row {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "old tag"
      "new new";
    gap: 0.35rem 0.75rem;
    padding: 0.9rem 1.1rem;
  }
  .old {
    grid-area: old;
    font-size: 0.95rem;
  }
  .new {
    grid-area: new;
  }
  .tag {
    grid-area: tag;
  }
  .arrow {
    display: none;
  }
}

/* ---------- Closing line ---------- */

.close {
  max-width: 44rem;
  margin: clamp(4rem, 12vh, 7rem) auto 0;
  padding: 0 1.5rem;
}

/* A real button (it takes the reader back to the signup form), so it gets
   the same filled accent as the form's own button. */
.close-cta {
  margin-top: clamp(1.25rem, 3vh, 1.75rem);
  padding: 0.8rem 1.4rem;
  font: inherit;
  font-size: 1rem;
  font-weight: 700;
  color: var(--paper);
  background: var(--accent);
  border: none;
  border-radius: 999px;
  cursor: pointer;
  transition: background-color 150ms ease;
}

.close-cta:hover {
  background: var(--accent-deep);
}

.close-cta:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---------- Hero arrival: once, on first paint ----------
   Bright rises, the headline builds line by line, and the room settles in
   beside it. Transforms and opacity only (no layout shift); fills release
   at the end. */

@media (prefers-reduced-motion: no-preference) {
  :root {
    --arrive: cubic-bezier(0.22, 1, 0.36, 1);
  }
  /* Bright comes up like the sun it is; its idle float carries on after. */
  .bright {
    animation:
      floaty 5.2s ease-in-out infinite,
      sun-up 1s var(--arrive) backwards;
  }
  .wordmark {
    animation: rise 0.7s var(--arrive) 0.1s backwards;
  }
  .hero .eyebrow {
    animation: rise 0.7s var(--arrive) 0.08s backwards;
  }
  .headline .line {
    animation: rise-line 0.7s var(--arrive) backwards;
  }
  .headline .line:nth-child(1) {
    animation-delay: 0.12s;
  }
  .headline .line:nth-child(2) {
    animation-delay: 0.18s;
  }
  .headline .line:nth-child(3) {
    animation-delay: 0.24s;
  }
  .headline .line:nth-child(4) {
    animation-delay: 0.3s;
  }
  .headline .line:nth-child(5) {
    animation-delay: 0.36s;
  }
  .lede {
    animation: rise 0.7s var(--arrive) 0.4s backwards;
  }
  .capture {
    animation: rise 0.7s var(--arrive) 0.48s backwards;
  }
  /* The poster is what shows here. Chrome ignores opacity on a
     cross-origin frame inside a masked parent, so the frame stays hidden
     until the room has arrived, then loads under the poster. */
  .room {
    animation: room-in 1s var(--arrive) 0.22s backwards;
  }
  .room-frame {
    animation: frame-on 0s linear 1.25s backwards;
  }
}

@keyframes sun-up {
  from {
    opacity: 0;
    translate: 0 40%;
    scale: 0.72;
  }
}

@keyframes rise {
  from {
    opacity: 0;
    translate: 0 14px;
  }
}

@keyframes rise-line {
  from {
    opacity: 0;
    translate: 0 0.28em;
  }
}

@keyframes frame-on {
  from {
    visibility: hidden;
  }
}

@keyframes room-in {
  from {
    opacity: 0;
    translate: 0 36px;
    scale: 0.965;
  }
}

/* ---------- Footer / utilities ---------- */

.foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem 2rem;
  margin: 2rem 1.25rem 0;
  padding: 1.5rem 0;
  border-top: 1px solid var(--line);
  font-size: 0.8125rem;
  color: var(--ink-soft);
  text-align: left;
}

.foot nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.25rem;
}

@media (min-width: 900px) {
  .foot {
    margin-inline: max(clamp(1.5rem, 5.5vw, 5rem), calc((100% - 100rem) / 2));
  }
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .bright,
  .ray,
  .track {
    animation: none;
  }
  /* Still chips: wrap the first set in place and drop the loop copy. */
  .asks {
    -webkit-mask-image: none;
    mask-image: none;
    padding: 0 1.5rem;
  }
  .track {
    width: auto;
    justify-content: center;
  }
  .set {
    flex-wrap: wrap;
    justify-content: center;
    padding: 0;
  }
  .set[aria-hidden="true"] {
    display: none;
  }
}
