/* TealKey — Bamboo Teal theme (shadcn/ui, by Serafim), hand-translated to plain CSS */

:root {
  --background: oklch(0.181 0.008 181.919);
  --foreground: oklch(0.941 0.003 197.102);
  --card: oklch(0.216 0.009 184.301);
  --card-foreground: oklch(0.941 0.003 197.102);
  --popover: oklch(0.216 0.009 184.301);
  --popover-foreground: oklch(0.941 0.003 197.102);
  --primary: oklch(0.69 0.061 188.984);
  --primary-foreground: oklch(0 0 0);
  --secondary: oklch(0.285 0.017 183.826);
  --secondary-foreground: oklch(0.941 0.003 197.102);
  --muted: oklch(0.254 0.016 189.311);
  --muted-foreground: oklch(0.708 0.02 192.636);
  --accent: oklch(0.462 0.024 184.671);
  --accent-foreground: oklch(0.941 0.003 197.102);
  --destructive: oklch(0.718 0.149 15.897);
  --destructive-foreground: oklch(0.181 0.008 181.919);
  --border: oklch(0.339 0.02 191.201);
  --input: oklch(0.216 0.009 184.301);
  --ring: oklch(0.69 0.061 188.984);

  --radius: 0.625rem;
  --radius-sm: calc(var(--radius) - 4px);
  --radius-lg: calc(var(--radius) + 4px);
  --radius-xl: calc(var(--radius) + 8px);

  --font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;

  --glow-accent: #2dd4bf;
  --celebrate-accent: #f5b942;

  --shadow-color: #000000;
  --container: 1120px;

  /* Horizontal gutter. In landscape the Dynamic Island insets only ONE side,
     so applying each env() to its own edge leaves the layout visibly lopsided
     (~59px one side, 24px the other) and still lets content collide with the
     island in the opposite rotation. Taking the larger of the two and using it
     on both edges keeps everything symmetric and clear of the island either
     way round. */
  --gutter: max(24px, env(safe-area-inset-left), env(safe-area-inset-right));

  /* Height of the sticky header. Sections use it for scroll-margin so anchor
     jumps don't park headings underneath it, and the hero subtracts it so
     header + hero equals exactly one viewport. Kept in sync with the header's
     own sizing in the short-viewport block below. */
  --header-h: 79px;
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--background);
  color: var(--foreground);
  font-family: var(--font-sans);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

img {
  max-width: 100%;
  display: block;
}

a {
  color: inherit;
}

.container {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 24px;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

/* Nav */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in oklab, var(--background) 85%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
}

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 16px;
  padding-bottom: 16px;
}

.brand {
  display: flex;
  align-items: center;
  text-decoration: none;
  color: var(--foreground);
}

.brand-logo {
  height: 46px;
  width: auto;
}

.nav-links {
  position: relative;
  display: flex;
  gap: 28px;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Merged with the nav CTA, which points at the same #contact target. Kept in
   the markup rather than deleted because that CTA is display:none under 640px
   (see the mobile block), so the dropdown would otherwise lose its only contact
   entry point. Shown again there. */
.nav-item-contact {
  display: none;
}

.nav-links li.nav-pill {
  position: absolute;
  top: -6px;
  bottom: -6px;
  left: 0;
  width: 0;
  border-radius: 999px;
  background: color-mix(in oklab, var(--primary) 16%, transparent);
  border: 1px solid color-mix(in oklab, var(--primary) 30%, transparent);
  opacity: 0;
  pointer-events: none;
  z-index: 0;
  transition: left 0.3s cubic-bezier(0.4, 0, 0.2, 1), width 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease;
}

.nav-links li {
  position: relative;
  z-index: 1;
}

.nav-links a {
  position: relative;
  text-decoration: none;
  color: var(--muted-foreground);
  font-size: 0.95rem;
  transition: color 0.15s ease;
}

.nav-links a:hover,
.nav-links a.is-active {
  color: var(--foreground);
}

@media (prefers-reduced-motion: reduce) {
  .nav-pill {
    transition: opacity 0.2s ease;
  }
}

.nav-cta {
  display: flex;
  align-items: center;
  gap: 12px;
}

.nav-toggle {
  display: none;
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--foreground);
  padding: 8px 10px;
  cursor: pointer;
}

