/* =============================================================
   Stratum — marketing site
   =============================================================

   Shared by every page under landing/. Split out of index.html the moment a
   second page existed to share it with, which is exactly the condition that
   file's own note set.

   The tokens are copied from web/css/system.css so the marketing site and the
   product stay visually identical. This is a separate Cloudflare Workers
   project from web/, so it cannot reach that file directly; the 500-odd lines
   of dashboard components under those tokens are not copied, because nothing
   here is a data table.

   Inter and JetBrains Mono are the product's faces, not a default. A marketing
   page in a different typeface would be a different company by the time
   somebody signed in.

   What belongs here: tokens, the reset, the header, buttons, the hero frame,
   section rhythm, the strata mark, the supporting-point grid, the footer and
   the reveal. What does not: anything only one page draws — the product mocks
   and the install walkthrough both stay inline in their own page.
   ============================================================= */
:root {
  --bg: #0A0B0F;
  --surface: #14151C;
  --elevated: #1E2029;
  --border: #2A2D38;
  --border-soft: rgba(255, 255, 255, 0.04);
  --text: #F2F4F8;
  --text-2: #9AA0AE;
  --muted: #5C6170;
  --primary: #F45708;
  --primary-hover: #FF6F24;
  --primary-soft: rgba(244, 87, 8, 0.12);
  /* The grey of the logo's unlit strata — the only non-brand hue on the page
     until the mocks, which need the dashboard's own status colours to be
     honest about what the screens look like. */
  --stone: #8A8F99;
  --danger: #FF3A5C;
  --warning: #F5D547;
  --success: #2BE08C;
  --high: #E2641C;

  --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --pad: clamp(20px, 4vw, 48px);
  --max: 1180px;

  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
}

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 1rem;
  line-height: 1.6;
  letter-spacing: -0.005em;
  -webkit-font-smoothing: antialiased;
}
img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
::selection { background: var(--primary-soft); }
:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; border-radius: 4px; }

/* Skip to content: the first thing a keyboard reaches, hidden until it is.
   The header carries up to seven links before the page starts. */
.skip {
  position: absolute; left: 12px; top: -64px; z-index: 40;
  padding: 10px 16px; border-radius: 10px;
  background: var(--primary); color: #fff; font-weight: 600; font-size: 0.9375rem;
  transition: top 150ms var(--ease-out-expo);
}
.skip:focus { top: 12px; }
.wrap { width: 100%; max-width: var(--max); margin-inline: auto; padding-inline: var(--pad); }
.eyebrow {
  font-family: var(--mono); font-size: 0.6875rem; font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--primary);
  margin: 0;
}

/* The icon sprite. One <symbol> per glyph, stroked in currentColor so a nav
   row and a phone tab can use the same one at different weights. */
.ico { width: 1em; height: 1em; flex: none; stroke: currentColor; fill: none;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }


/* ---------- nav ---------- */
header {
  position: sticky; top: 0; z-index: 20;
  background: rgba(10, 11, 15, 0.82);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border-soft);
}
.nav { display: flex; align-items: center; justify-content: space-between; gap: 16px; height: 68px; }
.brand { display: flex; align-items: center; gap: 10px; }
.brand img { width: 26px; height: 26px; object-fit: contain; }
.brand span {
  font-weight: 700; font-size: 1.0625rem; white-space: nowrap;
  letter-spacing: 0.18em; text-transform: uppercase;
}
.nav-links { display: flex; align-items: center; gap: 26px; }
.nav-links a {
  display: inline-flex; align-items: center; min-height: 44px;
  color: var(--text-2); font-size: 0.875rem; font-weight: 500;
  transition: color 150ms var(--ease-out-expo);
}
.nav-links a:hover { color: var(--text); }
/* `.nav-links a` (0,1,1) outranks `.btn-primary` (0,1,0), so without this the
   sign-up button renders muted-grey on orange. Same trap for the ghost. */
