/* Desk Goblin — landing page
   Tokens lifted straight from the app's DesignSystem/ so the site and the
   app are visibly the same product. No webfonts: Noteworthy and ui-rounded
   are system faces on macOS/iOS, which is who this page is for, and it
   keeps the page under a few KB of CSS. */

:root {
  --canvas:  #FFF9F3;
  --surface: #FFFCF8;
  --sunken:  #F5ECE1;
  --ink:     #292725;
  --ink-80:  rgba(41, 39, 37, 0.80);
  --ink-60:  rgba(41, 39, 37, 0.60);
  --ink-40:  rgba(41, 39, 37, 0.40);
  --hairline: rgba(41, 39, 37, 0.12);

  --blue:     #B9D7EA;
  --lavender: #CFC5F2;
  --peach:    #F3C4B5;
  --butter:   #F4DFA3;
  --mint:     #C7DFC9;
  --rose:     #E8C0CF;
  --mint-deep: #33604A;
  --peach-deep: #8C4A36;

  --display: Noteworthy, "Bradley Hand", ui-rounded, system-ui, sans-serif;
  --ui: ui-rounded, system-ui, -apple-system, "Segoe UI", sans-serif;

  --r-card: 18px;
  --r-window: 26px;
  --shadow: 0 18px 50px rgba(41, 39, 37, 0.10);
  --measure: 46ch;
}

*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--ui);
  font-size: 17px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3 { font-family: var(--display); font-weight: 700; line-height: 1.1; margin: 0; }
h1 { font-size: clamp(2.3rem, 5.4vw, 3.9rem); letter-spacing: -0.01em; }
h2 { font-size: clamp(1.75rem, 3.4vw, 2.6rem); }
h3 { font-size: 1.12rem; }
p { margin: 0; }

a { color: inherit; }

.wrap {
  width: 100%; max-width: 1080px; margin: 0 auto;
  /* Horizontal only: a shorthand here would wipe out the vertical padding
     that `section` sets, which is what carries the page's rhythm. */
  padding-left: 24px; padding-right: 24px;
}
.lede { color: var(--ink-60); max-width: var(--measure); }
.micro {
  font-size: 0.78rem; font-weight: 600; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-40);
}

section { padding: clamp(52px, 6.4vw, 90px) 0; }
.section-head { max-width: 34ch; margin-bottom: clamp(26px, 3.2vw, 44px); }
.section-head p { margin-top: 14px; }

/* ── announcement bar ───────────────────────────────────────────────── */
.announce {
  background: var(--butter);
  border-bottom: 1px solid var(--hairline);
  font-size: 0.92rem;
  text-align: center;
  padding: 9px 24px;
}
.announce strong { font-weight: 700; }
.announce .was { text-decoration: line-through; color: var(--ink-60); }

/* ── buttons ────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-family: var(--ui); font-size: 1rem; font-weight: 600;
  padding: 15px 26px; border-radius: 12px; border: 0;
  text-decoration: none; cursor: pointer;
  transition: transform 0.16s ease, background-color 0.16s ease, box-shadow 0.16s ease;
}
.btn-primary { background: var(--ink); color: var(--canvas); }
.btn-primary:hover { background: #3a3734; transform: translateY(-1px); }
.btn-ghost { background: transparent; color: var(--ink); border: 1.5px solid var(--hairline); }
.btn-ghost:hover { border-color: var(--ink-40); background: rgba(41,39,37,0.03); }
.btn:focus-visible { outline: 3px solid var(--lavender); outline-offset: 3px; }
.actions { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }

/* ── hero ───────────────────────────────────────────────────────────── */
.hero { padding-top: clamp(40px, 5vw, 70px); padding-bottom: clamp(40px, 5vw, 64px); }
.hero-grid {
  display: grid; grid-template-columns: 1.1fr 0.9fr;
  gap: clamp(24px, 5vw, 64px); align-items: center;
}
.brand { display: flex; align-items: center; gap: 10px; margin-bottom: 26px; }
.brand svg { width: 30px; height: 30px; }
.brand span { font-family: var(--display); font-weight: 700; font-size: 1.16rem; }
.hero h1 { margin-bottom: 18px; max-width: 20ch; }
.hero .lede { font-size: 1.12rem; }

