/* Styling for Relay's landing page.
 *
 * Unlike `legal.css`, which is deliberately document-styled and theme-free,
 * this page is meant to look like the app: same seed colour, same Material 3
 * tonal roles, same typeface. The values below are not eyeballed — they are the
 * exact output of `ColorScheme.fromSeed(seedColor: Color(0xFF3A0CA3))` for both
 * brightnesses, plus the softened dark surfaces `_softDarkScheme` derives in
 * `lib/app/theme.dart`. If the seed in `settings_service.dart` ever changes,
 * these need regenerating.
 *
 * Layout is a stack of full-width "bands" (2026-07-29), each holding a centred
 * `.wrap`. Bands alternate surface tone and are tall enough to fill a small
 * laptop viewport, so each one is a single idea with room to breathe. Before
 * this the page was one 46rem reading column, which cannot hold a screenshot
 * beside its explanation.
 *
 * Still no JavaScript and no dependence on the app bundle: this is the URL given
 * to Google as the OAuth homepage and to the stores as the marketing site, so it
 * has to render for someone who has never signed in, on a slow connection. The
 * nav collapses by media query rather than into a JavaScript menu for the same
 * reason.
 */

@font-face {
  font-family: "Noto Sans Relay";
  /* The app's variable font, subset to Latin and converted to woff2:
   * 2001 KB -> 74 KB, which is the difference between a landing page that costs
   * a user two megabytes and one that does not. Generated from
   * `fonts/NotoSans-Variable.ttf` (the app's own asset, unchanged) with
   * `subset-font`; regenerate from that source if the copy ever needs glyphs
   * outside Basic Latin, Latin-1 and common punctuation.
   *
   * The `wdth` axis survives the subset, which matters: `wdth 92` is what makes
   * this read like the app rather than like default Noto Sans. `font-stretch`
   * declares the axis range so the variation setting below is honoured. */
  src: url("/assets/fonts/NotoSans-Latin.woff2") format("woff2");
  font-weight: 100 900;
  font-stretch: 62.5% 100%;
  font-display: swap;
}

:root {
  color-scheme: light dark;

  --primary: #615690;
  --on-primary: #ffffff;
  --primary-container: #e6deff;
  --on-primary-container: #493e76;
  --surface: #fdf8ff;
  --on-surface: #1c1b20;
  --on-surface-variant: #48454e;
  --surface-container-low: #f7f2fa;
  --surface-container-highest: #e6e1e9;
  --outline-variant: #c9c4d0;
  /* dividerColor: alphaBlend(onSurface @ 0.18, surface) */
  --rule: #d4d0d7;
  --hover: rgba(97, 86, 144, 0.08);
  /* Translucent backdrop for the sticky header. Matches --surface, so the page
     appears to slide under frosted glass rather than under a second colour. */
  --header-bg: rgba(253, 248, 255, 0.85);
}

@media (prefers-color-scheme: dark) {
  :root {
    --primary: #cbbeff;
    --on-primary: #32285e;
    --primary-container: #493e76;
    --on-primary-container: #e6deff;
    --surface: #2b313c;
    --on-surface: #e6e1e9;
    --on-surface-variant: #c9c4d0;
    --surface-container-low: #313742;
    --surface-container-highest: #424752;
    --outline-variant: #5c5f69;
    /* dividerColor: alphaBlend(onSurface @ 0.26, softened surface) */
    --rule: #5c5f69;
    --hover: rgba(203, 190, 255, 0.1);
    --header-bg: rgba(43, 49, 60, 0.85);
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  /* No page padding: bands run edge to edge so their background tones can, and
     each band's .wrap supplies the gutter. */
  padding: 0;
  background: var(--surface);
  color: var(--on-surface);
  font-family: "Noto Sans Relay", system-ui, -apple-system, "Segoe UI", Roboto,
    sans-serif;
  /* The app pulls the width axis in to 92 so the default doesn't read as wide. */
  font-variation-settings: "wdth" 92;
  font-size: 16px;
  line-height: 1.6;
  -webkit-text-size-adjust: 100%;
}

/* Anchor targets sit under the sticky header without this. */
html { scroll-behavior: smooth; scroll-padding-top: 5rem; }

a { color: var(--primary); }

.wrap {
  max-width: 64rem;
  margin: 0 auto;
  padding: 0 1.25rem;
}

/* ---------------------------------------------------------------- header -- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--header-bg);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--rule);
}

.header-inner {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  min-height: 4rem;
}

.nav {
  display: flex;
  gap: 1.5rem;
  /* Pushes itself off the wordmark and the actions off itself, centring the nav
     between them without a three-column grid. */
  margin-left: auto;
}