/* Buttons */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border-radius: var(--radius-sm);
  padding: 10px 20px;
  font-size: 0.95rem;
  font-weight: 600;
  text-decoration: none;
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}

.btn:hover {
  transform: translateY(-1px);
}

.btn-primary {
  background: var(--primary);
  color: var(--primary-foreground);
  box-shadow: 0 0 0 0 color-mix(in oklab, var(--primary) 60%, transparent);
}

.btn-primary:hover {
  box-shadow: 0 6px 24px -6px color-mix(in oklab, var(--primary) 70%, transparent);
}

.btn-outline {
  background: transparent;
  border-color: var(--border);
  color: var(--foreground);
}

.btn-outline:hover {
  border-color: var(--primary);
  color: var(--primary);
}

/* Hero */

.hero {
  padding: clamp(48px, 9vh, 140px) 0 clamp(40px, 7vh, 110px);
  /* The sticky header sits in normal flow, so a full 100vh hero would push
     its own centred content below the fold. Subtract the header so
     header + hero == exactly one screen. svh (not dvh) keeps it stable while
     mobile browser chrome hides/shows during scroll. */
  min-height: calc(100vh - var(--header-h));
  min-height: calc(100svh - var(--header-h));
  display: flex;
  align-items: center;
  position: relative;
  overflow: hidden;
  background: #050707;
  z-index: 0;
}

.hero-sparkles {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  z-index: 0;
  /* Keep the animating canvas on its own compositing layer so .hero itself
     doesn't get promoted — see the iOS repaint note in main.js. */
  transform: translateZ(0);
}

.hero-inner {
  position: relative;
  z-index: 1;
  max-width: 720px;
}

.hero-glow-line {
  position: relative;
  height: 2px;
  width: 100%;
  max-width: 340px;
  margin: 8px 0 28px;
  background: var(--glow-accent);
  box-shadow:
    0 0 10px 2px color-mix(in oklab, var(--glow-accent) 95%, transparent),
    0 0 36px 6px color-mix(in oklab, var(--glow-accent) 75%, transparent),
    0 0 90px 16px color-mix(in oklab, var(--glow-accent) 45%, transparent);
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.85rem;
  color: var(--primary);
  background: color-mix(in oklab, var(--primary) 14%, transparent);
  border: 1px solid color-mix(in oklab, var(--primary) 30%, transparent);
  padding: 6px 14px;
  border-radius: 999px;
  margin-bottom: 24px;
}

.hero h1 {
  position: relative;
  font-size: clamp(2.4rem, 5vw, 3.4rem);
  line-height: 1.1;
  margin: 0 0 20px;
  letter-spacing: -0.02em;
}

/* Fallback / reduced-motion / no-JS appearance: plain gradient text. The JS
   path-drawing effect swaps this out at runtime by adding .is-drawing. */