.price-row { display: flex; align-items: baseline; gap: 12px; margin: 30px 0 6px; }
.price-was { font-size: 1.25rem; color: var(--ink-40); text-decoration: line-through; }
.price-now { font-family: var(--display); font-size: 2.7rem; font-weight: 700; line-height: 1; }
.price-tag {
  font-size: 0.76rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  background: var(--mint); color: var(--mint-deep);
  padding: 5px 11px; border-radius: 999px; align-self: center;
}
.price-note { color: var(--ink-60); font-size: 0.95rem; margin-bottom: 26px; }
.hero-actions { margin-bottom: 16px; }
.hero-meta { font-size: 0.88rem; color: var(--ink-40); }

.hero-art { display: flex; justify-content: center; }
.hero-art .goblin-stage { width: min(244px, 62vw); }

/* ── the goblin's own idle life ─────────────────────────────────────── */
.breathe { animation: breathe 4s ease-in-out infinite; transform-origin: 50% 100%; }
@keyframes breathe { 0%,100% { transform: scaleY(1); } 50% { transform: scaleY(1.018); } }

/* Blink lives on each eye (scaleY) while the gaze lives on the group
   (translate), so the two never fight over one transform. */
.dg-eye { animation: blink 5.4s steps(1, end) infinite; transform-origin: center; }
@keyframes blink {
  0%, 95.5%, 100% { transform: scaleY(1); }
  96.5%, 98% { transform: scaleY(0.08); }
}
.dg-eyes { transition: transform 0.35s ease-out; }

/* The two-frame line boil: seeds 11 and 29, swapped at 7fps, exactly as the
   app does it. Stacked, so only the outline appears to breathe. */
.goblin-stage { position: relative; display: inline-block; line-height: 0; }
.goblin-stage svg { display: block; width: 100%; height: auto; }
/* Base state is hidden: if a browser never runs the animation, one frame
   showing is correct and two stacked would ghost. */
.goblin-stage .boil-b { position: absolute; inset: 0; opacity: 0; }
.goblin-stage .boil-a { animation: breathe 4s ease-in-out infinite, boilA 0.286s steps(1, end) infinite; }
.goblin-stage .boil-b { animation: breathe 4s ease-in-out infinite, boilB 0.286s steps(1, end) infinite; }
@keyframes boilA { 0%, 49.9% { opacity: 1; } 50%, 100% { opacity: 0; } }
@keyframes boilB { 0%, 49.9% { opacity: 0; } 50%, 100% { opacity: 1; } }

/* Pointer resting on the goblin: a small compress, like the app's
   squashPulse. Not a toy, not draggable. */
.goblin-stage { transform-origin: 50% 100%; transition: transform 0.3s cubic-bezier(0.34, 1.4, 0.64, 1); }
.goblin-stage.poked { transform: scale(1.04, 0.94); }
.goblin-stage.hop { animation: hop 0.3s cubic-bezier(0.34, 1.5, 0.64, 1); }
@keyframes hop { 0%,100% { transform: translateY(0); } 40% { transform: translateY(-9px); } }
.goblin-stage:focus-visible { outline: 3px solid var(--lavender); outline-offset: 6px; border-radius: 12px; }

/* Rungs react a touch on hover, so the ladder invites a read. */
.rung img { transition: transform 0.25s cubic-bezier(0.34, 1.3, 0.64, 1); transform-origin: 50% 100%; }
.rung:hover img { transform: translateY(-4px) scale(1.03); }

/* ── demo ───────────────────────────────────────────────────────────── */
.demo-frame {
  position: relative; border-radius: var(--r-window); overflow: hidden;
  background: var(--sunken); border: 1px solid var(--hairline);
  box-shadow: var(--shadow); aspect-ratio: 16 / 10;
}
.demo-frame img, .demo-frame video { width: 100%; height: 100%; display: block; object-fit: cover; }
.demo-caption { margin-top: 14px; font-size: 0.88rem; color: var(--ink-40); text-align: center; }

/* ── steps ──────────────────────────────────────────────────────────── */
.steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(20px, 3vw, 40px); }
.step .num { font-family: var(--display); font-size: 1.5rem; color: var(--ink-40); }
.step h3 { margin: 6px 0 8px; }
.step p { color: var(--ink-60); font-size: 0.98rem; }