.nav-links a.btn-primary { color: #fff; }
.nav-links a.btn-ghost { color: var(--text); }
.nav-links a[aria-current="page"] { border-color: var(--stone); background: var(--surface); }

/* The bar carries three buttons now, so it gives ground in four steps rather
   than one. Each step drops the least useful thing left, in order. */

/* 1. The scroll anchors go first: they are shortcuts to content the page will
      reach by scrolling anyway, and the buttons are destinations it will not. */
@media (max-width: 980px) {
  .nav-links .hide-sm { display: none; }
  .nav-links { gap: 16px; }
}
/* 2. "Get the field app" becomes "Field app". Two labels rather than one with a
      hidden prefix, because display:none takes the unused one out of the
      accessibility tree and a screen reader hears exactly one of them.
      820px, not 760: at a 768px tablet the full bar left the wordmark to wrap
      onto two lines. */
.lbl-short { display: none; }
@media (max-width: 820px) {
  .lbl-long { display: none; }
  .lbl-short { display: inline; }
}
/* 3. The wordmark. The mark alone still says Stratum. */
@media (max-width: 470px) {
  .brand span { display: none; }
  .nav-links { gap: 10px; }
  .nav-links .btn-sm { padding: 8px 12px; font-size: 0.8125rem; }
}
/* 4. Dashboard goes into the menu: whoever is on a narrow screen most likely
      wants the field app, and the menu and the footer both still carry it.
      Earlier than it used to (390px) because the menu button needs the room. */
@media (max-width: 820px) {
  .nav-links .nav-dashboard { display: none; }
}

/* ---------- the phone menu ---------- */
/* Below 980px the section links leave the bar (step 1 above), so this is how a
   phone reaches them. A <details>, not a script: it opens and closes with
   scripting off, from a keyboard, and a screen reader already knows what it is.
   js/menu.js only adds the conveniences: close on a link, on Escape, on a tap
   outside, and when the window grows past the breakpoint. */
.menu { display: none; }
.menu > summary {
  list-style: none; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 40px; border: 1px solid var(--border); border-radius: 10px;
  color: var(--text);
}
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary:hover { border-color: var(--stone); background: var(--surface); }
/* Three bars that turn into a cross: the mark's own strokes, not an icon font. */
.menu-icon { position: relative; width: 16px; height: 12px; }
.menu-icon i { position: absolute; left: 0; right: 0; height: 2px; border-radius: 1px; background: currentColor;
  transition: transform 180ms var(--ease-out-expo), opacity 120ms; }
.menu-icon i:nth-child(1) { top: 0; }
.menu-icon i:nth-child(2) { top: 5px; }
.menu-icon i:nth-child(3) { top: 10px; }
.menu[open] .menu-icon i:nth-child(1) { transform: translateY(5px) rotate(45deg); }
.menu[open] .menu-icon i:nth-child(2) { opacity: 0; }
.menu[open] .menu-icon i:nth-child(3) { transform: translateY(-5px) rotate(-45deg); }
/* The panel hangs off the sticky header (a positioned box) at full width. */
.menu-panel {
  position: absolute; left: 0; right: 0; top: 100%;
  display: grid; padding: 6px var(--pad) 18px;
  /* Solid, not near-solid: inside the header's backdrop-filter a 0.97 alpha
     composites far thinner than it reads, and the hero showed through. */
  background: var(--bg); border-bottom: 1px solid var(--border);
  box-shadow: 0 18px 30px rgba(0, 0, 0, 0.45);
  max-height: calc(100vh - 68px); overflow-y: auto;
}
.nav-links .menu-panel a {
  min-height: 52px; padding: 0 2px; border-bottom: 1px solid var(--border-soft);
  font-size: 1rem; color: var(--text);
}
.nav-links .menu-panel a:last-child { border-bottom: 0; }
/* es/field-app.html: one line saying the links below go to English pages,
   rather than "(en inglés)" after every entry. */
.menu-note {
  margin: 0; padding: 12px 2px 4px;
  font-family: var(--mono); font-size: 0.6875rem; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--stone);
}
@media (max-width: 980px) { .menu { display: block; } }



/* ---------- links in prose ---------- */
.inline-link {
  color: var(--primary); text-decoration: underline;
  text-decoration-thickness: 1px; text-underline-offset: 2px;
  transition: color 150ms var(--ease-out-expo);
}
.inline-link:hover { color: var(--primary-hover); }


/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 44px; padding: 12px 22px; border-radius: 10px;
  font-size: 0.9375rem; font-weight: 600; white-space: nowrap;
  border: 1px solid transparent; cursor: pointer;
  transition: background 150ms var(--ease-out-expo), border-color 150ms var(--ease-out-expo),
    box-shadow 150ms var(--ease-out-expo), transform 150ms var(--ease-out-expo);
}
/* Depth, in three layers: a lit top edge, a tight contact shadow that sits the
   button on the page, and a wide brand-coloured glow at negative spread so it
   reads as light off the button, not a second border. On a near-black page the
   glow is most of what sells it. */
