/* The pre-paint ground.
   ---------------------------------------------------------------------------
   The inline bootstrap stamps `ofi-on` on <html> before <body> exists, and this
   pseudo-element covers the viewport from that instant — earlier than any real
   element could, because there is no element yet to insert. Without it there is
   a window, short but real on a cold load, in which the page is visible before
   the overlay mounts: a flash of the site, then a black sheet over it, which
   looks like a fault rather than an intro.

   `ofi-live` is added the moment the overlay div is in the DOM, and switches
   this off — from then on the div is the ground, and only the div, so its fade
   at the end is actually visible instead of hidden behind this. */
html.ofi-on::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 2147483000;
  background: #050609;
  pointer-events: none;
}
html.ofi-on.ofi-live::before { display: none; }

/* ============================================================================
   ONFLOW ADS — the welcome intro ("Converge").
   ============================================================================
   Shown once per browsing session to a visitor WITHOUT a live session: someone
   new, or someone returning whose session has lapsed. Never to a signed-in
   member going about their day. The decision is made by the inline bootstrap
   spliced into every page (onflow/intro.py); this file and intro.js are the
   presentation, loaded only when the intro is actually going to run.

   WHY THE CONVERGE MOTIF. Every page already draws a particle network behind
   it (the `#field` canvas in lux.js). The intro opens by pulling that same
   network inward until it resolves into the brand mark, so the first thing a
   visitor sees is not a logo bolted onto a loading screen — it is the site's
   own background assembling itself into the mark, and then dissolving into the
   page. The intro and the product are visibly one system.

   SELF-CONTAINED ON PURPOSE. This runs on onflowads.com AND on
   docs.onflowads.com, which is a Docusaurus build that loads none of the site's
   CSS. Every value it needs is declared here under `.ofi`, nothing is inherited,
   and nothing leaks: every selector is prefixed, and the overlay never touches
   `body`. The only outside dependency is /brand/logo, which resolves on both
   hostnames (Caddy rewrites the docs subdomain onto /docs/brand/logo, which
   serves the same admin upload with the same bundled fallback).
   ========================================================================= */

.ofi {
  --ofi-bg: #050609;
  --ofi-ink: #F4F5F7;
  --ofi-dim: #A7A8B0;
  --ofi-faint: #6B6C74;
  --ofi-red: #FB2C36;
  --ofi-deep: #E11D2E;
  --ofi-warm: #FF6B4A;
  --ofi-line: rgba(255, 255, 255, .10);
  --ofi-line2: rgba(255, 255, 255, .17);
  --ofi-grad: linear-gradient(120deg, #FF6B4A, #FB2C36 50%, #E11D2E);
  --ofi-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --ofi-disp: 'Space Grotesk', 'Inter', system-ui, -apple-system, sans-serif;
  /* One easing for everything that arrives, one for everything that leaves.
     Two curves across a whole sequence is what makes it read as composed
     rather than as a pile of separate animations. */
  --ofi-in: cubic-bezier(.22, 1, .36, 1);
  --ofi-out: cubic-bezier(.76, 0, .24, 1);

  position: fixed;
  inset: 0;
  z-index: 2147483000;        /* above the site's own chrome and the docs navbar */
  display: grid;
  place-items: center;
  background: var(--ofi-bg);
  color: var(--ofi-ink);
  font-family: var(--ofi-sans);
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
  contain: strict;            /* the overlay can never reflow the page under it */
  cursor: pointer;            /* the whole surface skips */
}

/* The one thing this otherwise self-contained sheet still inherits from the host
   page: `overflow-wrap:anywhere` on <body> (the long-URL safety net at phone
   widths). It licenses a break between ANY two letters, so on a 320-412px phone
   the tagline and the capability chips could split a word mid-way — and the
   overlay CLIPS, so half a broken word is what the visitor's first impression
   would be. Every string here is a short phrase: it wraps between WORDS or not
   at all. */
.ofi,.ofi *{overflow-wrap:break-word;word-break:normal;hyphens:none}

/* The ground: a slow brand wash that is already present on the first frame, so
   the overlay never flashes flat black before the animation starts. */
.ofi::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(1200px 680px at 50% 118%, rgba(225, 29, 46, .20), transparent 62%),
    radial-gradient(900px 520px at 50% -18%, rgba(255, 107, 74, .10), transparent 58%);
  opacity: 0;
  animation: ofiGround 1400ms var(--ofi-in) forwards;
}
@keyframes ofiGround { to { opacity: 1; } }