.hero h1 .accent-text {
  background: linear-gradient(90deg, var(--glow-accent), color-mix(in oklab, var(--glow-accent) 45%, white));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Path-drawing accent text — each letter draws itself as a gradient stroke,
   then fills. See the matching block in main.js for why the outline length is
   estimated rather than measured. The real text is still here and still
   selectable; it is only made invisible (transparent colour, no gradient to
   paint it) so the SVG overlay can stand in for it. */
/* Hidden via opacity (not by removing the gradient) so the real text can be
   cross-faded back in at the end. SVG and HTML rasterise glyphs slightly
   differently — different antialiasing and hinting — so swapping one for the
   other in a single frame reads as the text twitching a pixel or shifting
   weight, even though it is pixel-identical in position. Fading them past each
   other hides that difference; there is nothing to align, since both are drawn
   at the same measured coordinates. */
.hero h1 .accent-text.is-drawing {
  /* Hidden by removing the gradient that paints it, NOT by opacity. An opacity
     version of this was tried and is unsafe: restoring it depends on a CSS
     transition actually running, and in a hidden or backgrounded view
     transitions can sit frozen at their start value — which leaves the headline
     permanently invisible. Dropping the class here restores the text in the same
     frame, with no animation in the path. */
  background: none;
}

/* The swap is INSTANT — no fade. A 260ms overlay fade was tried and made things
   worse: the real text sits at full opacity underneath the whole time, so while
   the overlay fades the two copies composite together and the headline is
   visibly denser, thinning out as the overlay disappears. That density ramp is
   exactly the "changes colour then settles" being chased. With the two
   renderings matched (below) there is nothing left to hide, so they are simply
   swapped in one frame. */

/* THE FIX for the swap being perceptible at all: `body` sets
   `-webkit-font-smoothing: antialiased`, which thins HTML glyph stems. SVG text
   does not pick that up, so the overlay rendered BOLDER than the real text —
   read as a colour/weight change, and as the ink contracting up-and-left when
   the heavier stems were replaced by the lighter ones. Matching the smoothing
   (and pinning text-rendering so hinting cannot differ either) makes the two
   rasterise the same. Applied to both the stash glyphs and the <use> clones,
   since the clone inherits from the <use> element, not from the original. */
.draw-defs,
.draw-defs text,
.draw-overlay,
.draw-glyph {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: geometricPrecision;
}

/* Stop colours are declared here rather than computed in JS. Reading them back
   out of computed style does not work: `color-mix(in oklab, ...)` serialises as
   `oklab(0.903 -0.0596 -0.00197)`, and parsing those 0-1 floats as 0-255 RGB
   turned the light end of the ramp nearly black. Letting the browser resolve
   them as real stop-colors sidesteps colour parsing entirely. */
.draw-fill-start {
  stop-color: var(--glow-accent);
}

.draw-fill-end {
  stop-color: color-mix(in oklab, var(--glow-accent) 45%, white);
}

/* One absolutely-positioned overlay covering the whole <h1>. Each glyph inside
   is placed at coordinates measured off the real text, so the overlay is not
   laid out in flow at all and cannot affect the headline's metrics. overflow is
   visible so strokes and descenders are not clipped to the h1 box. */
.draw-overlay {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}

.draw-defs {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
}

/* Finished state, applied directly when the overlay is rebuilt after a resize
   so the intro is not replayed. */
.draw-glyph.is-drawn {
  animation: none;
  stroke-dashoffset: 0;
  stroke-opacity: 0;
  fill-opacity: 1;
}

.draw-glyph {
  fill: var(--glow-accent); /* overridden per character in main.js to match the gradient */
  fill-opacity: 0;
  stroke: color-mix(in oklab, var(--glow-accent) 70%, white);
  stroke-dasharray: var(--len);
  stroke-dashoffset: var(--len);
  animation: accent-draw 1150ms cubic-bezier(0.33, 0.9, 0.35, 1) var(--d, 0ms) forwards;
}


@keyframes accent-draw {
  0% {
    stroke-dashoffset: var(--len);
    fill-opacity: 0;
    stroke-opacity: 1;
  }
  65% {
    stroke-dashoffset: 0;
    fill-opacity: 0;
    stroke-opacity: 1;
  }
  100% {
    stroke-dashoffset: 0;
    fill-opacity: 1;
    stroke-opacity: 0;
  }
}

.hero p {
  font-size: 1.15rem;
  color: var(--muted-foreground);
  margin: 0 0 36px;
  max-width: 560px;
}

.hero-actions {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
}

/* Section shell */

section {
  /* Scales with viewport height so short screens (landscape phones) don't
     spend a quarter of the screen on padding. */
  padding: clamp(40px, 7vh, 88px) 0;
  border-top: 1px solid var(--border);
}

section[id],
main[id] {
  scroll-margin-top: var(--header-h);
}

.section-head {
  max-width: 620px;
  margin: 0 0 48px;
}

.section-head .eyebrow {
  margin-bottom: 16px;
}

.section-head h2 {
  font-size: clamp(1.8rem, 3vw, 2.4rem);
  margin: 0 0 12px;
  letter-spacing: -0.01em;
}

.section-head p {
  color: var(--muted-foreground);
  font-size: 1.05rem;
  margin: 0;
}

/* Cards / grid */

.grid {
  display: grid;
  gap: 24px;
  align-items: start;
}

.grid-3 {
  grid-template-columns: repeat(3, 1fr);
}

/* Glowing Shadow — a thin rotating, color-cycling gradient border ring,
   only active while hovered (or tapped/focused) — off by default, and only
   ever lit on the one rectangle being interacted with. Add class="glow-border"
   to any position:relative rectangle. */

.glow-border {
  isolation: isolate;
}

.glow-border::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  padding: 1.5px;
  background: conic-gradient(
    from 0deg,
    transparent 0deg,
    var(--glow-accent) 70deg,
    transparent 150deg,
    var(--celebrate-accent) 260deg,
    transparent 340deg,
    var(--glow-accent) 360deg
  );
  -webkit-mask: linear-gradient(#000 0 0) padding-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: 0;
  animation: glow-border-spin 4s linear infinite;
  animation-play-state: paused;
  transition: opacity 0.25s ease;
  pointer-events: none;
}

.glow-border::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  box-shadow: 0 0 22px 1px color-mix(in oklab, var(--glow-accent) 55%, transparent);
  opacity: 0;
  transition: opacity 0.25s ease;
  pointer-events: none;
}