.nav a {
  color: var(--on-surface-variant);
  text-decoration: none;
  font-size: 0.95rem;
  font-weight: 600;
}

.nav a:hover { color: var(--primary); }

.header-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-left: auto;
}

/* --------------------------------------------------------------- buttons -- */

/* M3 filled button: flat at rest, stadium corners, a state-layer on hover. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 40px;
  padding: 0 1.15rem;
  border-radius: 20px;
  font-size: 0.92rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}

.btn-lg {
  min-height: 48px;
  padding: 0 1.6rem;
  font-size: 1rem;
  border-radius: 24px;
}

.btn-primary {
  background: var(--primary);
  color: var(--on-primary);
}

.btn-primary:hover { box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.12); }

.btn-outline {
  border: 1px solid var(--outline-variant);
  color: var(--primary);
}

.btn-outline:hover { background: var(--hover); }

.btn-ghost { color: var(--on-surface-variant); }
.btn-ghost:hover { background: var(--hover); color: var(--primary); }

.cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 2rem;
}

.cta-note {
  margin: 0.9rem 0 0;
  font-size: 0.9rem;
  color: var(--on-surface-variant);
}

/* ----------------------------------------------------------------- bands -- */

.band { padding: 3.5rem 0; }

/* Alternating tone is what gives the stack its rhythm; without it the sections
   run together into one long scroll. */
.band-alt { background: var(--surface-container-low); }

.band-cta {
  background: var(--primary-container);
  color: var(--on-primary-container);
  text-align: center;
}

.band-cta .cta-row { justify-content: center; }
.band-cta .cta-note { color: var(--on-primary-container); opacity: 0.85; }

/* Only tall on a real screen. Forcing a minimum height on a phone would leave
   half-empty sections and a scroll that never ends. */
@media (min-width: 60rem) {
  .band {
    display: flex;
    align-items: center;
    /* 38rem + the 4rem header ≈ a 1366x768 laptop's viewport, so one band fills
       one screen on the smallest machine anyone is likely to browse this on. */
    min-height: 38rem;
    padding: 5rem 0;
  }
  .band > .wrap { width: 100%; }
  .band-hero { min-height: 42rem; }

  /* A band carrying only a heading and a paragraph does not need a full screen;
     stretched to one it reads as a mistake rather than as breathing room. */
  .band-intro { min-height: 0; padding: 5rem 0 1rem; }
}

/* ------------------------------------------------------------- headings -- */

h1 {
  font-size: clamp(2rem, 5vw, 3.1rem);
  line-height: 1.12;
  font-weight: 700;
  letter-spacing: -0.02em;
  margin: 0 0 1rem;
  max-width: 18ch;
}

h2 {
  font-size: clamp(1.4rem, 3vw, 1.9rem);
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: -0.015em;
  margin: 0 0 1.25rem;
  max-width: 24ch;
}

.subhead {
  font-size: 1.15rem;
  font-weight: 700;
  margin: 2.5rem 0 1rem;
}

.lead {
  font-size: clamp(1.05rem, 1.6vw, 1.2rem);
  color: var(--on-surface-variant);
  margin: 0 0 0.5rem;
  max-width: 46ch;
}

/* The plain-language purpose paragraph. Body size rather than lead size, and a
   wider measure, because it is explanatory prose rather than a second headline:
   it should read as detail, not as a competing claim. */
.purpose {
  color: var(--on-surface-variant);
  max-width: 60ch;
  margin: 0 0 1rem;
}

