:root {
  /* Light only: the site deliberately ignores the visitor's dark-mode
     setting. color-scheme keeps browser-drawn UI (scrollbars, form
     controls) light too. */
  color-scheme: light;
  --bg: #ffffff;
  --text: #000000;
  --muted: #6b6b6b;
  --button-bg: #222222;
  --button-text: #ffffff;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
}

body {
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; }

h1, h2, h3, h4, p { text-wrap: balance; }

.wrap {
  max-width: 1160px;
  margin: 0 auto;
  padding: 0 24px;
}

/* Hero */
.hero {
  /* 80vh tall with the headline centered inside it, so the first pinned
     card (100vh) starts right after at the 80vh mark — its top 20vh sits
     inside the initial viewport as a peek before any scrolling. */
  height: 80vh;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.hero h1 {
  margin: 0 auto;
  max-width: 720px;
  /* Fixed 60px down to 700px; below that it scales proportionally down to a
     32px floor at a 320px viewport (linear interpolation via clamp()).
     Lowered from a 42px floor so the headline caps at 4 lines instead of 5
     on common phone widths — padding alone couldn't get there (verified
     empirically: even near-zero side padding only shaved one line off). */
  font-size: clamp(32px, 7.368vw + 8.42px, 60px);
  font-weight: 500;
  line-height: 1.13;
  letter-spacing: -0.02em;
  opacity: 0;
  transform: translateY(22px);
  animation: heroFadeUp 0.9s cubic-bezier(0.16, 1, 0.3, 1) 0.1s forwards;
}

@keyframes heroFadeUp {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero h1 {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

/* Contact section */
.contact {
  padding: 120px 0 200px;
  text-align: center;
  background: var(--bg);
}

.contact h2 {
  margin: 0 0 40px;
  /* Same fixed-then-shrink curve as the hero h1: 60px down to 700px, then
     scales proportionally down to a 42px floor at a 320px viewport. */
  font-size: clamp(42px, 4.737vw + 26.84px, 60px);
  font-weight: 500;
  line-height: 1.13;
  letter-spacing: -0.02em;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  padding: 14px 24px;
  background: var(--button-bg);
  color: var(--button-text);
  border-radius: 100px;
  font-size: 16px;
  font-weight: 500;
  white-space: nowrap;
  text-decoration: none;
  line-height: 20px;
  border: none;
  cursor: pointer;
  transition: transform 0.15s ease, opacity 0.15s ease;
}

.btn:hover { opacity: 0.85; }
.btn:focus-visible { outline: 2px solid var(--text); outline-offset: 3px; }
.btn:active { transform: scale(0.98); }

footer {
  padding: 32px 0 48px;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}

@media (max-width: 640px) {
  .contact { padding: 70px 0 120px; }
}

/* ============================================
   Case study — sticky pinned card stack
   All selectors below are prefixed "cs-" so they
   never collide with the rest of the page's styles.
   ============================================ */

.cs-wrap {
  background: transparent;
  color: #000000;
}

.cs-section {
  width: 100%;
  height: 100vh;
  position: relative;
  overflow: visible;
  background: transparent;
}

.cs-card.cs-scroll {
  position: relative;
}

.cs-device {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate3d(-50%, -50%, 0) rotate(0deg);
  width: min(1000px, 90vw, calc((100vh - 120px) * 1000 / 960));
  aspect-ratio: 1000 / 960;
  border-radius: 36px;
  overflow: hidden;
  background: #ffffff;
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.18);
  will-change: transform;
}

/* Hidden until the page-load entrance animation (js/main.js) takes over;
   the class is set by an inline script in <head> and cleared by main.js,
   or by a timeout there if main.js never runs. */
.js-enter .cs-first .cs-device {
  opacity: 0;
}

/* Card content (the video, or the phone frame + video) fades in once all of
   the card's media has loaded, instead of popping in over the empty white
   card. Hidden only while .js-media is on <html>, which an inline script in
   <head> sets and removes after a failsafe timeout; main.js adds
   .is-media-ready per card. */
.cs-device > * {
  transition: opacity 0.7s ease;
}

.js-media .cs-device:not(.is-media-ready) > * {
  opacity: 0;
}

/* Card 1 — Today tab screen recording */
.cs-device--today {
  display: flex;
  align-items: center;
  justify-content: center;
}

.cs-today-video {
  height: 100%;
  width: auto;
  max-width: none;
}

/* Card 3 — Setup flow screen recording */
.cs-device--setup {
  display: flex;
  align-items: center;
  justify-content: center;
}

.cs-setup-video {
  /* This recording's phone mockup sits small within a much wider frame
     (unlike the other card videos), so scale up beyond 100% to bring the
     phone itself to a comparable size instead of filling by frame height. */
  width: 250%;
  height: auto;
  max-width: none;
}

/* Card 4 — Host actions: full-bleed grid/collage recording, no single
   centered phone mockup, so it just covers the whole card like a background. */
.cs-hostactions-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Card 5 — AirKey guest unlock instructions */
.cs-device--airkey {
  display: flex;
  align-items: center;
  justify-content: center;
}

.cs-airkey-video {
  height: 100%;
  width: auto;
  max-width: none;
}

/* Card 6 — Tips enter/exit thumbnail view (from Figma: Cecilia personal,
   node 2055:17857). A phone-frame photo with the recording clipped into the
   screen area, plus a static "global nav" export layered on top at the
   bottom to cover the (incorrect) nav bar baked into the recording itself. */
.cs-device--tips {
  display: flex;
  align-items: center;
  justify-content: center;
}

.cs-tips-phone {
  position: relative;
  width: 39.6%;
  aspect-ratio: 1716 / 3536;
  filter: drop-shadow(0 20px 40px rgba(0, 0, 0, 0.16));
}

.cs-tips-frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.cs-tips-screen {
  position: absolute;
  left: 4.03%;
  top: 1.72%;
  width: 91.61%;
  height: 96.61%;
  /* A single border-radius percentage resolves horizontal radius against
     width and vertical radius against height independently, which on this
     non-square box (~0.46 aspect) produced an elliptical corner instead of
     the phone's true circular one. The two-value slash syntax lets us pick
     a height-percentage that resolves to the same pixel radius as the
     width-percentage, restoring a circular corner. */
  border-radius: 13.84% / 6.37%;
  overflow: hidden;
}

.cs-tips-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.cs-tips-nav {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: auto;
  display: block;
}

/* Card 2 — Smart Lock connect screen (from Figma: Cecilia personal, node 2055:14659) */
.cs-device--lock {
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.cs-lock-phone {
  position: relative;
  width: 52.7%;
  aspect-ratio: 1049 / 1610;
  filter: drop-shadow(0 20px 40px rgba(0, 0, 0, 0.16));
}

.cs-lock-base {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

.cs-lock-video {
  position: absolute;
  left: 50%;
  top: 43.7%;
  transform: translate(-50%, -50%);
  width: 42%;
  aspect-ratio: 380 / 540;
  object-fit: cover;
}

/* Mobile / reduced motion: stack normally, no pinning, no tilt */
@media (max-width: 900px) {
  /* Sections hug their card (card + the 60px padding below) instead of each
     being a full screen tall. With a shorter card in a full-screen section, the
     blank space between cards was ~48% of the screen vs ~16% on desktop, so
     the next card took far too long to arrive and the overlap effect was lost. */
  .cs-section { height: auto; }
  .cs-device { position: relative; top: auto; left: auto; transform: none; margin: 0 auto; width: min(1000px, 85vw); aspect-ratio: 3 / 4; }
  .cs-card { display: flex; align-items: center; justify-content: center; padding: 60px 0; }
  /* Both phone mockups read too small against the taller mobile card, so
     they're bumped up on this breakpoint only: lock to ~133% of its desktop
     size (52.7% -> 70.15%, after two successive 10% increases from 57.97%),
     tips to 120% (39.6% -> 47.52%). */
  .cs-lock-phone { width: 70.15%; }
  /* Shadows on all the stacked cards compound into a heavy smudge, so
     js/main.js adds .no-shadow to every card but the two newest. */
  .cs-device { transition: box-shadow 0.5s ease; }
  .cs-device.no-shadow { box-shadow: none; }
  .cs-tips-phone { width: 47.52%; }
}

/* Contact "expandable screen": the trigger button morphs into a full-page
   panel. Geometry (top/left/width/height and the four corner radii) is
   animated via inline styles set from JS (a FLIP-style rect tween), not by
   this CSS — these rules just declare the panel's at-rest appearance
   before JS takes over on open. When open, the panel stops 100px short of
   the viewport's top edge (so the page peeks through above it) but stays
   flush with the bottom, hence rounded top corners and square bottom ones. */
body.contact-open { overflow: hidden; }

.wrap,
.cs-wrap,
footer {
  transition: filter 0.4s ease;
}

body.contact-open > .wrap,
body.contact-open > .cs-wrap,
body.contact-open > footer {
  filter: blur(20px);
  pointer-events: none;
}

.contact-trigger { position: relative; transition: opacity 0.25s ease; }

.contact-panel {
  /* top/left/width/height are tweened directly by Motion (js/main.js) for
     real spring physics. The four border-*-radius longhands are handled by
     this CSS transition instead — Motion 11.18.2 has a bug where animating
     border-top-left-radius / border-top-right-radius from a non-zero
     starting value silently no-ops (confirmed in isolation; unrelated to
     any other property in the same call), which is what caused the old
     "jumps to a rectangle, then snaps round" artifact on close. A native
     CSS transition doesn't have that bug and, with a back-out easing
     curve, reads as spring-like anyway. */
  position: fixed;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  z-index: 1000;
  overflow-y: auto;
  background: #171717;
  color: #ffffff;
  transition: border-top-left-radius 0.5s cubic-bezier(0.34, 1.56, 0.64, 1),
    border-top-right-radius 0.5s cubic-bezier(0.34, 1.56, 0.64, 1),
    border-bottom-left-radius 0.5s cubic-bezier(0.34, 1.56, 0.64, 1),
    border-bottom-right-radius 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
  visibility: hidden;
}

.contact-panel-close {
  position: absolute;
  top: 20px;
  right: 20px;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: none;
  border-radius: 100px;
  background: rgba(255, 255, 255, 0.12);
  color: #ffffff;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, background 0.15s ease;
}

.contact-panel-close:hover { background: rgba(255, 255, 255, 0.22); }

.contact-panel.is-open .contact-panel-close {
  opacity: 1;
  pointer-events: auto;
}

.contact-panel-inner {
  /* Centers the form vertically within the sheet: min-height matches the
     panel's own height (set inline from JS), so the flex column centers
     when content fits and still scrolls normally if it doesn't. */
  min-height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Three width tiers: 60% above 1200px, 80% from 641-1200px, then
     full-width again once it's as narrow as the mobile card stack gets
     (reusing the site's existing 640px breakpoint there). */
  width: 60%;
  margin: 0 auto;
  padding: 40px 24px;
  opacity: 0;
  transition: opacity 0.25s ease;
}

@media (max-width: 1200px) {
  .contact-panel-inner {
    width: 80%;
  }
}

@media (max-width: 640px) {
  .contact-panel-inner {
    width: 100%;
  }
}

.contact-panel.is-open .contact-panel-inner {
  opacity: 1;
  transition-delay: 0.15s;
}

.contact-form-error {
  margin: 0;
  padding: 16px 0 0;
  color: #ff3b30;
  font-size: 16px;
  line-height: 1.5;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Fields: no boxed background — large text sitting on a hairline underline,
   matching the Figma reference (node 2088:20224 / 2088:23939). The ghost
   placeholder and the typed value are just the input's native placeholder
   vs. its own text color; no JS needed for that swap. The underline is its
   own element (rather than a border on .contact-field) so the error message
   can sit below it, matching node 2088:25236. */
.contact-field {
  padding: 16px 0;
}

.contact-field-line {
  height: 1px;
  background: rgba(255, 255, 255, 0.16);
}

.contact-field-error {
  margin: 8px 0 0;
  color: #ff3b30;
  font-size: 14px;
  font-weight: 500;
}

.contact-form input,
.contact-form textarea {
  display: block;
  width: 100%;
  padding: 0;
  border: none;
  background: none;
  color: #ffffff;
  font: inherit;
  font-weight: 500;
  /* Same fixed-then-shrink curve as the headlines: fixed 48px down to
     700px, then scales proportionally down to a 34px floor at a 320px
     viewport (same 0.7 floor/max ratio). */
  font-size: clamp(34px, 3.684vw + 22.21px, 48px);
  letter-spacing: -0.02em;
  line-height: 1.15;
  resize: none;
  overflow: hidden;
}

.contact-form textarea {
  /* A fixed rest gap between the text and the underline below it, instead
     of the line sitting flush against the text. min-height (not JS) is
     what actually holds the line at rest here — a pure-CSS floor means it's
     correct from the very first paint, with no dependency on when/at-what-
     width a JS measurement happens to run. 1.15em matches the line-height
     above, so this is "one line + the 64px gap," staying responsive to the
     fluid font-size. box-sizing:border-box means the padding is already
     included in scrollHeight, so autoGrowMessage() (used only to grow the
     field as typed text wraps) naturally only grows past this floor once
     wrapped content actually needs more room. */
  min-height: calc(1.15em + 64px);
  padding-bottom: 64px;
}

.contact-form input::placeholder,
.contact-form textarea::placeholder {
  color: #6b6b6b;
}

.contact-form input:focus,
.contact-form textarea:focus {
  outline: none;
}

.contact-form-submit {
  width: 100%;
  margin-top: 32px;
  gap: 8px;
  --button-bg: #ffffff;
  --button-text: #171717;
  /* Sticks to the bottom of .contact-panel's scrollport (its nearest
     scrolling ancestor) once a long message grows the form taller than the
     viewport, instead of getting pushed down and off-screen with it. */
  position: sticky;
  bottom: 24px;
}

.contact-form-submit-text {
  transition: opacity 0.16s ease;
}

/* Text fades out, the label swaps underneath (in JS), then fades back in —
   this class only covers the "out" half of that cross-fade. */
.contact-form-submit.is-changing .contact-form-submit-text {
  opacity: 0;
}

.contact-form-submit-check {
  width: 0;
  opacity: 0;
  transform: scale(0.4);
  flex-shrink: 0;
  overflow: hidden;
  transition: width 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
    opacity 0.25s ease,
    transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.contact-form-submit.is-success .contact-form-submit-check {
  width: 16px;
  opacity: 1;
  transform: scale(1);
}

.contact-form-submit:disabled {
  cursor: default;
}

.contact-form-submit:disabled:hover {
  opacity: 1;
}