.glow-border:hover::before,
.glow-border:focus-within::before,
.glow-border:active::before,
.glow-border.is-touched::before {
  opacity: 1;
  animation-play-state: running;
}

.glow-border:hover::after,
.glow-border:focus-within::after,
.glow-border:active::after,
.glow-border.is-touched::after {
  opacity: 0.6;
}

@keyframes glow-border-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .glow-border::before {
    animation: none;
  }
}

.card {
  position: relative;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 28px;
  transition: border-color 0.2s ease, transform 0.2s ease;
}

.card:hover {
  border-color: color-mix(in oklab, var(--primary) 50%, var(--border));
  transform: translateY(-3px);
}

.card-icon {
  width: 44px;
  height: 44px;
  border-radius: var(--radius-sm);
  background: color-mix(in oklab, var(--primary) 16%, transparent);
  color: var(--primary);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 20px;
  font-size: 1.2rem;
}

.card h3 {
  margin: 0 0 10px;
  font-size: 1.1rem;
}

.card p {
  margin: 0;
  color: var(--muted-foreground);
  font-size: 0.96rem;
}

/* Process steps */

.steps {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 24px;
  align-items: start;
  counter-reset: step;
}

.step {
  position: relative;
  padding: 28px 20px 20px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--card);
}

/* The step number lives on a REAL element, not on .step::before. It used to be
   a pseudo-element, which silently collided with .glow-border::before — .step
   carries both classes, and ::before is one element, so the two rule sets
   merged. .step::before won the conflicting properties (width/height/
   border-radius/background) while .glow-border::before's position:absolute,
   inset:0, opacity:0 and its xor mask all survived. The result: the number was
   never visible at all (masked out and opacity 0), and the card's hover/tap glow
   ring rendered as a stray 32px circle pinned to the top-left corner instead of
   tracing the card's border. Do not move this back onto ::before or ::after —
   glow-border uses both. */
.step {
  counter-increment: step;
}

.step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 999px;
  background: var(--primary);
  color: var(--primary-foreground);
  font-weight: 700;
  font-size: 0.9rem;
  margin-bottom: 16px;
}

.step-num::before {
  content: counter(step);
}

.step h3 {
  margin: 0 0 8px;
  font-size: 1.02rem;
}

.step p {
  margin: 0;
  color: var(--muted-foreground);
  font-size: 0.92rem;
}

/* About */

/* Heading gets its own row in column 1, so the bullet list in column 2 lines
   up with the body copy rather than sitting up level with the heading. */
.about-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 48px;
  row-gap: 20px;
  align-items: start;
}

.about-heading {
  grid-column: 1;
  grid-row: 1;
  margin: 0;
  font-size: clamp(1.8rem, 3vw, 2.4rem);
  letter-spacing: -0.01em;
}

.about-copy {
  grid-column: 1;
  grid-row: 2;
}

.about-grid .pill-list {
  grid-column: 2;
  grid-row: 2;
}

.about-copy p:first-child {
  margin-top: 0;
}

.about-grid p {
  color: var(--muted-foreground);
  font-size: 1.02rem;
}

.pill-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.pill-list li {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  color: var(--foreground);
  font-size: 0.96rem;
}

.pill-list li::before {
  content: "";
  width: 8px;
  height: 8px;
  margin-top: 7px;
  border-radius: 999px;
  background: var(--primary);
  flex: none;
}