/* ----------------------------------------------------------- hero layout -- */

/* Copy centred over a full-width shot, rather than the two-column hero this
   started as: at 1fr 1fr the Engage capture displayed at ~464px, which is too
   small to read a single row of the app it is selling. One column lets it run
   the whole 64rem. */
.band-hero .wrap {
  display: grid;
  gap: 3rem;
  justify-items: center;
}

.hero-copy {
  text-align: center;
  max-width: 44rem;
}

/* h1 carries max-width: 18ch to keep the headline from running as one long
   line. In the centred hero that cap has to be centred too, or the heading box
   sits hard left inside the 44rem column and only its text is centred, which
   reads as a misalignment. */
.hero-copy h1 { margin-inline: auto; }
.hero-copy .lead { margin-inline: auto; }
.hero-copy .cta-row { justify-content: center; }

/* ------------------------------------------------------- platform support -- */

/* Replaced the hero's price note: what it costs now lives on /pricing, and the
   question a first-time visitor actually has here is whether it runs on their
   machine. Two rows so "available" and "coming soon" cannot be misread as one
   list - a single row with the unreleased ones greyed out relies on colour
   alone to carry the difference. */
.availability {
  margin-top: 1.75rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
}

.availability-label {
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--on-surface-variant);
  margin: 0;
}

/* Not margin-top on the second label: the gap belongs between the groups, and
   putting it here keeps the flex `gap` doing the rest. */
.availability-label + .platforms { margin-bottom: 0.5rem; }

.platforms {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem 1.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.platforms li {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--on-surface);
}

.platforms svg {
  width: 1.25rem;
  height: 1.25rem;
  fill: currentColor;
  flex: none;
}

/* Muted as a group, so the icons follow the text via currentColor. Kept above
   the contrast floor rather than faded to a hint: these are real platforms and
   a visitor checking for their own has to be able to read them. */
.platforms-soon li { color: var(--on-surface-variant); font-weight: 400; }

.band-hero .shot-hero { width: 100%; }

/* -------------------------------------------------------- feature layout -- */

.feature-row {
  display: grid;
  gap: 2rem;
  align-items: center;
}

@media (min-width: 60rem) {
  .feature-row {
    grid-template-columns: 1fr 1fr;
    gap: 4rem;
  }
  /* Puts the copy second in visual order while leaving it first in the DOM, so
     the reading order for a screen reader stays heading-then-explanation. */
  .feature-row.reverse .feature-copy { order: 2; }
}

.feature-copy h3 {
  font-size: clamp(1.3rem, 2.4vw, 1.75rem);
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: -0.015em;
  margin: 0 0 0.85rem;
}

.feature-copy p {
  color: var(--on-surface-variant);
  margin: 0 0 0.85rem;
  max-width: 46ch;
}

.feature-copy p:last-child { margin-bottom: 0; }

/* ------------------------------------------------------- screenshot slot -- */

/* Placeholder until real captures exist. Replacing it with a <picture> that
   carries the same class keeps the frame, so the layout does not move when the
   screenshots land — the dashed border and the label are the only things that
   disappear (they key off :empty-ish content, so a <picture> simply overrides). */
.shot {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 4 / 3;
  border-radius: 12px;
  background: var(--surface-container-highest);
  border: 1px dashed var(--outline-variant);
  color: var(--on-surface-variant);
  font-size: 0.85rem;
  text-align: center;
  padding: 1rem;
  overflow: hidden;
}

/* The capture is a 2000x1267 window that includes the installed PWA's title bar
   (the "Relay" caption and the minimise/maximise/close controls) in its top
   44px. Window chrome on a marketing shot reads as a desktop app screenshot and
   the controls are dead pixels, so it is cropped off here rather than re-encoded:
   the box holds exactly the aspect of the region BELOW the bar (2000x1223), and
   `object-position: bottom` pins the image's bottom edge, so cover pushes
   precisely those 44px past the top of the frame. Measured, not estimated - the
   content background starts at y=44 in both the light and dark files.

   Same reasoning as .shot-portrait below: the only rasteriser in the toolchain
   writes PNG, and re-encoding a 58KB WebP through it to save 3.5% of the pixels
   would cost more bytes than it saves. If these are ever recaptured without the
   chrome, drop the object-position and set aspect-ratio to the new intrinsic. */