/* ── escalation ladder ──────────────────────────────────────────────── */
.ladder {
  display: grid; grid-template-columns: repeat(5, 1fr);
  gap: clamp(8px, 1.6vw, 22px); align-items: end;
  background: var(--sunken); border-radius: var(--r-window);
  padding: clamp(20px, 3vw, 38px) clamp(16px, 3vw, 40px) clamp(18px, 2.4vw, 28px);
}
.rung {
  text-align: center;
  /* Three aligned rows — goblin, quote, timing — so a quote that wraps to
     two lines doesn't drag its timing label out of line with the others. */
  display: grid; grid-template-rows: auto 2.9em auto; align-items: end;
}
.rung img { display: block; margin: 0 auto 12px; height: auto; }
.rung:nth-child(1) img { width: 34%; }
.rung:nth-child(2) img { width: 48%; }
.rung:nth-child(3) img { width: 64%; }
.rung:nth-child(4) img { width: 82%; }
.rung:nth-child(5) img { width: 100%; }
.rung .said {
  font-family: var(--display); font-size: clamp(0.82rem, 1.3vw, 1.05rem);
  align-self: start; line-height: 1.25;
}
.rung .when { font-size: 0.74rem; color: var(--ink-40); margin-top: 3px; }

/* ── features ───────────────────────────────────────────────────────── */
/*
 * The colour used to float above each item as a small pill, which read as an
 * icon that had failed to load — six pastel dashes with no meaning. It now
 * sits *in* the rule that separates the items, so the palette becomes
 * structure instead of decoration, and every item starts on a shared line no
 * matter how long its copy is.
 */
.features {
  display: grid; grid-template-columns: repeat(3, 1fr);
  column-gap: clamp(28px, 3.4vw, 52px); row-gap: clamp(28px, 3vw, 40px);
}
.feature { position: relative; border-top: 1px solid var(--hairline); padding-top: 18px; }
.feature::before {
  content: ""; position: absolute; top: -2.5px; left: 0;
  width: 34px; height: 4px; border-radius: 2px;
  background: var(--accent, var(--ink-40));
}
.feature h3 { margin-bottom: 6px; }
.feature p { color: var(--ink-60); font-size: 0.96rem; max-width: 33ch; }

/* ── split panels (calls, privacy) ──────────────────────────────────── */
.split {
  display: grid; grid-template-columns: 0.85fr 1.15fr;
  gap: clamp(24px, 5vw, 64px); align-items: center;
}
.panel {
  background: var(--sunken); border-radius: var(--r-window);
  padding: clamp(28px, 4vw, 44px); border: 1px solid var(--hairline);
}
.panel-art { display: grid; place-items: center; }
.panel-art svg { width: min(200px, 54vw); height: auto; }
.claims { list-style: none; margin: 22px 0 0; padding: 0; }
.claims li { padding: 9px 0; border-top: 1px solid var(--hairline); color: var(--ink-80); }
.claims li:first-child { border-top: 0; padding-top: 0; }

/* ── pricing ────────────────────────────────────────────────────────── */
.pricing { display: grid; place-items: center; }
.card {
  width: 100%; max-width: 520px; text-align: center;
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: var(--r-window); padding: clamp(32px, 5vw, 52px);
  box-shadow: var(--shadow);
}
.card .price-row { justify-content: center; margin-top: 18px; }
.card .price-now { font-size: 3.4rem; }
.card .price-tag { display: inline-block; margin-top: 12px; }
/* Block, not inline-block: as an inline-block the list sat on the same
   line as the launch badge and the two overlapped. */
.card ul {
  list-style: none; padding: 0; margin: 26px auto; text-align: left;
  display: block; width: max-content; max-width: 100%;
}
.card ul li { padding: 7px 0; color: var(--ink-80); }
.card ul li::before { content: "·"; color: var(--mint-deep); font-weight: 700; margin-right: 10px; }
.card .fine { font-size: 0.86rem; color: var(--ink-40); margin-top: 16px; }