/* Case studies placeholder */

.placeholder-card {
  border: 1px dashed var(--border);
  border-radius: var(--radius-lg);
  padding: 48px;
  text-align: center;
  color: var(--muted-foreground);
  background: color-mix(in oklab, var(--card) 60%, transparent);
}

/* Founder / about-me */

.founder-grid {
  display: grid;
  grid-template-columns: minmax(0, 300px) minmax(0, 1fr);
  gap: clamp(28px, 5vw, 56px);
  align-items: center;
}

.founder-photo-wrap {
  position: relative;
}

/* Soft teal bloom behind the photo so it reads as lit by the same light source
   as the rest of the page rather than pasted on. Sits at z-index 0 with the
   image at 1 instead of using a negative z-index, which would escape into
   .container's stacking context. */
.founder-photo-wrap::before {
  content: "";
  position: absolute;
  inset: -12%;
  z-index: 0;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    color-mix(in oklab, var(--glow-accent) 26%, transparent),
    transparent 72%
  );
  filter: blur(16px);
  pointer-events: none;
}

.founder-photo {
  position: relative;
  z-index: 1;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--radius-xl);
  border: 1px solid color-mix(in oklab, var(--glow-accent) 30%, var(--border));
  box-shadow:
    0 24px 50px -22px rgba(0, 0, 0, 0.75),
    0 0 40px -18px color-mix(in oklab, var(--glow-accent) 60%, transparent);
}

.founder-copy h2 {
  margin: 0 0 18px;
  font-size: clamp(1.7rem, 3vw, 2.3rem);
  letter-spacing: -0.01em;
}

.founder-copy p {
  margin: 0 0 16px;
  color: var(--muted-foreground);
  font-size: 1.02rem;
}

.founder-copy p:last-of-type {
  margin-bottom: 0;
}

/* Needs the extra class in the selector to outrank `.founder-copy p` above,
   which would otherwise repaint the eyebrow muted-grey at body size. */
.founder-copy .founder-eyebrow {
  margin: 0 0 10px;
  color: var(--primary);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.founder-sign {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 24px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

/* mb-signature.png is pre-processed by tools/make-signature.py into light ink
   on real transparency, so no filter or blend mode is needed here. Doing the
   knockout in CSS (invert + mix-blend-mode: screen) was tried first and does
   not work: `.container` is positioned with a z-index, which creates a stacking
   context, and that isolates blending — the signature blends against a
   transparent backdrop instead of the page and keeps a visible black box. See
   that script's docstring before reaching for a CSS-only approach again. */
.founder-signature {
  width: auto;
  max-width: 230px;
  height: auto;
  margin-bottom: 4px;
  opacity: 0.95;
}

.founder-role {
  color: var(--muted-foreground);
  font-size: 0.9rem;
}

/* Contact */

#contact {
  position: relative;
  overflow: hidden;
}

.contact-flow-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  z-index: 0;
  opacity: 0.8;
}

.contact-card {
  position: relative;
  z-index: 1;
  background: var(--card);
  border: 1px solid color-mix(in oklab, var(--celebrate-accent) 35%, var(--border));
  border-radius: var(--radius-xl);
  padding: 56px;
  box-shadow: 0 0 60px -20px color-mix(in oklab, var(--celebrate-accent) 45%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .contact-flow-canvas {
    display: none;
  }
}

.contact-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 48px;
  align-items: start;
  text-align: left;
}

.contact-card h2 {
  margin: 0 0 12px;
  font-size: clamp(1.6rem, 3vw, 2.1rem);
}

.contact-card p {
  color: var(--muted-foreground);
  margin: 0 0 32px;
  font-size: 1.05rem;
}

.contact-actions {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
}

/* Lead form — added for visual review; not wired to a backend yet, so
   submitting just swaps the form for a static "thanks" message. */

.lead-form-wrap {
  position: relative;
}

.lead-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.lead-form .form-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.lead-form label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted-foreground);
}

.lead-form input,
.lead-form textarea {
  width: 100%;
  background: var(--input);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  font-family: inherit;
  font-size: 0.95rem;
  color: var(--foreground);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.lead-form input::placeholder,
.lead-form textarea::placeholder {
  color: color-mix(in oklab, var(--muted-foreground) 75%, transparent);
}

.lead-form input:focus,
.lead-form textarea:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--primary) 25%, transparent);
}

