/* LAYER: site — The marketing site's and the docs' own layout: section shells, the plans grid,
   the feature table, and the headline face. */
@layer site {


/* ---------- The headline face ----------
   --display is this site's and the docs' alone — the two pages that fetch
   Mozilla Text — which is why it lives here and the reading face does not.
   `--body` and its fallback are brand.css's: every Drift surface reads in Inter,
   so the token belongs in the file all of them load.

   THE RULE THAT APPLIES TO BOTH: a page must NAME the face in CSS and REQUEST it
   in its own <head>. A name the page never fetches does not fail, it falls
   silently through to the stack behind it, which looks exactly like nothing
   happened.

   Mozilla Text is variable across 200..700. The docs request that whole range
   and use 500 and 700 from it; the marketing page requests 700 alone and states
   700 everywhere it uses the face. Match the request to the weights the page
   actually sets: an unrequested weight is SUBSTITUTED rather than refused, so a
   rule asking for one the page never fetched reads as a rule that had no effect.

   ---------- Metric-matched fallback ----------
   The face is fetched from a CDN with `display=swap`, so the page renders in a
   fallback and then re-renders in the real thing. If the two measure differently
   the text re-wraps and everything below it moves — a layout shift the reader
   watches happen.

   So the fallback pins ONE local face rather than leaving it to `system-ui`,
   which resolves to a different font on every OS and cannot be corrected for,
   and `size-adjust` scales it until its advance width matches.

   THE PERCENTAGE IS MEASURED, not guessed. The same 130-character string set at
   100px, rendered in each face and read back with getBoundingClientRect:

       Mozilla Text   6464.47
       Arial          5908.80      → 6464.47 / 5908.80 = 109.40%

   (Inter's twin is 105.00%, in brand.css, from the same measurement.) Re-measure
   if the face is ever changed or re-versioned; a stale ratio is worse than none,
   because it shifts the text in the other direction.

   ONLY the width is corrected, and there are no ascent/descent overrides. That
   is a decision rather than an omission. Every line-height on this site is a
   unitless multiplier, so a line box is a function of font-size alone and the
   faces' vertical metrics cannot change a block's height — only re-wrapping can,
   and re-wrapping is width. Overrides would move the glyphs inside a box whose
   size is already settled, which is not the problem being solved, and they
   interact with size-adjust in a way this file would then be asserting without
   having measured it. */
/* SELF-HOSTED, like the reading face in brand.css and for the same two reasons
   (cloud/shared's fonts/README.md): a render-blocking third-party request, and a
   sovereignty claim that a call to a US ad company contradicts. Two subsets,
   each carrying the unicode-range of that exact file. */
@font-face {
	font-family: 'Mozilla Text';
	font-style: normal;
	font-weight: 200 700;
	font-display: swap;
	src: url('/shared/fonts/mozilla-text-latin.woff2') format('woff2');
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
		U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
		U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
	font-family: 'Mozilla Text';
	font-style: normal;
	font-weight: 200 700;
	font-display: swap;
	src: url('/shared/fonts/mozilla-text-latin-ext.woff2') format('woff2');
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
		U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
		U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
		U+A720-A7FF;
}

@font-face {
	font-family: "Mozilla Text Fallback";
	src: local("Arial"), local("Helvetica"), local("Liberation Sans");
	size-adjust: 109.40%;
}

:root {
	--display: "Mozilla Text", "Mozilla Text Fallback", system-ui,
		-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
		"Helvetica Neue", sans-serif;
}

/* ---------- Base ---------- */
/* Smooth scrolling ONLY when a fragment is being visited — never by default.
 *
 * `scroll-behavior: smooth` on the root does not just soften anchor links. It
 * makes the browser animate EVERY scroll it performs itself, and the one it
 * performs on every reload is scroll restoration. So refreshing any page left
 * the reader at the top for a beat and then slid the whole document down to
 * where they had been: 46 scroll events crawling 1 → 755 over about half a
 * second on /docs/, measured. On longer pages it restored instantly and then
 * smooth-nudged the last twenty pixels, which reads as a twitch rather than a
 * slide. Stripping this one declaration on the wire took both to a single jump
 * straight to the restored position.
 *
 * It looked like the docs TOC because the TOC is the most obviously generated
 * thing on the page and does not render on a phone. It was never the TOC: the
 * prose column's document-space top is a single value at every scroll offset on
 * every page, and load-time CLS is 0.
 *
 * :has(:target) is what keeps the good half. A fragment is targeted exactly when
 * someone follows an in-page link, which is the case worth animating, and never
 * during a restore, which has no fragment. Where :has is not supported the rule
 * is simply dropped and anchors jump instantly — the animation is what degrades,
 * the bug cannot come back, which is the right way round for these two.
 *
 * Wrapped in prefers-reduced-motion because a page that scrolls itself is
 * motion, and the previous blanket rule never asked. */
html {
	scroll-behavior: auto;
}

@media (prefers-reduced-motion: no-preference) {
	html:has(:target) {
		scroll-behavior: smooth;
	}
}


/* ---------- Universal section shell ---------- */
section {
	width: 100vw;
	display: flex;
	justify-content: center;
	position: relative;
	z-index: 900;
	padding-top: 2rem;
}


/* ---------- Headings ---------- */
h2 {
	margin-bottom: 1rem;
	font-size: 1.9rem;
	color: var(--ink);
	font-weight: 300;
}

h3 {
	margin-bottom: 1.5rem;
	font-size: 1.15rem;
	color: var(--ink);
	font-weight: 300;
	margin-top: -1rem;
}

p {
	color: var(--ink);
}

/* ---------- Buttons: site-specific parts only ----------
   The button itself is brand.css's `.btn-raised` — raised at rest, springy on
   release, slamming flat under `.pressed`. Spacing is `.btns`, also brand.css.

   What used to be here: `margin-top: 2rem; margin-right: 1rem` on the buttons
   themselves, and a `.btn-docs` variant. Both are gone. A margin on a button is
   a claim about what sits next to it, which the button cannot know — see the
   note on `.btns` in brand.css for the phone-width bug it caused on /docs/.

   What is left is one colourway this site uses and the platform does not. */
.btn-rev {
	background: #f1f1f1;
	/* The fill is a literal, so the label is one too — the same rule as a triad
	   fill, for the same reason. As --ink this read light-on-light at 1.05:1 the
	   moment the palette was re-grounded. */
	color: var(--on-brand);
	border: 2px solid var(--toy-edge);
}

/* ---- Header positioning -----------------------------------------------------
 * The shared navbar deliberately does not position itself — its appearance is in
 * brand.css, and where a header sits is the page's business. This
 * site pins it, as it always has: the announcement strip and the navbar are
 * BOTH fixed and stacked — not "scrolls away, then a sticky nav appears" — so
 * they can never overlap, and .hero's top padding clears the pair.
 *
 * These three rules were lost when the header moved into the component and had
 * to be put back; without them the header scrolled away with the page. If you
 * are adding a third band to the header, it goes here too, and .hero /
 * .navbar-secondary have to account for it. */
.announce-bar {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  z-index: 1000;
}
.navbar {
  position: fixed;
  top: var(--announce-h);
  left: 0;
  width: 100vw;
  z-index: 999;
  /* AT REST THE HEADER IS PART OF THE PAGE. The edge brand.css gives it is what
     separates a header from content SCROLLING UNDER IT, and at the top there is
     nothing under it yet — the hero simply continues, so a rule across the page
     draws a division that is not there.
     TRANSPARENT, not `none`. The border keeps its 2px whichever state it is in,
     so gaining the edge cannot move the header or the 100vh of page below it by
     two pixels. navbar.js publishes the state; this decides what it means. */
  border-bottom-color: transparent;
  transition: border-bottom-color 0.15s ease;
}
/* LIGHT THEME: the header is the same plate as the hero under it. brand.css gives
 * every navbar the desk grey, which is right where the page below is also grey — but
 * this site's hero is the white plate now, so a grey header drew a band across the
 * top of it and re-introduced exactly the division the transparent border above
 * exists to avoid.
 *
 * Only this site, and only light. The docs keep the grey header because their pages
 * keep the grey ground, and under dark both are already the same near-black.
 *
 * @layer site is enough here — brand.css's `.navbar` is in @layer brand, which loses
 * to this one. No unlayered escape needed, unlike the pressed-button rule. */
:root:not([data-theme="dark"]) .navbar { background: var(--panel); }
:root[data-scrolled] .navbar { border-bottom-color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  .navbar { transition: none; }
}

/* NOTHING HERE FOR .navbar-cta ANY MORE. This site's header no longer carries
   one (navbar.html), so the colourway and the phone-width hide that used to live
   here matched nothing and were removed with it. HQ's Log out is the same class
   on a different surface; it was never reached from this file, which HQ does not
   load, and is unaffected. */

/* THE STRIP LEAVES, THE NAVBAR STAYS. The pair rides the shared scroll-linked
   retraction, but only as far as the strip's own height: at rest the strip sits
   above the navbar as always, and once you have scrolled it slides off the top
   and the navbar comes to rest against the edge in its place.

   THE CAP IS THE WHOLE RULE. navbar.js publishes --header-shift up to the
   combined height of both bands, which is what the docs want — their entire
   header leaves. Here `min()` stops it at --announce-h, so the travel is exactly
   the strip and the navbar can never retract past top:0. One function of one
   token, rather than a second threshold in script that would have to agree with
   this file about a number.

   THE COLOURED LINES COME WITH IT. .navbar-accent is the navbar's first child
   and brand.css lays the navbar out as a column, so the triad is already its top
   4px. It rides up with the bar and ends flush against the viewport edge; there
   is nothing to pin separately, and pinning it separately is what would let it
   drift away from the bar it belongs to.

   Scoped by `.announce-bar ~ .navbar`, and that sibling combinator is doing real
   work rather than decoration: the docs load this stylesheet too, have NO
   announce bar, and their sub-navbar is deliberately coordinated with the
   retraction — it rides the same --header-shift so it comes to rest against the
   top edge as the navbar leaves. Capping the docs navbar while its sub-navbar
   kept moving would tear that band in half. No announce bar, no match.

   `body` on the first selector because brand.css sets the same property at the
   same specificity; an unprefixed class loses that tie silently, which is
   exactly how the strip carried on sliding while the navbar had already
   stopped. */
body .announce-bar,
.announce-bar ~ .navbar {
  transform: translateY(calc(-1 * min(var(--header-shift, 0px), var(--announce-h))));
}

/* ---- The EU line, in the announcement strip --------------------------------
 * The strip carries one standing claim about where Drift runs. It used to close
 * the hero as a band of its own; it is the header's band now, and this is the
 * same object moved rather than a second one written.
 *
 * IT TAKES THE WHOLE STRIP. brand.css fills .announce-bar with ink and states
 * why its height is fixed: shared chrome that changes size between surfaces is
 * worse than no shared chrome. So the line does not add padding and grow the
 * band — it takes 100% of whatever --announce-h currently is and centres itself
 * in it, which holds at the 640px breakpoint where brand.css restates that token
 * without this file having to know the number.
 *
 * The fill and the ink are the line's, and they cover the strip's own ink
 * completely. That is deliberate: this band is the one differently-grounded
 * thing above the hero, and a strip half ink and half desk would read as two
 * bars. Only this site has an .eu-line, so only this site's strip changes.
 *
 * It draws none of its own edges and neither is missing: the navbar under it
 * carries the header's rule, and THE FILL IS THE SEPARATION. */
.announce-bar .eu-line {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  padding: 0 var(--padding);
  background: var(--bg);
  border: none;
  font-size: 1.05rem;
  font-weight: 300;
  color: var(--ink);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
}
.announce-bar .eu-line b { font-weight: 600; }

/* On hover the line slides up and out, revealing a "Read our story" prompt
   linking to /docs/why/made-in-europe/. Two faces stacked in a track masked to
   one line tall; the track slides up exactly one line on hover and back down on
   hover-out. */
.announce-bar .eu-line-mask {
  display: block;
  height: 1.5em;
  overflow: hidden;
}
/* The line is a two-face slider inside a 1.5em mask, so it gets exactly ONE line
   and anything longer is clipped rather than wrapped — invisibly, because
   overflow:hidden does not announce itself. At 390px "Proudly made and hosted in
   the European Union." wraps, and the strip reads "…in the European" and stops
   mid-phrase. Shortened rather than shrunk: "Europe" says the same thing in a
   quarter of the width, and the bold and the flag both stay. */
.eu-short { display: none; }
@media (max-width: 640px) {
  .eu-long { display: none; }
  .eu-short { display: inline; }
}
.announce-bar .eu-line-track {
  display: block;
  transition: transform 0.35s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.announce-bar .eu-line-face {
  display: block;
  height: 1.5em;
  line-height: 1.5em;
}
.announce-bar .eu-line-face--story {
  color: var(--brand-2);
  font-weight: 300;
}
.announce-bar .eu-line-arrow {
  display: inline-block;
  transition: transform 0.35s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.announce-bar .eu-line:hover .eu-line-track,
.announce-bar .eu-line:focus-visible .eu-line-track {
  transform: translateY(-1.5em);
}
.announce-bar .eu-line:hover .eu-line-arrow,
.announce-bar .eu-line:focus-visible .eu-line-arrow {
  transform: translateX(4px);
}
@media (prefers-reduced-motion: reduce) {
  .announce-bar .eu-line-track,
  .announce-bar .eu-line-arrow { transition: none; }
}

/* The marketing wordmark holds 3rem at EVERY width. The shared component drops
   to 1.6rem below 900px for the app surfaces — HQ and the docs, whose navbars
   carry tabs, a badge and an account cluster and where a masthead-sized logotype
   competes with the controls. This navbar carries two links; here the wordmark
   is the point.

   `.announce-bar ~ .navbar .title` is (0,0,2,1) against the component's
   `.navbar .title` at (0,0,1,1), so it wins on SPECIFICITY and does not depend
   on which file loads last — and it can only match a page that has an announce
   bar, which is this site and nothing else. */
.announce-bar ~ .navbar .title { font-size: 3rem; }

@media (max-width: 640px) {
  /* (The 44px floor on .right lives in brand.css, where it fixes the same class
     of drift on every surface — a chrome bar's height should not depend on what
     happens to be inside it.) */

  /* No wordmark override here any more — the shared component now keeps 3rem at
     every width, so this file has nothing to correct. */
}

} /* @layer site */