/* ── faq ────────────────────────────────────────────────────────────── */
.faq { max-width: 760px; }
.faq details { border-top: 1px solid var(--hairline); padding: 4px 0; }
.faq details:last-of-type { border-bottom: 1px solid var(--hairline); }
.faq summary {
  cursor: pointer; padding: 18px 0; font-weight: 600; list-style: none;
  display: flex; justify-content: space-between; gap: 16px; align-items: center;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: "+"; color: var(--ink-40); font-size: 1.3rem; line-height: 1; }
.faq details[open] summary::after { content: "–"; }
.faq details p { padding: 0 0 20px; color: var(--ink-60); max-width: 62ch; }

/* ── final cta ──────────────────────────────────────────────────────── */
.final { text-align: center; }
.final svg { width: min(168px, 44vw); height: auto; margin-bottom: 22px; }
.final .price-row { justify-content: center; }
.final .actions { justify-content: center; }

/* ── footer ─────────────────────────────────────────────────────────── */
footer {
  border-top: 1px solid var(--hairline); padding: 34px 0;
  font-size: 0.88rem; color: var(--ink-40);
}
footer .wrap { display: flex; flex-wrap: wrap; gap: 10px 24px; justify-content: space-between; }
footer a { text-decoration: none; }
footer a:hover { color: var(--ink); }
footer nav { display: flex; flex-wrap: wrap; gap: 20px; }

/* ── scroll reveal ──────────────────────────────────────────────────── */
.js .reveal { opacity: 0; transform: translateY(14px); transition: opacity 0.6s ease, transform 0.6s ease; }
.js .reveal.in { opacity: 1; transform: none; }

/* ── responsive ─────────────────────────────────────────────────────── */
@media (max-width: 900px) {
  .hero-grid { grid-template-columns: 1fr; }
  .hero-art { order: -1; }
  .hero-art svg { width: min(230px, 56vw); }
  .hero h1 { max-width: none; }
  .steps, .features { grid-template-columns: 1fr; gap: 30px; }
  .split { grid-template-columns: 1fr; }
  .panel-art { order: -1; }
}
@media (max-width: 620px) {
  body { font-size: 16px; }
  /* The ladder is the memorable bit, so it stays a ladder — two rows,
     never a single stacked column that loses the progression. */
  .ladder { grid-template-columns: repeat(3, 1fr); row-gap: 26px; }
  .rung:nth-child(n) img { width: 66%; }
  .rung:nth-child(4) img { width: 84%; }
  .rung:nth-child(5) img { width: 100%; }
  .price-now { font-size: 2.3rem; }
  .card .price-now { font-size: 2.8rem; }
  .btn { width: 100%; }
  .actions { flex-direction: column; align-items: stretch; }
  .hero-meta, .price-note { text-align: left; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation: none !important; transition: none !important; }
  /* Boil off means one frame has to stay put, or both stack at full opacity. */
  .goblin-stage .boil-b { opacity: 0 !important; }
  /* Gaze survives — it's a few pixels of eye movement, and it's the part
     that makes the character feel present. */
  .dg-eyes { transition: none !important; }
  .js .reveal { opacity: 1; transform: none; }
}

/* ── legal pages ────────────────────────────────────────────────────── */
.legal { max-width: 720px; padding-top: clamp(40px, 5vw, 64px); }
.legal h1 { font-size: clamp(2rem, 4vw, 2.8rem); margin-bottom: 10px; }
.legal .updated { color: var(--ink-40); font-size: 0.9rem; margin-bottom: 8px; }
.legal .summary {
  background: var(--sunken); border: 1px solid var(--hairline);
  border-radius: var(--r-card); padding: 20px 22px; margin: 28px 0 8px;
}
.legal .summary p { color: var(--ink-80); }
.legal h2 { font-size: clamp(1.3rem, 2.2vw, 1.6rem); margin: 44px 0 12px; }
.legal h3 { margin: 26px 0 8px; }
.legal p, .legal li { color: var(--ink-80); }
.legal p + p { margin-top: 14px; }
.legal ul { padding-left: 22px; margin: 12px 0; }
.legal li { padding: 4px 0; }
.legal a:not(.btn):not(.backlink) { text-decoration: underline; text-underline-offset: 2px; }
.legal .backlink { display: inline-block; margin-bottom: 26px; font-size: 0.92rem;
  color: var(--ink-60); text-decoration: none; }