.lead-form textarea {
  resize: vertical;
  min-height: 84px;
}

.lead-form-submit {
  align-self: flex-start;
  margin-top: 4px;
}

.lead-form-note {
  margin: 0;
  font-size: 0.82rem;
  color: var(--muted-foreground);
}

.lead-form-success {
  padding: 32px 0;
}

/* Footer */

.site-footer {
  position: relative;
  overflow: hidden;
  border-top: 1px solid var(--border);
  padding: 96px 0 32px;
  background: #050707;
  --glow-intensity: 0.25;
}

.footer-glow {
  position: absolute;
  inset: 0;
  transform-origin: bottom center;
  background:
    radial-gradient(
      ellipse clamp(280px, 42vw, 440px) clamp(60px, 9vw, 100px) at 50% 100%,
      color-mix(in oklab, var(--glow-accent) calc(var(--glow-intensity) * 150%), transparent),
      transparent 72%
    ),
    radial-gradient(
      ellipse clamp(420px, 60vw, 620px) clamp(170px, 26vw, 250px) at 50% 132%,
      color-mix(in oklab, var(--glow-accent) calc(var(--glow-intensity) * 70%), transparent),
      transparent 68%
    );
  opacity: calc(0.4 + var(--glow-intensity) * 0.6);
  pointer-events: none;
  z-index: 0;
  animation: footer-glow-breathe 6s ease-in-out infinite;
}

@keyframes footer-glow-breathe {
  0%, 100% { transform: scaleY(1); }
  50% { transform: scaleY(1.12); }
}

.footer-key-wrap {
  position: relative;
  z-index: 1;
  display: flex;
  justify-content: center;
  perspective: 700px;
  margin-bottom: 28px;
}

.footer-key-bob {
  animation: footer-key-bob 2.6s ease-in-out infinite;
  transform-style: preserve-3d;
  will-change: transform;
}

.footer-key-3d {
  position: relative;
  width: 34px;
  height: 60px;
  transform-style: preserve-3d;
  animation: footer-key-spin 2.8s linear infinite;
  filter: drop-shadow(0 0 14px color-mix(in oklab, var(--glow-accent) 70%, transparent));
  will-change: transform;
}

.footer-key-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform: translateZ(var(--depth));
}

@keyframes footer-key-bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-10px); }
}

@keyframes footer-key-spin {
  from { transform: rotateY(0deg); }
  to { transform: rotateY(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .footer-key-bob,
  .footer-key-3d {
    animation: none;
  }
  .footer-glow {
    animation: none;
  }
}

.footer-inner {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 16px;
  color: var(--muted-foreground);
  font-size: 0.88rem;
}

.footer-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--foreground);
  font-weight: 600;
}

.footer-mark {
  height: 16px;
  width: auto;
  opacity: 0.85;
}

/* Name / phone / location block. Real static markup rather than anything
   JS-rendered, because Google's crawler needs to read it and it has to match
   the Business Profile character for character. Deliberately no street address:
   this is a service-area business and the address is hidden on the profile. */