/* A hairline that fills across the whole sequence. It costs one pixel and it
   changes the feel completely: the wait stops being indefinite and becomes a
   thing with a visible end, which is the difference between "loading" and
   "an intro". */
.ofi__bar {
  position: absolute;
  top: 0; left: 0;
  height: 1.5px;
  width: 100%;
  transform: scaleX(0);
  transform-origin: 0 50%;
  background: var(--ofi-grad);
  box-shadow: 0 0 14px rgba(251, 44, 54, .55);
  opacity: .85;
}
.ofi.is-run .ofi__bar { animation: ofiBar var(--ofi-dur, 5000ms) linear forwards; }
@keyframes ofiBar { to { transform: scaleX(1); } }

.ofi__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.ofi__c {
  position: relative;
  display: grid;
  justify-items: center;
  text-align: center;
  padding: 0 24px;
  max-width: 760px;
}

/* ---- the mark ----------------------------------------------------------- */
.ofi__markwrap { position: relative; display: grid; place-items: center; }

/* The bloom sits behind the mark and swells as the particles land, so the
   convergence appears to deposit energy rather than just stop. */
.ofi__bloom {
  position: absolute;
  width: min(620px, 116vw);
  height: min(620px, 116vw);
  border-radius: 50%;
  background: radial-gradient(circle, rgba(251, 44, 54, .34), rgba(225, 29, 46, .11) 40%, transparent 68%);
  filter: blur(16px);
  opacity: 0;
  transform: scale(.45);
  pointer-events: none;
}
.ofi.is-run .ofi__bloom { animation: ofiBloom 2200ms var(--ofi-in) 620ms forwards; }
@keyframes ofiBloom {
  0%   { opacity: 0;   transform: scale(.45); }
  38%  { opacity: 1;   transform: scale(1.08); }
  100% { opacity: .5;  transform: scale(1); }
}

.ofi__mark {
  position: relative;
  width: clamp(78px, 10.5vw, 112px);
  height: clamp(78px, 10.5vw, 112px);
  object-fit: contain;
  display: block;
  opacity: 0;
  transform: scale(.78);
  filter: blur(12px);
}
.ofi.is-run .ofi__mark { animation: ofiMark 1150ms var(--ofi-in) 780ms forwards; }
@keyframes ofiMark {
  0%   { opacity: 0; transform: scale(.78); filter: blur(12px); }
  55%  { opacity: 1; transform: scale(1.045); filter: blur(0); }
  100% { opacity: 1; transform: scale(1); filter: blur(0); }
}

/* Two rings, staggered and unequal. A single ring reads as a spinner; two of
   different weights and speeds read as a pulse the mark emitted. */
.ofi__ring {
  position: absolute;
  width: clamp(78px, 10.5vw, 112px);
  height: clamp(78px, 10.5vw, 112px);
  border-radius: 50%;
  border: 1px solid rgba(251, 44, 54, .70);
  opacity: 0;
  pointer-events: none;
}
.ofi.is-run .ofi__ring { animation: ofiRing 1750ms var(--ofi-in) 1320ms forwards; }
.ofi__ring--2 { border-color: rgba(255, 107, 74, .40); }
.ofi.is-run .ofi__ring--2 { animation: ofiRing 2100ms var(--ofi-in) 1560ms forwards; }
@keyframes ofiRing {
  0%   { opacity: .9; transform: scale(1); }
  100% { opacity: 0;  transform: scale(3.1); }
}