.shot-hero { aspect-ratio: 2000 / 1223; }
.shot-hero img { object-position: center bottom; }

/* A phone capture is portrait, and forcing one into the 4:3 default would either
   letterbox it or crop the device in half. The row is a two-column grid, so a
   taller box here just makes that row taller; nothing else moves. */
.shot-portrait { aspect-ratio: 3 / 4; }

/* The widget capture is a whole 1008x2244 home screen, and the interesting band
   (the Google Calendar widget through the bottom of Relay's) runs y 415..1759.
   Re-verified against the recaptured shots 2026-07-29: the two cards span
   y 431..1726, so they sit inside the window with a little wallpaper either
   side, and the status bar and dock stay outside it. If these are recaptured
   again, check that band before trusting the 46% below.
   `object-fit: cover` on a 3:4 box already shows 59.9% of that height; this just
   slides the window to start at 18.5% instead of centring it.
   46% = 18.5 / (100 - 59.9), the fraction of the available scroll.

   Cropped here rather than in the file on purpose: the source is a JPEG (a
   photographic wallpaper, which PNG stores terribly), and the only rasteriser in
   the toolchain writes PNG. Re-cropping would have turned 350KB into 700KB to
   save 40% of the pixels, which is a bad trade. */
.shot-portrait img { object-position: center 46%; }

/* A page of the setup guide (/guide). Two differences from a screenshot slot.
   The aspect is A4 exactly, matching the images guide/build.sh renders out of
   the PDF, so `cover` below crops nothing. And it is capped well under its
   column: an A4 page filling a 1fr column stands taller than any other image on
   the site and reads as a document someone has shoved in your face, where the
   same page at 22rem reads as a document someone is holding up.

   White background rather than a themed one because the pages are white paper
   in both schemes. If the render ever stops matching the aspect exactly, the
   letterbox that appears will be page-coloured instead of a dark bar. */
.shot-doc {
  aspect-ratio: 210 / 297;
  max-width: 22rem;
  margin-inline: auto;
  background: #fff;
}

/* The exception is a page whose content is a diagram rather than prose. At
   22rem the clarify map's labels stop being legible and it becomes a picture of
   a page, which proves nothing; given the whole column the boxes can be read,
   which is the entire argument for downloading the thing. */
.shot-doc-full { max-width: none; }

/* Once a real <picture>/<img> is dropped in with class="shot", these make it
   behave as a framed screenshot rather than a placeholder. */
.shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.shot:has(img) {
  border-style: solid;
  padding: 0;
  background: var(--surface-container-low);
}

/* ----------------------------------------------------------------- cards -- */

.grid {
  display: grid;
  gap: 0.75rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}

/* Pricing wants exactly two columns, not as-many-as-fit. */
@media (min-width: 44rem) {
  .grid-2 { grid-template-columns: 1fr 1fr; }
}

/* M3 outlined card, at the app's 12px menu/surface radius. */
.card {
  background: var(--surface-container-low);
  border: 1px solid var(--outline-variant);
  border-radius: 12px;
  padding: 1.1rem 1.2rem;
}

/* On a tinted band the card would otherwise be the same colour as its
   background and vanish. */
.band-alt .card { background: var(--surface); }

.card h3 {
  margin: 0 0 0.35rem;
  font-size: 0.95rem;
  font-weight: 700;
}

.card p {
  margin: 0;
  font-size: 0.92rem;
  color: var(--on-surface-variant);
}

/* --------------------------------------------------------------- pricing -- */

.price-card {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1.5rem;
}

.price-card .btn { margin-top: auto; align-self: flex-start; }

.price {
  font-size: 2.5rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--on-surface);
  margin: 0;
}

.price span {
  font-size: 1rem;
  font-weight: 600;
  color: var(--on-surface-variant);
}