.legal .backlink:hover { color: var(--ink); }

/* The ground the goblin walks on. Fades the very bottom of the page out
 * from under it, so it reads as standing on a ledge instead of on a
 * heading. Only present during a visit. */
.walker-floor {
  position: fixed; left: 0; right: 0; bottom: 0; height: 104px; z-index: 39;
  pointer-events: none; opacity: 0;
  transition: opacity 0.5s ease;
  background: linear-gradient(to top, var(--canvas) 42%, rgba(255, 249, 243, 0) 100%);
}
.walker-floor.on { opacity: 1; }
@media (max-width: 767px) { .walker-floor { display: none !important; } }
@media (prefers-reduced-motion: reduce) { .walker-floor { display: none !important; } }

/* ── the wandering goblin ───────────────────────────────────────────────
 * Fixed to the bottom of the viewport, so it walks the width of the screen
 * regardless of scroll. The container is click-through; only the 56px
 * creature itself takes a click, which is small enough that it can never
 * sit on top of something you were trying to press.
 */
.walker {
  position: fixed; bottom: 8px; left: 0; z-index: 40;
  width: 56px; pointer-events: none;
  will-change: transform;
}
.walker-goblin { position: relative; pointer-events: auto; cursor: pointer; }
.walker[data-facing="left"] .walker-goblin { transform: scaleX(-1); }
.walker .wf { display: block; width: 56px; height: auto; }

/* Two leg frames swapped at 6fps while moving — deliberately jerky, like a
 * flip-book. Standing still shows the idle frame instead. */
.walker .wf-b, .walker .wf-idle { position: absolute; inset: 0; }
.walker .wf-a, .walker .wf-b { opacity: 0; }
.walker[data-moving="yes"] .wf-a { animation: stepA 0.32s steps(1, end) infinite; }
.walker[data-moving="yes"] .wf-b { animation: stepB 0.32s steps(1, end) infinite; }
.walker[data-moving="yes"] .wf-idle { opacity: 0; }
@keyframes stepA { 0%, 49.9% { opacity: 1; } 50%, 100% { opacity: 0; } }
@keyframes stepB { 0%, 49.9% { opacity: 0; } 50%, 100% { opacity: 1; } }

.walker-say {
  position: absolute; bottom: calc(100% - 6px); left: 50%;
  transform: translateX(-50%) scale(0.92); transform-origin: bottom center;
  margin: 0; white-space: nowrap;
  font-family: var(--display); font-size: 0.95rem; font-weight: 700;
  color: var(--ink); background: var(--canvas);
  padding: 6px 14px; border-radius: 999px;
  border: 1px solid var(--hairline);
  box-shadow: 0 8px 22px rgba(41, 39, 37, 0.14);
  opacity: 0; transition: opacity 0.22s ease, transform 0.22s ease;
}
/* Un-mirror the words when the goblin is facing left. */
.walker[data-facing="left"] .walker-say { transform: translateX(50%) scaleX(-1) scale(0.92); }
.walker[data-talking="yes"] .walker-say { opacity: 1; transform: translateX(-50%) scale(1); }
.walker[data-talking="yes"][data-facing="left"] .walker-say {
  opacity: 1; transform: translateX(50%) scaleX(-1) scale(1);
}

.walker-hop { animation: walkerHop 0.34s cubic-bezier(0.34, 1.5, 0.64, 1); }
@keyframes walkerHop {
  0%, 100% { margin-bottom: 0; }
  40% { margin-bottom: 12px; }
}

@media (max-width: 767px) {
  /* Never on phones: it would stand on the buy button. */
  .walker { display: none !important; }
}
@media (prefers-reduced-motion: reduce) {
  .walker { display: none !important; }
}

/* A CTA with nothing to point at yet.
 *
 * Not a blanket opacity: fading a filled ink button turns it into a grey
 * slab with an unreadable label, which reads as broken rather than as
 * "not yet". It becomes a quiet well instead, so the label stays legible
 * and it still can't be clicked into a 404. Matches the app's disabled
 * primary button exactly. */
.btn.is-disabled {
  background: var(--sunken);
  color: var(--ink-40);
  border: 1px solid var(--hairline);
  cursor: not-allowed;
  pointer-events: none;
}