/* ---- wordmark + rule ---------------------------------------------------- */
.ofi__wm {
  margin-top: 26px;
  font-family: var(--ofi-disp);
  font-weight: 700;
  font-size: clamp(1.6rem, 4.4vw, 2.65rem);
  line-height: 1;
  letter-spacing: .30em;
  white-space: nowrap;
  opacity: 0;
  transform: translateY(14px);
}
.ofi__wm b { font-weight: 700; background: var(--ofi-grad); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
.ofi.is-run .ofi__wm { animation: ofiWm 1100ms var(--ofi-in) 1500ms forwards; }
@keyframes ofiWm { to { opacity: 1; transform: none; letter-spacing: -.022em; } }

.ofi__rule {
  position: relative;
  margin-top: 20px;
  width: min(380px, 66vw);
  height: 1px;
  background: var(--ofi-line);
  overflow: hidden;
}
.ofi__rule::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--ofi-grad);
  transform: scaleX(0);
  transform-origin: 50% 50%;
}
.ofi.is-run .ofi__rule::after { animation: ofiRule 1000ms var(--ofi-in) 1860ms forwards; }
@keyframes ofiRule { to { transform: scaleX(1); } }

/* ---- the three capability lines ----------------------------------------
   Capabilities, deliberately, not statistics. A number on a splash screen is
   a claim the visitor cannot check and that goes stale the moment it is true;
   each of these is a thing the product actually does and can be verified two
   clicks later. */
.ofi__facts {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px 12px;
  margin-top: 30px;
}
.ofi__fact {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px 7px 12px;
  border: 1px solid var(--ofi-line);
  border-radius: 999px;
  background: rgba(255, 255, 255, .028);
  font-size: clamp(.68rem, 1.5vw, .78rem);
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--ofi-dim);
  white-space: nowrap;
  opacity: 0;
  transform: translateY(11px);
}
.ofi__fact i {
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--ofi-grad);
  box-shadow: 0 0 9px rgba(251, 44, 54, .8);
  flex: 0 0 auto;
}
.ofi.is-run .ofi__fact { animation: ofiFact 820ms var(--ofi-in) forwards; }
.ofi.is-run .ofi__fact:nth-child(1) { animation-delay: 2380ms; }
.ofi.is-run .ofi__fact:nth-child(2) { animation-delay: 2600ms; }
.ofi.is-run .ofi__fact:nth-child(3) { animation-delay: 2820ms; }
@keyframes ofiFact { to { opacity: 1; transform: none; } }

.ofi__tag {
  margin-top: 26px;
  font-size: clamp(.64rem, 1.35vw, .74rem);
  font-weight: 600;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--ofi-faint);
  opacity: 0;
}
.ofi.is-run .ofi__tag { animation: ofiTag 900ms var(--ofi-in) 3150ms forwards; }
@keyframes ofiTag { to { opacity: 1; } }

/* ---- skip --------------------------------------------------------------- */
.ofi__skip {
  position: absolute;
  right: max(18px, env(safe-area-inset-right));
  bottom: max(18px, env(safe-area-inset-bottom));
  padding: 9px 16px;
  border: 1px solid var(--ofi-line2);
  border-radius: 999px;
  background: rgba(255, 255, 255, .03);
  color: var(--ofi-dim);
  font: inherit;
  font-size: .74rem;
  font-weight: 650;
  letter-spacing: .02em;
  cursor: pointer;
  opacity: 0;
  transition: color .2s, border-color .2s, background .2s;
}
.ofi.is-run .ofi__skip { animation: ofiTag 600ms var(--ofi-in) 1100ms forwards; }
.ofi__skip:hover { color: var(--ofi-ink); border-color: var(--ofi-line2); background: rgba(255, 255, 255, .07); }
.ofi__skip:focus-visible { outline: 2px solid var(--ofi-red); outline-offset: 3px; color: var(--ofi-ink); }

/* ---- exit ---------------------------------------------------------------
   The content leaves before the ground does, so the page is revealed by the
   overlay lifting rather than by everything vanishing at once. */
.ofi.is-out .ofi__c,
.ofi.is-out .ofi__skip,
.ofi.is-out .ofi__bar { opacity: 0; transition: opacity 340ms var(--ofi-out); }
.ofi.is-out {
  opacity: 0;
  transform: scale(1.035);
  transition: opacity 620ms var(--ofi-out) 120ms, transform 760ms var(--ofi-out) 60ms;
  pointer-events: none;
}
/* The mark is exempt: on pages that have a brand mark in their header it flies
   to it (see intro.js), so it must not fade with the rest. */
