/* ============================================================
   Linup — linup.io
   ============================================================
   The palette is CLOSED at five values. Every colour below is one of
   the five, or one of the two locked gradients built from them, or an
   rgba() shadow derived from one of them. There is no sixth colour,
   and adding one is a brand decision rather than a CSS decision.

   Two derivations are permitted, both specified rather than invented:
     #FF7A9C  a lightened tint of Hot Fuchsia, ONLY as the attention
              gradient's second stop — never a standalone fill or text.
     #FFFFFF  card white, per the Digital Playbook's rule for
              informational cards. See the note on .card below.

   Source: LINUP_BRAND_GUIDELINES.md, LINUP_DIGITAL_PLAYBOOK.md.
   ============================================================ */

:root {
  --snow:    #F6F7F8;  /* every light surface        */
  --ink:     #011627;  /* text, structure, dark surfaces */
  --green:   #2EC4B6;  /* money on track, celebration    */
  --blue:    #20A4F3;  /* coins / recognition, always    */
  --fuchsia: #FF3366;  /* needs-a-look. Only that job.   */

  /* The two gradients. There are no others — a third combination
     needs a Directive-level decision, not a one-off here. */
  --positive:  linear-gradient(135deg, #2EC4B6, #20A4F3);
  --attention: linear-gradient(135deg, #FF3366, #FF7A9C);

  /* Shadows are warm — tinted to match the object casting them,
     never a neutral grey blur. */
  --shadow-neutral:  0 12px 28px -10px rgba(1, 22, 39, 0.16);
  --shadow-positive: 0 14px 26px -10px rgba(46, 196, 182, 0.45);

  /* One system stack. Personality comes from weight and scale, not an
     exotic face — so there is no webfont to load and no FOUT. */
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;
}

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.6;
  color: var(--ink);
  background: var(--snow);
  -webkit-font-smoothing: antialiased;
}

/* Any figure, anywhere. The name is the mechanism: a column of
   amounts has to hold its column. */
.price-figure, .tabular { font-variant-numeric: tabular-nums; }

.wrap { width: min(1080px, 100% - 3rem); margin-inline: auto; }
.wrap.narrow { width: min(680px, 100% - 3rem); }

.skip {
  position: absolute; left: -9999px;
  background: var(--snow); color: var(--ink);
  padding: 0.75rem 1rem; z-index: 10;
}
.skip:focus { left: 1rem; top: 1rem; }

:where(a, button, input):focus-visible {
  outline: 3px solid var(--blue);
  outline-offset: 3px;
}

/* ---------- Hero: Ink surface, Snow wordmark ---------- */
/* The wordmark is Ink on light or Snow on dark and never anything
   else — which is why there are two SVG files rather than one tinted
   with CSS. This is the dark case. */

.hero {
  background: var(--ink);
  color: var(--snow);
  padding: clamp(3rem, 9vw, 6.5rem) 0 clamp(3rem, 8vw, 5rem);
}

/*
 * Links inside the dark hero.
 *
 * Without this they render in the browser's own default link blue,
 * which against the ink background measures 1.95:1 - well under 4.5:1,
 * the floor for normal-size text. It went unnoticed because the
 * home page's hero contains no links; only the confirmation and
 * unsubscribe pages have them, and both are pages people reach once,
 * from an email, and never look at again.
 *
 * Snow rather than the brand blue: --blue on --ink is 6.1:1, which
 * passes, but the blue means "coins and recognition" everywhere else
 * in the product and a navigation link is neither.
 *
 * Buttons are excluded rather than overridden. .download-cta already
 * sets text-decoration: none, but `.hero a` is one class plus one type
 * and .download-cta is one class, so the hero rule out-specifies it and
 * quietly underlined every call to action in a hero - including
 * "Download for Android" on the home page. Source order does not decide
 * that; specificity does. Excluding here means there is nothing to win.
 */
.hero a:not(.download-cta):not(.unsub-button) {
  color: var(--snow);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.hero a:not(.download-cta):not(.unsub-button):hover,
.hero a:not(.download-cta):not(.unsub-button):focus-visible {
  text-decoration-thickness: 2px;
}

/*
 * Short pages - confirmation, unsubscribe - are a single hero and
 * nothing else, so without this the ink block stops partway down and
 * leaves a band of bare page under it that looks like content failed
 * to load. Only applies where a page opts in.
 */
.hero.full { min-height: 100svh; display: flex; align-items: center; }

.hero-mark {
  display: block;
  width: clamp(150px, 26vw, 210px);
  height: auto;
  margin-bottom: clamp(2rem, 6vw, 3.5rem);
}

.tagline {
  margin: 0 0 1.25rem;
  font-size: clamp(2rem, 6.2vw, 3.75rem);
  line-height: 1.1;
  font-weight: 700;
  letter-spacing: -0.02em;
  max-width: 15ch;
}

/* The gradient carries the emphasis; the words carry the meaning.
   background-clip is progressive — where it is unsupported the text
   simply stays Snow, which is still correct and still readable. */
.tagline em {
  font-style: normal;
  background: var(--positive);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
@supports not (background-clip: text) {
  .tagline em { color: var(--snow); }
}

.lede {
  margin: 0 0 clamp(2rem, 5vw, 3rem);
  font-size: clamp(1.05rem, 2.1vw, 1.3rem);
  max-width: 54ch;
  opacity: 0.86;
}

/* ---------- Waitlist ---------- */
/* One primary action on the page, and it carries the positive
   gradient — the Playbook allows the attention gradient on a button
   in exactly one place in the app, and this is not it. */

.waitlist { max-width: 34rem; }

.waitlist-label {
  display: block;
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin-bottom: 0.6rem;
  opacity: 0.7;
}

.waitlist-row { display: flex; flex-wrap: wrap; gap: 0.6rem; }

.waitlist input {
  flex: 1 1 16rem;
  min-width: 0;
  font: inherit;
  color: var(--ink);
  background: var(--snow);
  border: 2px solid transparent;
  border-radius: 12px;
  padding: 0.85rem 1rem;
  min-height: 48px;
}
.waitlist input::placeholder { color: var(--ink); opacity: 0.45; }
.waitlist input[aria-invalid="true"] { border-color: var(--fuchsia); }

.waitlist button {
  flex: 0 0 auto;
  font: inherit;
  font-weight: 650;
  color: var(--snow);
  background: var(--positive);
  border: 0;
  border-radius: 12px;
  padding: 0.85rem 1.5rem;
  min-height: 48px;      /* the app's minimum touch target */
  cursor: pointer;
  box-shadow: var(--shadow-positive);
}
.waitlist button:disabled { cursor: progress; opacity: 0.65; }

.waitlist-note {
  margin: 0.9rem 0 0;
  font-size: 0.9rem;
  opacity: 0.7;
  max-width: 44ch;
}

/* No state is ever colour alone — the status line always says what
   happened in words, and the colour only reinforces it. */
.status { margin: 0.9rem 0 0; font-size: 0.95rem; min-height: 1.5rem; }
.status[data-state="ok"]      { color: var(--green); }
.status[data-state="error"]   { color: var(--fuchsia); }
.status[data-state="working"] { color: var(--snow); opacity: 0.75; }

.status a { color: var(--blue); }

/* ---------- Sections ---------- */

.section { padding: clamp(3rem, 8vw, 5.5rem) 0; }
.section-alt { background: var(--ink); color: var(--snow); }

h2 {
  margin: 0 0 0.75rem;
  font-size: clamp(1.6rem, 3.6vw, 2.4rem);
  line-height: 1.15;
  letter-spacing: -0.015em;
}

.section-lede {
  margin: 0 0 clamp(2rem, 4vw, 2.75rem);
  font-size: 1.1rem;
  max-width: 52ch;
  opacity: 0.75;
}

/* Informational cards are white with dark text and a neutral shadow.
   Gradients are reserved for moments that mean something — using one
   on every card would spend the emphasis on nothing. */
.cards {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1.25rem;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
}

.card {
  /* Card white, not Snow. The five-colour palette has Snow as the light
     SURFACE; the Digital Playbook separately requires informational
     cards to be "white with dark text and a neutral shadow", and
     CLAUDE.md settles the tie in the Playbook's favour. Same value and
     same reasoning as CARD_WHITE in src/theme/palette.ts — a specified
     derivation, not an invented sixth colour. */
  background: #FFFFFF;
  border-radius: 18px;
  padding: 1.6rem 1.5rem;
  box-shadow: var(--shadow-neutral);
}
.card h3 { margin: 0 0 0.6rem; font-size: 1.15rem; line-height: 1.25; }
.card p  { margin: 0; opacity: 0.8; }

/* The one gradient moment on the page, on the one claim that most
   distinguishes the product. */
.statement {
  background: var(--positive);
  color: var(--snow);
  border-radius: 20px;
  padding: 1.75rem;
  margin: 0 0 2rem;
  box-shadow: var(--shadow-positive);
}
.statement-kicker {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.85;
}
.statement-body {
  margin: 0;
  font-size: clamp(1.2rem, 2.6vw, 1.6rem);
  line-height: 1.3;
  font-weight: 600;
}

.section-alt p { opacity: 0.85; }
.section-alt .statement p { opacity: 1; }

/* ---------- Price ---------- */

.price { margin: 0 0 1rem; display: flex; align-items: baseline; flex-wrap: wrap; gap: 0.6rem; }
.price-figure {
  font-size: clamp(2.4rem, 6vw, 3.4rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  background: var(--positive);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
@supports not (background-clip: text) {
  .price-figure { color: var(--ink); }
}
.price-unit { font-size: 1.05rem; opacity: 0.75; }

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

.footer {
  background: var(--ink);
  color: var(--snow);
  padding: clamp(2.5rem, 6vw, 4rem) 0;
}
.footer-icon { display: block; margin-bottom: 1rem; }
.footer-line { margin: 0 0 0.75rem; }
.footer-small { margin: 0; font-size: 0.9rem; opacity: 0.65; max-width: 60ch; }

/* ---------- Motion ---------- */
/* Motion marks a real change only. There is none here that is purely
   decorative, and what little there is stops on request. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------- Unsubscribe ---------- */
/* The attention gradient, and one of the few places it belongs: this
   is a destructive action and the page should not pretend otherwise.
   The Playbook reserves this gradient for "needs a look" — a button
   that permanently deletes someone's data qualifies. */
.unsub-button {
  font: inherit;
  font-weight: 650;
  color: var(--snow);
  background: var(--attention);
  border: 0;
  border-radius: 12px;
  padding: 0.85rem 1.5rem;
  min-height: 48px;
  cursor: pointer;
  box-shadow: 0 14px 26px -10px rgba(255, 51, 102, 0.45);
}
.unsub-button:disabled { cursor: progress; opacity: 0.65; }

.tagline-sm { font-size: clamp(1.8rem, 5vw, 2.75rem); }

/* ---------- Expectation-setting, and the store badges ---------- */

.waitlist-expectation {
  margin: 1.25rem 0 0;
  font-size: 0.95rem;
  line-height: 1.55;
  max-width: 52ch;
  opacity: 0.8;
  border-left: 2px solid var(--green);
  padding-left: 0.9rem;
}

.stores { margin-top: 2rem; }

.stores-label {
  margin: 0 0 0.7rem;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.55;
}

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

/* DELIBERATELY NOT LINKS, NOT BUTTONS.
   These are <li> elements with no href and no click handler, because
   the apps do not exist. A disabled-looking control that is secretly
   inert still invites a tap; something that was never interactive in
   the markup cannot mislead a screen reader, a keyboard user, or
   anyone else. `aria-disabled` states it outright.
   They also deliberately do NOT reproduce Apple's or Google's official
   badge artwork: those marks may not be altered or greyed out under
   their own brand terms, so imitating them in a "coming soon" state
   would be both misleading and a misuse of someone else's trademark.
   These are plainly Linup's own placeholders, in Linup's palette. */
.store-badge {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.6rem 0.95rem;
  border-radius: 12px;
  border: 1px solid var(--snow);
  /* Ink at low alpha over the Ink hero — a documented derivation, not
     a sixth colour. */
  background: rgba(246, 247, 248, 0.06);
  opacity: 0.55;
  cursor: default;
}

.store-badge-icon {
  width: 22px;
  height: 22px;
  border-radius: 6px;
  background: var(--positive);
  flex: 0 0 auto;
  opacity: 0.85;
}

.store-badge-text { display: flex; flex-direction: column; line-height: 1.15; }
.store-badge-top { font-size: 0.7rem; letter-spacing: 0.04em; opacity: 0.8; }
.store-badge-name { font-size: 1rem; font-weight: 650; }

/* The state is never colour or opacity alone — it says the words. */
.store-badge-flag {
  margin-left: 0.35rem;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 0.2rem 0.45rem;
  border-radius: 999px;
  border: 1px solid var(--snow);
  white-space: nowrap;
}

/* ============================================================
   The Android download, in the hero. Uses the positive gradient
   and the existing shadow token - no new colour, no sixth value.
   ============================================================ */
.download { margin: 1.5rem 0 0; display: flex; flex-direction: column; gap: .5rem; align-items: flex-start; }
.download-cta {
  display: inline-block;
  padding: .85rem 1.6rem;
  border-radius: 999px;
  font-weight: 700;
  text-decoration: none;
  color: var(--snow);
  background: var(--positive);
  box-shadow: var(--shadow-positive);
}
.download-cta:hover { filter: brightness(1.05); }
.download-cta:focus-visible { outline: 3px solid var(--blue); outline-offset: 3px; }

/*
 * The browser link, subordinate to the download.
 *
 * Two solid gradient buttons side by side read as two equal choices,
 * and they are not: Linup is a phone app and the browser build is a
 * convenience. An outline button says "also available" without
 * shouting it, and keeps the positive gradient meaning one thing on
 * this page rather than two.
 */
.download-cta-secondary {
  background: none;
  box-shadow: none;
  color: var(--snow);
  border: 2px solid rgba(246, 247, 248, .45);
}
.download-cta-secondary:hover { border-color: var(--snow); filter: none; }
.download-note { font-size: .85rem; color: rgba(246,247,248,.62); }