.footer-identity {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* <address> italicises by default; the rest of the footer is upright. */
.footer-nap {
  margin: 0;
  font-style: normal;
  line-height: 1.65;
}

.footer-nap a {
  color: inherit;
  text-decoration: none;
}

.footer-nap a:hover {
  color: var(--foreground);
}

.footer-legal {
  margin-top: 2px;
}

.footer-links {
  display: flex;
  gap: 20px;
  list-style: none;
  margin: 0;
  padding: 0;
}

.footer-links a {
  text-decoration: none;
  color: var(--muted-foreground);
}

.footer-links a:hover {
  color: var(--foreground);
}

/* Middle-section aurora background — soft drifting color blobs behind
   Services + How It Works + About + Case Studies together, based on the
   21st.dev "Aurora Background" component. Mostly teal (matching the site's
   glow accent) with one smaller, subtler gold blob mixed in. Sections have
   no opaque background of their own, so this shows through the gaps and
   margins around the cards while a blurred filter keeps it soft/washed-out
   rather than a hard-edged shape. */

.middle-glow-wrap {
  position: relative;
  overflow: hidden;
}

.middle-aurora {
  position: absolute;
  inset: -8% -6%;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  filter: blur(70px);
}

.aurora-blob {
  position: absolute;
  border-radius: 50%;
  mix-blend-mode: screen;
  background: radial-gradient(circle, color-mix(in oklab, var(--glow-accent) 85%, transparent) 0%, transparent 70%);
}

.aurora-blob-1 {
  top: -6%;
  left: -10%;
  width: 55%;
  height: 36%;
  opacity: 0.55;
  animation: aurora-drift-1 24s ease-in-out infinite;
}

.aurora-blob-2 {
  bottom: -10%;
  right: -8%;
  width: 58%;
  height: 40%;
  opacity: 0.5;
  animation: aurora-drift-2 28s ease-in-out infinite;
}

.aurora-blob-3 {
  top: 34%;
  left: 22%;
  width: 46%;
  height: 30%;
  opacity: 0.4;
  animation: aurora-drift-3 20s ease-in-out infinite;
}

.aurora-blob-gold {
  top: 8%;
  right: 4%;
  width: 34%;
  height: 24%;
  background: radial-gradient(circle, color-mix(in oklab, var(--celebrate-accent) 70%, transparent) 0%, transparent 70%);
  opacity: 0.22;
  animation: aurora-drift-gold 22s ease-in-out infinite;
}

@keyframes aurora-drift-1 {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(6%, 10%) scale(1.18); }
}

@keyframes aurora-drift-2 {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(-7%, -8%) scale(1.12); }
}

@keyframes aurora-drift-3 {
  0%, 100% { transform: translate(-4%, 0) scale(1); }
  50% { transform: translate(8%, 6%) scale(1.2); }
}

@keyframes aurora-drift-gold {
  0%, 100% { transform: translate(0, -4%) scale(1); opacity: 0.18; }
  50% { transform: translate(-6%, 6%) scale(1.3); opacity: 0.28; }
}

@media (prefers-reduced-motion: reduce) {
  .aurora-blob {
    animation: none;
  }
}


/* Reveal-on-scroll */