.ofi.is-out .ofi__mark { opacity: 1; }
.ofi__mark.ofi-fly {
  position: fixed;
  margin: 0;
  z-index: 2147483001;
  transition: transform 760ms var(--ofi-out), opacity 220ms linear 560ms;
  will-change: transform;
}

/* ---- reduced motion -----------------------------------------------------
   The established behaviour in this codebase is to skip motion entirely rather
   than to serve a slower version of it, so this composes the final frame at
   once and holds it briefly. intro.js shortens the whole sequence to match. */
@media (prefers-reduced-motion: reduce) {
  .ofi::before,
  .ofi .ofi__bar,
  .ofi .ofi__bloom,
  .ofi .ofi__mark,
  .ofi .ofi__wm,
  .ofi .ofi__rule::after,
  .ofi .ofi__fact,
  .ofi .ofi__tag,
  .ofi .ofi__skip { animation: none !important; opacity: 1; transform: none; filter: none; }
  .ofi .ofi__wm { letter-spacing: -.022em; }
  .ofi .ofi__rule::after { transform: scaleX(1); }
  .ofi .ofi__ring { display: none; }
  .ofi.is-out { transition: opacity 200ms linear; transform: none; }
  .ofi__mark.ofi-fly { transition: opacity 200ms linear; }
}

@media (max-width: 560px) {
  .ofi__wm { letter-spacing: .22em; }
  .ofi__facts { gap: 8px; margin-top: 24px; }
  .ofi__fact { padding: 6px 11px 6px 10px; }
  .ofi__tag { letter-spacing: .18em; margin-top: 22px; }
}

/* ---- short and narrow viewports ----------------------------------------
   The composition is a five-element vertical stack, which is the one thing
   that cannot simply scale: on a landscape phone or a split-screen window
   there is height for the mark and the wordmark and very little else. The
   overlay clips (overflow:hidden), so anything that does not fit is not merely
   cramped, it is invisible — a tagline half a line tall reads as a bug.

   So the stack tightens first, and then sheds from the BOTTOM of the
   hierarchy: the capability chips go before the tagline, the tagline goes
   before the wordmark, and the mark never goes. What survives at any height is
   always a complete composition rather than a truncated one. */
@media (max-height: 540px) {
  .ofi__mark, .ofi__ring { width: clamp(54px, 7.5vw, 76px); height: clamp(54px, 7.5vw, 76px); }
  .ofi__wm { margin-top: 15px; font-size: clamp(1.3rem, 3.6vw, 1.95rem); }
  .ofi__rule { margin-top: 13px; }
  .ofi__facts { margin-top: 15px; gap: 7px 8px; }
  .ofi__fact { padding: 5px 10px 5px 9px; font-size: .68rem; }
  .ofi__tag { margin-top: 14px; }
  .ofi__bloom { width: min(420px, 110vw); height: min(420px, 110vw); }
}
@media (max-height: 400px) {
  .ofi__facts { display: none; }
}
@media (max-height: 300px) {
  .ofi__rule, .ofi__tag { display: none; }
  .ofi__wm { margin-top: 12px; }
}

/* Very narrow: the tagline is the longest single string on the overlay, and
   generous tracking is what makes it wrap badly rather than its length. */
@media (max-width: 380px) {
  .ofi__tag { letter-spacing: .13em; }
  .ofi__fact { font-size: .67rem; }
  .ofi__rule { width: min(300px, 74vw); }
}

/* A pointer-less device gets no hover affordance, so the skip control has to
   read as a control on its own. */
@media (hover: none) {
  .ofi__skip { background: rgba(255, 255, 255, .07); color: var(--ofi-ink); }
}

/* Thumb-sized skip on touch: 9px of padding around a .74rem label is a ~34px
   box, under the 44px floor the rest of the site keeps. Grown from the inside
   so it stays pinned to the same corner and the label stays centred. */
@media (pointer: coarse) {
  .ofi__skip {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
}
