/* Finharbor for an investor. The site is only a stage: graphite and
   paper, one typeface, two sizes on a screen. Colour, glass and motion
   come from the product inside it. */

:root {
  --graphite: #202123;
  --app-ground: #131415;
  --app-ground-light: #f1f3f2;
  --ink: #f2f2ef;
  --ink-quiet: rgb(242 242 239 / 0.58);
  --ink-dark: #141516;
  --ink-dark-quiet: rgb(20 21 22 / 0.6);
  --m: clamp(16px, 4vw, 72px);
  --title: clamp(56px, min(6.2vw, 9.2svh), 120px);
  --reply: clamp(44px, min(4.5vw, 7.2svh), 96px);
  --note: 16px;
  /* What Safari's bars take when they show: the stage is as tall as the
     screen without them, the words stay above them. */
  --bars: calc(100lvh - 100svh);
  --foot: calc(var(--m) + var(--bars));
  --ease: cubic-bezier(0.25, 0.1, 0.25, 1);
  color-scheme: dark;
}

* { box-sizing: border-box; }

html, body { margin: 0; background: var(--graphite); color: var(--ink); }

body {
  font-family: 'Geist', system-ui, sans-serif;
  font-size: var(--note);
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
}

h1, h2, p, ol { margin: 0; padding: 0; }
ol { list-style: none; }

/* A scene: a tall section whose stage stays on screen while the page
   scrolls through it. */
.scene { position: relative; }
.stage {
  position: sticky;
  top: 0;
  height: 100lvh;
  overflow: hidden;
}

/* The two sizes. */
.title {
  font-size: var(--title);
  font-weight: 600;
  line-height: 0.94;
  letter-spacing: -0.045em;
  text-wrap: balance;
}
.note {
  max-width: 23em;
  color: var(--ink-quiet);
}
.note b { color: var(--ink); font-weight: 600; }

/* ── 0 · The opening mosaic ─────────────────────────────────────────── */

.hero { height: 190svh; }
.hero .ground-hero {
  opacity: 1;
  background:
    radial-gradient(60% 80% at 100% 0%, #009e72d9, transparent 70%),
    radial-gradient(60% 70% at 0% 100%, #00402cff, transparent 70%),
    #1b2f28;
}
.hero-words {
  position: absolute;
  left: var(--m);
  top: var(--m);
  bottom: var(--foot);
  width: 30vw;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  z-index: 1;
  opacity: var(--o, 1);
}
.hero-words .title { font-size: var(--title); }
.mosaic {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--m) + 30vw);
  display: grid;
  grid-template-columns: 30vw 12.5vw 30vw 12.5vw;
  gap: 1.6vw;
  align-items: start;
}
.mcol {
  display: flex;
  flex-direction: column;
  gap: 1.6vw;
  transform: translate3d(0, var(--ty, 0px), 0);
  will-change: transform;
}
.mcol img { display: block; width: 100%; height: auto; border-radius: 1.2vw; }
.mcol-p img { border-radius: 1.6vw; }

/* ── 1–3 · The stage ────────────────────────────────────────────────── */

.story { height: 490svh; }

/* Each scene's ground: the app's own hero light (the roles it makes for
   the scene's hue, at glow 1.3), spread over the whole screen. Scenes
   take turns between dark and light (Igor, 22 October). */