.btn-primary {
  /* Same gradient the dashboard's primary and Strat's send key wear, so the
     marketing site's CTA and the button it promises are one object. */
  background: linear-gradient(160deg, var(--primary-hover), var(--primary));
  color: #fff;
  box-shadow: inset 0 1.5px 0 rgba(255,255,255,0.22), inset 0 -1.5px 0 rgba(0,0,0,0.20),
              0 1px 2px rgba(0,0,0,0.4), 0 8px 24px -10px var(--primary);
}
.btn-primary:hover {
  background: linear-gradient(160deg, var(--primary-hover), var(--primary-hover));
  box-shadow: inset 0 1.5px 0 rgba(255,255,255,0.28), inset 0 -1.5px 0 rgba(0,0,0,0.20),
              0 2px 5px rgba(0,0,0,0.42), 0 16px 34px -12px var(--primary-hover);
}
/* Press inverts the bevel — lit edge to the bottom, the way a real key does. */
.btn-primary:active {
  background: var(--primary);
  box-shadow: inset 0 1.5px 4px rgba(0,0,0,0.38), inset 0 -1px 0 rgba(255,255,255,0.10), 0 1px 1px rgba(0,0,0,0.35);
}
.btn-ghost {
  background: transparent; color: var(--text); border-color: var(--border);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.04), inset 0 -1px 0 rgba(0,0,0,0.22);
}
.btn-ghost:hover { border-color: var(--stone); background: var(--surface); box-shadow: inset 0 1px 0 rgba(255,255,255,0.06), inset 0 -1px 0 rgba(0,0,0,0.22), 0 6px 18px -8px rgba(0,0,0,0.6); }
.btn-ghost:active { box-shadow: inset 0 1.5px 4px rgba(0,0,0,0.3); }
.btn-sm { min-height: 40px; padding: 8px 16px; font-size: 0.875rem; }
/* Lift on hover, press on click. :active is written after :hover deliberately —
   both are (0,2,0) so source order is what makes a press beat a hover. The press
   scales as well as drops, and runs faster than the lift so the release springs. */
.btn:hover { transform: translateY(-2px); }
.btn:active { transform: translateY(0) scale(0.97); transition-duration: 80ms; }


/* ---------- hero ---------- */
.hero {
  position: relative; overflow: hidden;
  padding: clamp(64px, 10vw, 116px) 0 clamp(40px, 6vw, 72px);
  /* Copy on the left, a working pit on the right, and a scrim deciding where one
     ends and the other starts.
     The scrim is the whole trick, and it is opaque --bg for the first third
     rather than a flat wash over everything: the headline sits on the same
     ground it always did, and the photograph only gets to exist once the text
     column is past. A picture never gets a vote on whether the first sentence
     can be read.
     Layers paint first-to-last, so the warm bloom stays on top, the scrim under
     it, the photograph under that, and --bg last as the floor the transparent
     parts of the scrim show through to.
     The file is a 1320px-wide crop, not the 5464px original — see the note in
     assets/. */
  background:
    radial-gradient(900px 460px at 82% -12%, rgba(244, 87, 8, 0.10), transparent 62%),
    linear-gradient(95deg, rgb(10, 11, 15) 0 44%, rgba(10, 11, 15, 0.80) 62%, rgba(10, 11, 15, 0.34) 100%),
    url("../assets/hero-mine.webp") right center / cover no-repeat,
    var(--bg);
}
h1 {
  margin: 18px 0 0;
  font-size: clamp(2.25rem, 6.2vw, 4rem);
  line-height: 1.04;
  letter-spacing: -0.035em;
  font-weight: 800;
  max-width: 15ch;
}
h1 em { font-style: normal; color: var(--primary); }
.lede {
  margin: 22px 0 0; max-width: 56ch;
  font-size: clamp(1.0625rem, 2vw, 1.25rem);
  color: var(--text-2); line-height: 1.55;
}
.cta-row { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 34px; }
.cta-note {
  margin-top: 16px; font-family: var(--mono);
  font-size: 0.75rem; color: var(--stone);
}
/* Text left, photograph right. The second column is deliberately empty — it
   holds the copy off the picture, which is a background rather than a grid item
   so it can bleed past the wrap's max width to the edge of the screen.
   It used to be a floating copy of the logo; the mark is already in the header
   two inches above it, and a photograph of the thing this software is for says
   more than the logo said twice.
   index.html draws its own hero over this frame instead: strata bands and a
   form mock, no photograph (.strata-hero there, played by js/motion.js). */