.reveal {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.reveal.is-visible {
  opacity: 1;
  transform: none;
}

/* Responsive */

/* Short viewports — landscape phones above all, but also small laptop windows.
   Width-based breakpoints alone can't catch these: a landscape iPhone is ~930px
   wide (desktop layout) but only ~430px tall, so the desktop type scale and
   padding leave headings sliced off and cards running past the bottom edge.
   Everything here trades vertical space back. */
@media (max-height: 600px) {
  :root {
    --header-h: 54px;
  }
  .brand-logo {
    height: 32px;
  }
  .nav {
    padding-top: 10px;
    padding-bottom: 10px;
  }
  /* The CTA button, not the logo, is what actually sets header height here —
     shrink it too or --header-h won't match the real measured height. */
  .nav-cta .btn {
    padding: 6px 14px;
    font-size: 0.85rem;
  }
  .nav-links a {
    font-size: 0.9rem;
  }
  .hero {
    padding: 32px 0 28px;
  }
  .hero h1 {
    font-size: clamp(1.5rem, 3.4vw, 2.1rem);
    margin-bottom: 12px;
  }
  .hero-glow-line {
    max-width: 240px;
    margin: 6px 0 16px;
  }
  .hero p {
    font-size: 0.95rem;
    margin-bottom: 20px;
  }
  .section-head {
    margin-bottom: 24px;
  }
  .section-head h2 {
    font-size: clamp(1.25rem, 2.4vw, 1.7rem);
  }
  .section-head p {
    font-size: 0.95rem;
  }
  section {
    padding: 32px 0;
  }
  .card {
    padding: 16px;
  }
  .card-icon {
    width: 30px;
    height: 30px;
    margin-bottom: 10px;
    font-size: 0.95rem;
  }
  .card h3 {
    font-size: 1rem;
  }
  .card p,
  .step p {
    font-size: 0.9rem;
  }
  .step {
    padding: 18px 16px 16px;
  }
  .grid,
  .steps {
    gap: 16px;
  }
  .about-grid {
    column-gap: 32px;
    row-gap: 16px;
  }
  .about-grid p {
    font-size: 0.95rem;
  }
  .pill-list li {
    font-size: 0.9rem;
  }
  .placeholder-card {
    padding: 28px;
  }
  .contact-card {
    padding: 28px;
  }
  .contact-grid {
    gap: 28px;
  }
  .founder-grid {
    gap: 28px;
    grid-template-columns: minmax(0, 200px) minmax(0, 1fr);
  }
  .founder-copy h2 {
    margin-bottom: 12px;
  }
  .founder-copy p {
    margin-bottom: 12px;
    font-size: 0.95rem;
  }
  .founder-sign {
    margin-top: 16px;
    padding-top: 14px;
  }
}

@media (max-width: 900px) {
  .grid-3 {
    grid-template-columns: 1fr 1fr;
  }
  .steps {
    grid-template-columns: 1fr 1fr;
  }
  .about-grid,
  .contact-grid {
    grid-template-columns: 1fr;
  }
  .about-heading,
  .about-copy,
  .about-grid .pill-list {
    grid-column: 1;
    grid-row: auto;
  }
  .founder-grid {
    grid-template-columns: 1fr;
    gap: 28px;
    align-items: start;
  }
  .founder-photo-wrap {
    max-width: 200px;
  }
}

@media (max-width: 640px) {
  .nav-pill {
    display: none;
  }
  .nav-links {
    display: flex;
    position: absolute;
    top: calc(100% + 10px);
    right: var(--gutter);
    left: auto;
    min-width: 220px;
    flex-direction: column;
    gap: 2px;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius-xl);
    padding: 10px;
    box-shadow: 0 20px 44px -14px rgba(0, 0, 0, 0.55), 0 0 0 1px color-mix(in oklab, var(--primary) 6%, transparent);
    opacity: 0;
    visibility: hidden;
    transform: scale(0.94) translateY(-6px);
    transform-origin: top right;
    transition: opacity 0.22s ease, transform 0.22s cubic-bezier(0.2, 0.8, 0.2, 1), visibility 0s linear 0.22s;
  }
  .nav-links.nav-links-open {
    opacity: 1;
    visibility: visible;
    transform: scale(1) translateY(0);
    transition: opacity 0.22s ease, transform 0.22s cubic-bezier(0.2, 0.8, 0.2, 1), visibility 0s linear 0s;
  }
  @media (prefers-reduced-motion: reduce) {
    .nav-links {
      transition: opacity 0.15s ease, visibility 0s linear 0.15s;
      transform: none;
    }
    .nav-links.nav-links-open {
      transition: opacity 0.15s ease, visibility 0s linear 0s;
    }
  }
  .nav-links a {
    padding: 12px 14px;
    border-radius: var(--radius-sm);
    transition: background 0.15s ease, color 0.15s ease;
  }
  .nav-links a:hover,
  .nav-links a:focus-visible {
    background: color-mix(in oklab, var(--primary) 14%, transparent);
    color: var(--foreground);
  }
  .nav-cta .btn-outline {
    display: none;
  }
  /* The CTA is gone at this width, so the dropdown carries Contact again. */
  .nav-item-contact {
    display: block;
  }
  .nav-toggle {
    display: inline-flex;
  }
  .grid-3,
  .steps {
    grid-template-columns: 1fr;
  }
  .hero {
    /* Sized to the GENUINELY visible viewport, not 100svh. iOS Safari's floating
       bottom toolbar overlays the page, and with `viewport-fit=cover` even svh
       measures the full screen — so a hero pinned to svh had its bottom border
       running underneath that pill and getting sliced by it. --visible-vh comes
       from window.visualViewport (see main.js) and excludes the chrome; 100svh is
       the fallback where that API is missing.
       --hero-bottom-gap is the breathing room between the border and the toolbar,
       and is the knob to turn if the clearance looks wrong on a given device.
       Padding is clamped rather than fixed so the content can give some height
       back on short screens instead of pushing the border down into the toolbar. */
    --hero-bottom-gap: 28px;
    min-height: calc(
      var(--visible-vh, 100svh) - var(--header-h) - var(--hero-bottom-gap)
    );
    padding: clamp(48px, 9vh, 96px) 0 clamp(28px, 5vh, 64px);
  }
  .contact-card {
    padding: 36px 24px;
  }
}