.ground { position: absolute; inset: 0; opacity: var(--o, 0); will-change: opacity; }
.ground-1 {
  opacity: var(--o, 1);
  background:
    radial-gradient(60% 80% at 100% 0%, #009e72d9, transparent 70%),
    radial-gradient(60% 70% at 0% 100%, #00402cff, transparent 70%),
    #1b2f28;
}
.ground-2 {
  background:
    radial-gradient(60% 80% at 100% 0%, #0094bbd9, transparent 70%),
    radial-gradient(60% 70% at 0% 100%, #00304fff, transparent 70%),
    #192a3d;
}
.ground-3 {
  background:
    radial-gradient(60% 80% at 100% 0%, #9ebdffcc, transparent 70%),
    radial-gradient(60% 70% at 0% 100%, #3bf3b577, transparent 70%),
    #eaefed;
}

/* Words on a light scene. */
.on-light { color: var(--ink-dark); }
.on-light .note { color: var(--ink-dark-quiet); }

/* Each scene's words; the scroll sets their --o. */
.words { position: absolute; opacity: var(--o, 0); will-change: opacity; }

.words-inside {
  inset: var(--m) var(--m) var(--foot);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-start;
}
.words-inside .title span { display: block; }

/* Beside the window, their foot on the window's foot. */
.words-side {
  left: var(--side-x, 66vw);
  right: var(--m);
  bottom: var(--side-bottom, var(--foot));
}
.words-side .title { font-size: var(--reply); }

/* Under the windows, at the bottom left, clear of the other device. */
.words-low {
  left: var(--m);
  right: var(--low-right, var(--m));
  bottom: var(--foot);
}

/* Line breaks that hold on one kind of screen only. */
.on-phone { display: none; }

/* A window: the app's own size, moved and scaled by the scroll. */
.window {
  position: absolute;
  inset: 0 auto auto 0;
  width: var(--lw, 1600px);
  height: var(--lh, 1000px);
  transform-origin: 0 0;
  transform: translate3d(var(--x, 0px), var(--y, 0px), 0) scale(var(--s, 1));
  border-radius: var(--r, 0px);
  overflow: hidden;
  isolation: isolate;
  background: var(--app-ground);
  will-change: transform;
}
.window-other { visibility: var(--shown, hidden); background: var(--app-ground-light); }

.poster,
.app,
.camera {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
.poster { object-fit: cover; object-position: top; }
.app { opacity: 0; transition: opacity 400ms var(--ease); }
.app.is-ready { opacity: 1; }

/* A recording, and the camera that frames a part of it. */
/* Recordings fade in over what was there; what they cover goes after. */
.camera {
  transform-origin: 0 0;
  transform: scale(var(--cz, 1)) translate(var(--cx, 0px), var(--cy, 0px));
  opacity: 0;
  visibility: hidden;
  transition: opacity 320ms var(--ease), visibility 0s 320ms;
}
.camera.is-on {
  opacity: 1;
  visibility: visible;
  transition: opacity 320ms var(--ease), visibility 0s;
}
.clip { display: block; width: 100%; height: 100%; object-fit: cover; }
.window.is-clip .app,
.window.is-clip .poster { visibility: hidden; transition: visibility 0s 320ms; }

/* ── 4–8 · Scenes of their own ──────────────────────────────────────── */

.cards { height: 230svh; }
.exchange { height: 240svh; }
.phones { height: 280svh; }
.brand { height: 420svh; }
.admin { height: 470svh; }

.ground-cards,
.ground-exchange,
.ground-phones,
.ground-brand,
.ground-admin { opacity: 1; }

/* The same hero light the app makes for each scene's hue (glow 1.3). */
.ground-cards {
  background:
    radial-gradient(60% 80% at 100% 0%, #9b6dccd9, transparent 70%),
    radial-gradient(60% 70% at 0% 100%, #00402cff, transparent 70%),
    #1b2f28;
}
.ground-exchange {
  background:
    radial-gradient(60% 80% at 100% 0%, #3ecfffb3, transparent 70%),
    radial-gradient(60% 70% at 0% 100%, #3bf3b566, transparent 70%),
    #eaefed;
}
.ground-phones {
  background:
    radial-gradient(60% 80% at 100% 0%, #c15e9ad9, transparent 70%),
    radial-gradient(60% 70% at 0% 100%, #00402cff, transparent 70%),
    #1b2f28;
}
.ground-admin {
  background:
    radial-gradient(60% 80% at 100% 0%, #d0a9ffb3, transparent 70%),
    radial-gradient(60% 70% at 0% 100%, #3bf3b55c, transparent 70%),
    #eaefed;
}
/* The brand scene's light follows the colour picked; the roles are read
   from the live app and fade from one to the next. */
@property --b-glow { syntax: '<color>'; inherits: false; initial-value: #72941b; }
@property --b-deep { syntax: '<color>'; inherits: false; initial-value: #00402c; }
@property --b-wash { syntax: '<color>'; inherits: false; initial-value: #1a2923; }
.ground-brand {
  background:
    radial-gradient(60% 80% at 100% 0%, var(--b-glow), transparent 70%),
    radial-gradient(60% 70% at 0% 100%, var(--b-deep), transparent 70%),
    var(--b-wash);
  transition: --b-glow 700ms var(--ease), --b-deep 700ms var(--ease), --b-wash 700ms var(--ease);
}

/* 4 · the cards screen, built piece by piece in its own pixels. */
.cards-title { position: absolute; left: var(--m); top: var(--m); }
.page {
  position: absolute;
  inset: 0 auto auto 0;
  width: var(--lw, 1600px);
  height: var(--lh, 1000px);
  transform-origin: 0 0;
  transform: translate3d(var(--x, 0px), var(--y, 0px), 0) scale(var(--s, 1));
}
.page-empty,
.page-full {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: var(--r, 0px);
}
.page-empty { opacity: var(--eo, 0); transform: scale(var(--es, 1)); }
.page-full { opacity: var(--fo, 0); }
.page { contain: layout paint; will-change: transform; }
.piece {
  position: absolute;
  left: var(--px, 0px);
  top: var(--py, 0px);
  width: var(--pw, 100px);
  height: var(--ph, 100px);
  opacity: var(--po, 1);
  transform: translate3d(var(--dx, 0px), var(--dy, 0px), 0);
  will-change: transform, opacity;
}
.piece-card {
  transform-origin: 50% 50%;
  transform: perspective(1800px) translate3d(var(--dx, 0px), var(--dy, 0px), 0) scale(var(--cs, 1)) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
}

/* 5 · exchange */
.exchange-title { position: absolute; left: var(--m); top: var(--m); }
.window-light { background: var(--app-ground-light); }

/* 6 · phones */
.phones-title {
  position: absolute;
  left: var(--m);
  top: 50%;
  width: 36vw;
  transform: translateY(-50%);
}
.phones-cols {
  position: absolute;
  top: 0;
  right: var(--m);
  width: 54vw;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2vw;
}
.col {
  display: flex;
  flex-direction: column;
  gap: 2vw;
  transform: translate3d(0, var(--ty, 0px), 0);
  will-change: transform;
}
.col img { display: block; width: 100%; height: auto; border-radius: 2vw; }

/* 6½ · the desk: the section names as a column of titles, the window
   opening the one that is lit. */
.desk { height: 460svh; }
.ground-desk {
  opacity: 1;
  background:
    radial-gradient(60% 80% at 100% 0%, #a4d139b3, transparent 70%),
    radial-gradient(60% 70% at 0% 100%, #3bf3b577, transparent 70%),
    #eaefec;
}
.desk-words {
  position: absolute;
  left: var(--m);
  top: var(--m);
  display: flex;
  flex-direction: column;
  gap: 28px;
}
.desk-index { display: flex; flex-direction: column; gap: 0.06em; }
.desk-index li {
  /* Six names in one column: as large as a title, but never taller than
     the screen or wider than a phone. */
  font-size: min(var(--title), 5.2vw, calc((100svh - 2 * var(--m) - 120px) / 6.4));
  white-space: nowrap;
  color: var(--ink-dark);
  opacity: 0.18;
  transition: opacity 360ms var(--ease);
}
.desk-index li[aria-current='true'] { opacity: 1; }
.window-desk { box-shadow: 0 1px 2px rgb(20 21 22 / 0.08), 0 30px 80px -30px rgb(20 21 22 / 0.3); }
.desk-screen {
  position: absolute;
  inset: 0;
  display: block;
  opacity: 0;
  transform: translate3d(0, 2.5%, 0);
  transition: opacity 420ms var(--ease), transform 520ms var(--ease);
}
.desk-screen.is-on { opacity: 1; transform: none; }
.desk-screen img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: top; }

/* 7 · brand */
.brand-foot {
  position: absolute;
  left: var(--m);
  right: var(--m);
  bottom: var(--foot);
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 32px;
}
.brand-title { font-size: var(--reply); }
.swatches { display: flex; flex-wrap: nowrap; gap: 14px; padding: 6px; }
.swatches button {
  width: 36px;
  height: 36px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--seed);
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.2);
  cursor: pointer;
  transition: transform 200ms var(--ease);
}
.swatches button:hover { transform: scale(1.12); }
.swatches [aria-checked='true'] { outline: 2px solid var(--ink); outline-offset: 4px; }

/* 8 · the back office: sheets on a table, so they cast a little shadow. */
.admin-title { position: absolute; left: var(--m); right: var(--m); top: var(--m); }
.sheets {
  position: absolute;
  left: var(--sx, 0px);
  top: var(--sy, 0px);
  width: var(--sw, 960px);
  height: var(--sh, 600px);
}
.sheet {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: 20px;
  background: var(--app-ground-light);
  box-shadow: 0 1px 2px rgb(20 21 22 / 0.08), 0 24px 60px -24px rgb(20 21 22 / 0.28);
  transform-origin: 50% 0;
  transform: translate3d(var(--tx, 0px), var(--ty, 0px), 0) scale(var(--ts, 1));
  opacity: var(--to, 1);
}
.sheet picture { display: block; width: 100%; height: 100%; }
.sheet img { display: block; width: 100%; height: 100%; object-fit: cover; }
.sheet .camera { opacity: 1; visibility: visible; }

/* 9 · the film and the way in */
.final {
  position: relative;
  min-height: 100svh;
  padding: calc(var(--m) * 1.5) var(--m) var(--m);
  background:
    radial-gradient(60% 50% at 100% 0%, #009e72a6, transparent 70%),
    #1b2f28;
}
.final-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 24px;
  margin-bottom: 40px;
}
.open {
  display: inline-flex;
  align-items: center;
  height: 56px;
  padding: 0 28px;
  border-radius: 64px;
  background: var(--ink);
  color: var(--ink-dark);
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  transition: transform 200ms var(--ease);
}
.open:hover { transform: translateY(-2px); }
.film { display: block; width: 100%; max-height: calc(100svh - var(--m) * 2.5 - 96px); object-fit: contain; border-radius: 24px; background: var(--app-ground); }

/* 10 · thanks, and the author's name. */
.thanks {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding: var(--m);
  overflow: hidden;
  background:
    radial-gradient(60% 80% at 100% 0%, #9b6dccd9, transparent 70%),
    radial-gradient(60% 70% at 0% 100%, #00402cff, transparent 70%),
    #1b2f28;
}
.thanks-words { display: flex; flex-direction: column; gap: 0.3em; }
.sign { color: var(--ink-quiet); }
/* The name in the brands' colours, the ones the app is shown in. */
.sign .name {
  background: linear-gradient(90deg, #3bd39a, #2f9bff 30%, #8b5cf6 55%, #ec4899 78%, #f97316);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  text-decoration: none;
}


/* The page rests on each step of a scene: a gentle stop near it. */
/* One gesture, one step: the page stops at every step of a scene, however
   hard it is flung (snap-stop always). */
/* (Stops are markers only: the wheel settles on them, touch flows.) */
.snap {
  position: absolute;
  left: 0;
  top: var(--top, 0px);
  width: 1px;
  height: 1px;
  pointer-events: none;
}


/* Phones: the phone app, and the page always scrolls. */
@media (max-width: 767px) {
  .desk-index li { font-size: 11.5vw; }
  .words-inside { justify-content: flex-end; gap: 16px; }
  .words-inside .note { order: 2; }
  .title { font-size: 13vw; line-height: 0.95; text-wrap: wrap; }
  .words-side { left: var(--m); }
  .words-side .title { font-size: 13vw; }
  .on-phone { display: inline; }
  .hero-words { width: auto; right: var(--m); bottom: auto; gap: 16px; }
  .hero-words .title { font-size: 15vw; }
  .mosaic { left: var(--m); top: 0; grid-template-columns: repeat(2, 46vw); gap: 12px; }
  .mosaic .mcol-d { display: none; }
  .mcol { gap: 12px; }
  .mcol img, .mcol-p img { border-radius: 18px; }
  .off-phone { display: none; }
  .cards-title { top: auto; bottom: var(--foot); }
  .exchange-title { top: var(--m); }
  /* Phones: a ribbon to swipe, the page scrolls on. */
  .phones { height: auto; }
  .phones .stage { position: relative; height: auto; padding: calc(var(--m) * 3) 0; }
  .phones-title { position: relative; left: auto; top: auto; width: auto; transform: none; padding: 0 var(--m) 32px; }
  .phones-cols {
    position: relative;
    right: auto;
    width: auto;
    display: flex;
    gap: 16px;
    padding: 0 var(--m);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
  }
  .phones-cols::-webkit-scrollbar { display: none; }
  .col { display: contents; }
  .col img { flex: 0 0 64vw; width: 64vw; border-radius: 24px; scroll-snap-align: center; }
  .brand-foot { flex-direction: column; align-items: flex-start; gap: 20px; }
  .brand-title { font-size: 13vw; }
  .swatches { gap: 12px; }
  .final-head { flex-direction: column; align-items: flex-start; }
  .film { border-radius: 16px; }
}
@media (hover: none), (max-width: 767px) {
  .app { pointer-events: none; }
  .for-pointer { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .app { transition: none; }
}