.hero-grid { display: grid; grid-template-columns: 1.25fr 1fr; gap: clamp(32px, 6vw, 80px); align-items: center; }
@media (max-width: 900px) {
  /* One column, so there is no empty half to hide behind and the horizontal
     scrim above would be sitting under the headline. It turns vertical here:
     near-solid over the copy, the pit surfacing below it. */
  .hero-grid { grid-template-columns: 1fr; }
  .hero {
    background:
      radial-gradient(900px 460px at 82% -12%, rgba(244, 87, 8, 0.10), transparent 62%),
      linear-gradient(to bottom, rgba(10, 11, 15, 0.95) 0 58%, rgba(10, 11, 15, 0.70) 100%),
      url("../assets/hero-mine.webp") center / cover no-repeat,
      var(--bg);
  }
}

/* The logo's strata, redrawn in CSS: five stone bars and one lit. It is the
   page's one repeated mark — a section opener, and the spine of the handoff
   diagram. Decorative, so it is hidden from the accessibility tree. */
.strata { display: flex; flex-direction: column; gap: 5px; margin: 0 0 4px; }
.strata i {
  display: block; height: 5px; border-radius: 2px;
  background: var(--stone); opacity: 0.28;
}
.strata i:nth-child(1) { width: 108px; }
.strata i:nth-child(2) { width: 88px; }
.strata i:nth-child(3) { width: 124px; }
.strata i:nth-child(4) { width: 72px; background: var(--primary); opacity: 1; }
.strata i:nth-child(5) { width: 96px; }


/* ---------- sections ---------- */
section { padding: clamp(64px, 9vw, 108px) 0; border-top: 1px solid var(--border-soft); }
/* A link to a section lands it just under the sticky header (68px) rather than
   beneath it: on a phone the section's own top padding is 64px, 4px short. */
section[id] { scroll-margin-top: 12px; }
h2 {
  margin: 14px 0 0;
  font-size: clamp(1.75rem, 3.6vw, 2.5rem);
  line-height: 1.14; letter-spacing: -0.03em; font-weight: 700;
  max-width: 20ch;
}
.section-lede { margin: 16px 0 0; max-width: 60ch; color: var(--text-2); font-size: 1.0625rem; }


/* ---------- supporting point lists ---------- */
.points { display: grid; grid-template-columns: repeat(4, 1fr); gap: 28px 32px; margin-top: 44px; }
@media (max-width: 900px) { .points { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .points { grid-template-columns: 1fr; } }
.point h3 {
  margin: 0 0 7px; font-size: 0.9375rem; font-weight: 650; letter-spacing: -0.012em;
  display: flex; align-items: baseline; gap: 9px;
}
.point h3::before {
  content: ""; width: 3px; height: 13px; border-radius: 2px;
  background: var(--primary); flex: none; transform: translateY(1px);
}
.point p { margin: 0; color: var(--text-2); font-size: 0.875rem; line-height: 1.6; }


/* ---------- close ---------- */
.close { text-align: center; }
.close .strata { align-items: center; margin-inline: auto; }
.close h2 { margin-inline: auto; }
.close .cta-row { justify-content: center; }
.close .section-lede { margin-inline: auto; }
/* Signing in is not a conversion — it is the door for somebody who already came
   through this page once. As a ghost button beside the primary it competed with
   the last ask on the page; as a line under it, it is still one click away and
   no longer an alternative to the only thing being asked for. */
.close-note { margin: 18px 0 0; font-size: 0.875rem; color: var(--stone); }


/* ---------- footer ---------- */
footer { border-top: 1px solid var(--border-soft); padding: 40px 0 56px; }
.foot {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 20px;
  font-size: 0.8125rem; color: var(--stone);
}
.foot-links { display: flex; flex-wrap: wrap; gap: 24px; }
.foot-links a {
  display: inline-flex; align-items: center; min-height: 44px;
  color: var(--text-2); transition: color 150ms var(--ease-out-expo);
}
.foot-links a:hover { color: var(--text); }
.copyright { margin-top: 6px; }
.tagline { font-family: var(--mono); font-size: 0.6875rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--primary); }


/* ---------- motion ---------- */
/* Still no JS and no IntersectionObserver. Everything below is either a plain
   CSS animation or a scroll-driven one behind @supports, and the rule that makes
   that safe is absolute: NOTHING sets opacity:0 in a base rule. Invisibility only
   ever exists inside a keyframe's `from`, so a browser without scroll-driven
   animations — and the reduced-motion block at the bottom of this file, which
   kills `animation` outright — both land on fully visible content rather than a
   blank page. Break that rule and the site disappears for somebody.

   Two things earn motion here. Entrances, because a page this long needs to feel
   like it is arriving rather than already spent; and the four or five places
   where the movement IS the explanation — the strata drawing itself, the rule
   under the handoff extending step by step, a severity bar growing to its number.
   Everything else holds still on purpose. */