.price-card.featured { border-color: var(--primary); }

/* ----------------------------------------------------------------- chips -- */

/* Mirrors chipTheme in theme.dart: 8px radius, highest container, hairline. */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0 0 1rem;
  padding: 0;
  list-style: none;
}

.chips li {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.65rem;
  border: 1px solid var(--outline-variant);
  border-radius: 8px;
  background: var(--surface-container-highest);
  color: var(--on-surface-variant);
  font-size: 0.85rem;
}

/* Brand marks sit on a fixed white tile rather than bare on the chip. The logos
   are supplied at whatever background their owner ships them with, and they do
   not all survive the dark palette: Linear's is near-black and vanishes against
   the dark chip. A neutral tile makes every mark legible in both themes without
   recolouring anyone's logo, which brand guidelines generally forbid. */
.chips img {
  width: 18px;
  height: 18px;
  border-radius: 4px;
  background: #fff;
  flex: none;
}

/* ----------------------------------------------------------- notes, callout */

.note {
  color: var(--on-surface-variant);
  margin: 0;
  max-width: 68ch;
  font-size: 0.92rem;
}

/* A .note directly after a card grid would otherwise butt against the cards. */
.grid + .note { margin-top: 1.25rem; }

.callout {
  background: var(--primary-container);
  color: var(--on-primary-container);
  border-radius: 12px;
  padding: 1.5rem 1.6rem;
  max-width: 60ch;
}

.callout p { margin: 0; }
.callout p + p { margin-top: 0.75rem; }

.cta-head { margin-left: auto; margin-right: auto; }

/* ---------------------------------------------------------------- footer -- */

footer {
  padding: 2.5rem 0;
  border-top: 1px solid var(--rule);
  color: var(--on-surface-variant);
  font-size: 0.9rem;
}

footer ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.25rem;
  margin: 0 0 1rem;
  padding: 0;
  list-style: none;
}

/* Trademark attribution. Smaller and dimmer than the rest of the footer because
   it is a notice rather than something anyone needs to read, but deliberately
   not hidden: its whole job is to be findable. */
.trademark {
  margin: 0 0 0.5rem;
  font-size: 0.82rem;
  opacity: 0.85;
  max-width: 70ch;
}

/* "Crafted and hosted in Switzerland", with the cross drawn inline rather than
   set as the 🇨🇭 emoji: Windows has no colour glyph for regional-indicator
   pairs, so the emoji renders there as the letters CH in a box. Fixed hex
   fills, not theme variables — a flag whose colours follow the palette is not
   that flag any more. */
.made-in {
  display: flex;
  align-items: center;
  gap: 0.45em;
  margin: 0 0 0.5rem;
}

.swiss-cross {
  width: 0.95em;
  height: 0.95em;
  border-radius: 2px;
  flex: none;
}

.copyright { margin: 0; }

/* ---------------------------------------------------------------- header -- */

/* The wordmark lives in the header now, so it no longer needs the 4rem gap it
   carried when it sat at the top of a single column. */
.wordmark {
  display: flex;
  align-items: center;
  gap: 0.4em;
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--primary);
  text-decoration: none;
  flex: none;
}

/* Sized in `em` so the mark tracks the wordmark's font-size instead of needing
   its own breakpoint.

   If this ever renders enormous, the rule is not being applied at all rather
   than being too large: an inline `<svg>` with no width has no intrinsic size
   and expands to fill its container. The usual cause is a cached stylesheet
   served alongside fresh HTML. Check that before adjusting the number. */
.wordmark-mark {
  width: 2.1em;
  height: 2.1em;
  flex: none;
}

/* Below this the three nav links do not fit beside the two buttons. They are
   dropped rather than hidden behind a menu button: every destination is also in
   the footer, so a JavaScript menu would add a focus trap to reach links the
   page already offers. */
@media (max-width: 52rem) {
  .nav { display: none; }
  .header-inner { justify-content: space-between; }
}

@media (max-width: 30rem) {
  .btn-ghost { display: none; }
}