@keyframes rise { from { opacity: 0; transform: translateY(22px); } to { opacity: 1; transform: none; } }

/* The subject enters, the subject settles. `entry 0% entry 40%` reads as "from
   the moment its top edge clears the fold until it is 40% of the way in". */
@supports (animation-timeline: view()) {
  .reveal {
    animation: rise 0.7s var(--ease-out-expo) both;
    animation-timeline: view();
    animation-range: entry 0% entry 40%;
  }

  /* A stagger, done the only way a scroll-driven animation can do one:
     animation-delay is meaningless against a view() timeline, because the
     timeline is scroll position rather than elapsed time. So each child is given
     a slightly later window in the same range instead, and the offset in scroll
     distance is what produces the cascade. Past six, they share the last window —
     eighteen chips do not need eighteen rules. */
  .stagger > * {
    animation: rise 0.6s var(--ease-out-expo) both;
    animation-timeline: view();
    animation-range: entry 0% entry 34%;
  }
  .stagger > *:nth-child(2)    { animation-range: entry 5%  entry 39%; }
  .stagger > *:nth-child(3)    { animation-range: entry 10% entry 44%; }
  .stagger > *:nth-child(4)    { animation-range: entry 15% entry 49%; }
  .stagger > *:nth-child(5)    { animation-range: entry 20% entry 54%; }
  .stagger > *:nth-child(6)    { animation-range: entry 25% entry 59%; }
  .stagger > *:nth-child(n+7)  { animation-range: entry 30% entry 64%; }
}

/* The strata draws itself. This is the page's one repeated mark and the closest
   thing it has to a signature, so it is the thing worth animating: five bars
   extending from the left, and the lit one arriving last because it is the point
   the mark is making. Ranges are ordered 1,2,3,5,4 for exactly that reason. */
/* Scale only, never opacity. Four of these bars are meant to rest at 0.28 and
   the lit one at 1 — an animation that ended on `opacity: 1` would hold all five
   at full strength once it finished and quietly delete the mark's whole point. */
@keyframes strata-draw { from { transform: scaleX(0); } to { transform: none; } }
@supports (animation-timeline: view()) {
  .strata i {
    transform-origin: left center;
    animation: strata-draw 0.5s var(--ease-out-expo) both;
    animation-timeline: view();
  }
  .strata i:nth-child(1) { animation-range: entry 0%  entry 26%; }
  .strata i:nth-child(2) { animation-range: entry 4%  entry 30%; }
  .strata i:nth-child(3) { animation-range: entry 8%  entry 34%; }
  .strata i:nth-child(5) { animation-range: entry 12% entry 38%; }
  .strata i:nth-child(4) { animation-range: entry 18% entry 46%; }
  /* The closing section centres its mark, so the bars have to grow from the
     middle there or they draw off-axis. */
  .close .strata i { transform-origin: center; }
}

/* The hero plays on load rather than on scroll — it is already on screen, and a
   scroll timeline would leave the first thing anyone sees waiting for an input
   they have no reason to give. Time-based, so it needs no @supports: every
   browser that renders this file can run it. */
@keyframes hero-in { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
.hero-grid > div > * { animation: hero-in 0.65s var(--ease-out-expo) both; }
.hero-grid > div > *:nth-child(1) { animation-delay: 60ms; }
.hero-grid > div > *:nth-child(2) { animation-delay: 130ms; }
.hero-grid > div > *:nth-child(3) { animation-delay: 200ms; }
.hero-grid > div > *:nth-child(4) { animation-delay: 270ms; }
.hero-grid > div > *:nth-child(5) { animation-delay: 340ms; }
/* The header gains its shadow as the page leaves the top. Driven by scroll
   position on the root scroller, and linear on purpose: the easing of a
   scroll-linked animation is the reader's own scrolling, and a curve on top of
   it is what makes a sticky header feel like it is lagging behind the page.
   The shadow lives on a pseudo-element so what animates is opacity rather than
   box-shadow, which would repaint the whole bar every frame. */
header::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
  opacity: 0;
}
@supports (animation-timeline: scroll()) {
  @keyframes header-lift { to { opacity: 1; } }
  header::after {
    animation: header-lift 1s linear both;
    animation-timeline: scroll(root);
    animation-range: 0 120px;
  }
}

/* Print takes the page as it stands, not as it was arriving. */
@media print { *, .reveal { animation: none !important; } }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, .reveal { animation: none !important; transition-duration: 0ms !important; }
  /* A zero-duration transform is a jump, not a calm. Drop the travel; the
     shadows stay, because depth is not motion. */
  .btn:hover, .btn:active { transform: none; }
}
