/* ============================================================================
   SELF-HOSTED FONTS. Both are VARIABLE files, so one download covers every
   weight the site uses (Archivo 600/700, Inter 400/600) — 83 KB for all four.

   These were served from the Google CDN until 2026-08-14. That was prototype
   convenience, and FACTS type.hosting always said production self-hosts. It was
   also the ONLY third-party request the site made, which meant every visitor's
   IP reached a third party before they had read a word. Removing it is what
   lets the privacy page say, truthfully and without qualification, that this
   site calls nobody.

   font-display: swap — text renders in the fallback immediately rather than
   waiting on the download, so the page is readable before the font arrives.
   ============================================================================ */
@font-face {
  font-family: 'Archivo';
  src: url('/assets/fonts/archivo-latin.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  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: 'Inter';
  src: url('/assets/fonts/inter-latin.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  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;
}

/* ============================================================================
   omnihub.css — the house sheet for omnihubcrm.app.

   Every value below is named after its FACTS.json key. There is no hard-coded
   colour, size or duration outside :root. If you need a value that is not here,
   you need a ruling, not a new number.

   INHERITANCE. Tokens are LaunchLocal's, unchanged — the ramp is lifted from
   the LaunchLocal site's house-sheet v2 so the two sites are the same system.
   The OmniHub-specific part is the GROUND SPLIT (FRESNEL GUIDELINES.md §1):

       chrome  = ink      header, nav, footer. Where the brand register lives.
       content = paper    the reading area.
       structure = blue   rules, frames, links. ON PAPER ONLY.
       accent  = orange   the active item. ONE per page.

   ⚠ CHROME EROSION is the named failure mode. Ink chrome gets "lightened for
   airiness" one component at a time and ends as blue-on-white, which is exactly
   what LaunchLocal's palette amendment exists to prevent. Do not lighten .chrome.
   ============================================================================ */

:root{
  /* --- palette.* (FACTS.json, inherited verbatim) --- */
  --ink:#1E1E1E;
  --paper:#FFFFFF;
  --blue:#0C65F5;              /* ON PAPER ONLY — 5.01:1. On ink it is 3.33:1 and fails. */
  --blue-light:#5A95F8;        /* ON INK ONLY — the only blue permitted there. */
  --signal-orange:#FB940F;     /* the lit thing. One per composition. */
  --ink-soft:#333333;
  --grey-on-paper:#6A6A73;
  --grey-on-ink:#8A94A3;
  --edge-subtle-on-ink:#33373E;
  --edge-subtle-on-paper:#E6E6E9;
  --edge-strong-on-ink:#6E7889;
  --edge-strong-on-paper:#6A6A73;
  --text-on-ink-floor:#7F8A99; /* holds against TRUE ink only — see the panel note below */

  /* --- palette._web_text_ramp (screen only) --- */
  --text-strong-on-ink:#F1F2F4;
  --text-body-on-ink:#C6CAD2;
  --text-muted-on-ink:#8A94A3;

  /* --- pane.edge_width_px --- */
  --pane-edge:1px;

  /* --- type.* --- */
  --font-display:'Archivo',system-ui,sans-serif;
  --font-text:'Inter',system-ui,sans-serif;

  --step-display:clamp(40px,11.2vw,180px);
  --step-huge:clamp(36px,8.6vw,112px);
  --step-page:clamp(30px,6.4vw,84px);
  --step-section:clamp(26px,4.6vw,40px);
  --step-subhead:clamp(19px,2.6vw,24px);
  --size-body:18px;
  --size-label:13px;
  --size-caption:14.5px;

  --w-display:700;
  --w-heading:600;
  --w-body:400;
  --w-label:600;

  --lh-display:0.94;
  --tr-display:-0.02em;
  --tr-heading:-0.022em;
  --tr-subhead:-0.015em;

  /* --- motion.specimens (inherited verbatim) --- */
  --dur-signal-dot:200ms;
  --dur-section-reveal:400ms;
  --dur-btn-hover:120ms;
  --dur-btn-press:80ms;
  --dur-link-underline:120ms;
  --dur-form-focus:120ms;
  --dur-nav:240ms;
  --ease-standard:cubic-bezier(0.2,0,0,1);

  /* --- layout --- */
  --shell:1180px;
  --gutter:clamp(20px,5vw,56px);
  --unit:22px;                 /* one body line-height = the clearance unit */
}

@media (prefers-reduced-motion:reduce){
  :root{
    --dur-signal-dot:0ms;--dur-section-reveal:0ms;--dur-btn-hover:0ms;
    --dur-btn-press:0ms;--dur-link-underline:0ms;--dur-form-focus:0ms;--dur-nav:0ms;
  }
}

/* ============================================================ reset + base */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;
  background:var(--paper);
  color:var(--ink);
  font-family:var(--font-text);
  font-size:var(--size-body);
  font-weight:var(--w-body);
  line-height:calc(var(--unit) / var(--size-body) * 1em);
  -webkit-font-smoothing:antialiased;
}
img,svg,video{max-width:100%;height:auto;display:block}

/* Tabular figures. FACTS type.tabular_figures is MANDATORY on every number.
   ⚠ The `font:` shorthand silently resets font-variant-numeric. Never use it. */
.num,time,td,th,.price,.duration,.count{font-variant-numeric:tabular-nums}

/* The hidden attribute must beat layout. `.field--check` sets display:flex, and
   an author display rule overrides the UA's [hidden]{display:none} — which is
   why the post-mode "Keep me signed in" checkbox was VISIBLE on the live home
   page and 404 in handoff mode, offering a control that does nothing. Found in
   the wild, not in review. Any element the auth switch hides needs this. */
[hidden]{display:none !important}

h1,h2,h3{font-family:var(--font-display);margin:0}
h1{font-size:var(--step-page);font-weight:var(--w-display);line-height:var(--lh-display);letter-spacing:var(--tr-display)}
h2{font-size:var(--step-section);font-weight:var(--w-heading);letter-spacing:var(--tr-heading)}
h3{font-size:var(--step-subhead);font-weight:var(--w-heading);letter-spacing:var(--tr-subhead)}
p{margin:0 0 var(--unit)}

/* ============================================================ legal prose
   Terms and Privacy are the only long-form reading on the site. Everywhere else
   spacing comes from section structure, so headings carry margin:0 — which left
   these two pages with headings sitting directly on top of their paragraphs.

   A heading needs far more space ABOVE than below: the gap above separates it
   from the previous section, the small gap below binds it to the text it
   introduces. Equal spacing makes a heading float between two blocks and belong
   to neither. */
.legal h2{
  font-size:var(--step-subhead);
  margin:calc(var(--unit) * 2.2) 0 calc(var(--unit) * .5);
  line-height:var(--lh-display);
}
.legal > :first-child{margin-top:0}
.legal p{max-width:62ch;line-height:var(--lh-body)}
.legal p:last-child{margin-bottom:0}

/* ============================================================ features hub
   The hub lists all 74 pages grouped under their 24 families, with the family
   list pinned beside it. Single column on a phone: a sticky sidebar on a small
   screen eats the reading area it is meant to help you navigate. The sidebar
   only appears once there is width to spare. */
.hub{display:grid;gap:calc(var(--unit) * 2)}
@media (min-width:900px){
  .hub{grid-template-columns:minmax(12rem,15rem) 1fr;gap:calc(var(--unit) * 3);align-items:start}
  .hub__nav{position:sticky;top:var(--unit);max-height:calc(100vh - var(--unit) * 2);overflow-y:auto}
}
.hub__nav ol{list-style:none;margin:0;padding:0}
.hub__nav li{margin-bottom:calc(var(--unit) * .35)}
.hub__nav a{font-size:var(--size-caption);color:var(--grey-on-paper);text-decoration:none}
.hub__nav a:hover,.hub__nav a:focus-visible{color:var(--blue);text-decoration:underline}
/* Anchor targets sit under nothing, but a jump that lands a heading hard against
   the viewport top reads as a broken page. Give it room. */
.hub__family{scroll-margin-top:calc(var(--unit) * 2)}
.hub__family + .hub__family{margin-top:calc(var(--unit) * 2.5);
  padding-top:calc(var(--unit) * 2.5);border-top:var(--pane-edge) solid var(--edge-subtle-on-paper)}
.hub__family h2{font-size:var(--step-subhead);margin:0}
.hub__pages{list-style:none;margin:calc(var(--unit) * 1.2) 0 0;padding:0;display:grid;gap:var(--unit)}
.hub__pages li{max-width:62ch}
.hub__pages strong{font-weight:var(--w-label)}
/* The canonical page of each family is the one to read first. Marked with a
   word, not a colour: orange is spent on the header pane, and a coloured tag
   here would be a second lit element. */
.hub__canonical{font-size:var(--size-caption);color:var(--grey-on-paper);font-weight:var(--w-body)}
ul,ol{margin:0 0 var(--unit);padding-left:1.2em}

.shell{max-width:var(--shell);margin-inline:auto;padding-inline:var(--gutter)}
.label{font-size:var(--size-label);font-weight:var(--w-label);letter-spacing:.02em}
.caption{font-size:var(--size-caption);color:var(--grey-on-paper)}
.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}

/* Links. On paper, blue. Underline grows from the left (motion 7). */
a{color:var(--blue);text-decoration:none;background-image:linear-gradient(currentColor,currentColor);
  background-size:0 1px;background-repeat:no-repeat;background-position:0 100%;
  transition:background-size var(--dur-link-underline) ease-out}
a:hover,a:focus-visible{background-size:100% 1px}
a:focus-visible{outline:var(--pane-edge) solid var(--blue);outline-offset:3px}

/* ============================================================ the pane
   The pane edge is the most-repeated brand element. CONSTANT WEIGHT, everywhere.
   Nothing crosses an edge. Content is inset from it. */
.pane{border:var(--pane-edge) solid var(--edge-subtle-on-paper);padding:calc(var(--unit) * 1.5)}
.on-ink .pane,.chrome .pane{border-color:var(--edge-subtle-on-ink)}

/* THE lit pane. orange_budget: ONE per page, never two, max 8% of the composition. */
.pane--lit{position:relative}
.pane--lit::before{
  content:"";position:absolute;left:calc(var(--pane-edge) * -1);top:0;bottom:0;width:3px;
  background:var(--signal-orange);
}
/* ⚠ orange on paper may ONLY be a bounded shape or a filled block — never text,
   never a 1px rule, never an icon stroke (FRESNEL GUIDELINES §1). The 3px bar
   above is a shape, and it sits on the pane edge, not in the text. */

/* ============================================================ ink surfaces */
.on-ink,.chrome{background:var(--ink);color:var(--text-body-on-ink)}
.on-ink h1,.on-ink h2,.on-ink h3,.chrome h1,.chrome h2{color:var(--text-strong-on-ink)}
.on-ink a,.chrome a{color:var(--blue-light)}
.on-ink .caption,.chrome .caption{color:var(--text-muted-on-ink)}
/* ⚠ --text-on-ink-floor (#7F8A99) is 4.76:1 against TRUE ink #1E1E1E ONLY. On any
   lifted panel it drops to ~4.43:1 and fails AA. Panels use --text-body-on-ink. */
.panel-on-ink{background:#232427;color:var(--text-body-on-ink)}

/* ============================================================ chrome
   ⚠ DO NOT LIGHTEN. See the chrome-erosion note at the top of this file. */
.chrome{border-bottom:var(--pane-edge) solid var(--edge-subtle-on-ink)}
.chrome__bar{display:flex;align-items:center;gap:var(--unit);
  padding-block:calc(var(--unit) * .8);flex-wrap:wrap}
.chrome__logo{display:block}
.chrome__logo img{height:32px;width:auto}   /* lockup minimum is 120px wide */

/* 'Brought to you by LaunchLocal' — operator instruction 2026-09-03,
   REVISED the same day after the operator looked at the rendered header.

   WHAT HE ASKED FOR, and it is three separate things:
     1. the PHRASE at 50% of its previous size  -> 16px becomes 8px
     2. the lockup DIRECTLY RIGHT of the phrase, on the same line
     3. the whole line starting under the MIDPOINT of the OmniHub lockup

   ⚠️ ONLY THE PHRASE HALVED. The lockup stays 16px — he named the phrase, and
   an 8px mark would be unreadable. The two are no longer the same size and that
   is deliberate, so do not 'restore' the pairing.

   THE INDENT IS DERIVED, NOT TYPED. --chrome-logo-w is the lockup's real width
   and the indent is half of it, so 'under the 50% position' stays true if the
   lockup is ever resized. A hardcoded 80px would silently become a lie.

   🔑 WHY THIS IS BUILT SO IT CANNOT STACK.
   The operator's screenshot showed the lockup on its own line BELOW the phrase.
   I could not reproduce that from the served bytes: the markup is a <p> holding
   the text and the <a>, and the rule was already display:flex, which cannot wrap
   without flex-wrap. So the cause is UNDIAGNOSED, and the honest response to an
   undiagnosed layout fault is to remove every way it could happen rather than to
   claim a fix. Hence: explicit flex-wrap:nowrap, width:max-content so the line is
   never squeezed, and flex-shrink:0 on the mark so it can never be compressed to
   nothing. If it still stacks, the cause is outside this rule and worth finding.

   The lockup keeps its own colours — another company's mark, inheriting nothing
   from this page. Only the words take chrome's muted ink. */
.chrome__brand{display:flex;flex-direction:column;align-items:flex-start;
  gap:calc(var(--unit) * 0.35);--chrome-logo-w:160px}
.chrome__by{align-self:flex-start;margin:0 0 0 calc(var(--chrome-logo-w) / 2);
  display:flex;flex-wrap:nowrap;align-items:center;gap:.5em;width:max-content;
  font-size:8px;line-height:1;white-space:nowrap;color:var(--text-muted-on-ink)}
.chrome__by a{display:block;line-height:0;flex:0 0 auto}
.chrome__by img{height:16px;width:auto;display:block}

/* On a narrow screen the indent would push the line off the edge, so it drops
   away. The phrase and the mark keep their sizes: they are already small, and
   shrinking 8px further is how type stops being type. */
@media (max-width:520px){
  .chrome__by{margin-left:0}
}
.chrome__nav{margin-left:auto;display:flex;align-items:center;gap:calc(var(--unit) * 1.2)}
.chrome__nav a{font-size:var(--size-label);font-weight:var(--w-label)}
.chrome__nav a[aria-current="page"]{color:var(--text-strong-on-ink)}
.site-footer{border-top:var(--pane-edge) solid var(--edge-subtle-on-ink);
  padding-block:calc(var(--unit) * 2);font-size:var(--size-caption)}
.site-footer nav{display:flex;flex-wrap:wrap;gap:var(--unit);margin-bottom:var(--unit)}

/* ============================================================ buttons
   Motion 4: hover is COLOUR ONLY — background blue -> blue_light AND the label
   paper -> ink. Paper on blue_light is 2.96:1 and fails; ink on blue_light is
   5.63:1, which is a pair the book already approves. No lift, no shadow, no scale. */
.btn{
  display:inline-block;font-family:var(--font-text);font-size:var(--size-body);
  font-weight:var(--w-label);line-height:1.2;padding:.7em 1.3em;border:var(--pane-edge) solid var(--blue);
  background:var(--blue);color:var(--paper);cursor:pointer;background-image:none;
  transition:background-color var(--dur-btn-hover) ease-out,color var(--dur-btn-hover) ease-out;
}
.btn:hover{background:var(--blue-light);border-color:var(--blue-light);color:var(--ink)}
.btn:active{background:var(--blue);border-color:var(--blue);color:var(--paper)}
.btn:focus-visible{outline:var(--pane-edge) solid var(--blue-light);outline-offset:3px}
.btn--ghost{background:transparent;color:var(--blue);}
.on-ink .btn--ghost,.chrome .btn--ghost{color:var(--blue-light);border-color:var(--edge-strong-on-ink)}
.btn--block{display:block;width:100%;text-align:center}

/* ============================================================ forms
   Motion 8: focus steps the pane edge to blue, WEIGHT UNCHANGED. The edge never
   thickens — constant pane-edge weight is a brand law and does not get suspended
   for a state change.
   ERRORS CARRY NO COLOUR (FACTS palette._error_state): the edge steps
   edge_subtle -> edge_strong, plus a message. No red. No orange. */
.field{margin-bottom:var(--unit)}
.field label{display:block;font-size:var(--size-label);font-weight:var(--w-label);
  margin-bottom:calc(var(--unit) * .35)}
.field input[type=email],.field input[type=password],.field input[type=text]{
  width:100%;font-family:var(--font-text);font-size:var(--size-body);
  padding:.65em .8em;background:transparent;color:inherit;
  border:var(--pane-edge) solid var(--edge-subtle-on-ink);
  transition:border-color var(--dur-form-focus) ease-out;
}
.field input:focus{outline:none;border-color:var(--blue-light)}
.field[data-error] input{border-color:var(--edge-strong-on-ink)}
.field__error{display:none;font-size:var(--size-caption);color:var(--text-strong-on-ink);
  margin-top:calc(var(--unit) * .3)}
.field[data-error] .field__error{display:block}
.field--check{display:flex;align-items:center;gap:.6em;font-size:var(--size-caption)}
.field--check label{margin:0;font-weight:var(--w-body)}

/* ============================================================ video pane */
.video{border:var(--pane-edge) solid var(--edge-subtle-on-paper);background:var(--ink)}
.video__frame{position:relative;aspect-ratio:16/9}
.video__frame video,.video__frame iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
.video__meta{display:flex;gap:var(--unit);padding:calc(var(--unit) * .7) calc(var(--unit) * 1);
  font-size:var(--size-caption);color:var(--text-muted-on-ink);border-top:var(--pane-edge) solid var(--edge-subtle-on-ink)}

/* ============================================================ rhythm */
.section{padding-block:calc(var(--unit) * 3)}
.section__kicker{font-size:var(--size-label);font-weight:var(--w-label);
  color:var(--grey-on-paper);margin-bottom:calc(var(--unit) * .5)}
.on-ink .section__kicker{color:var(--text-muted-on-ink)}
.grid{display:grid;gap:var(--unit)}
@media (min-width:760px){
  .grid--3{grid-template-columns:repeat(3,1fr)}
  .grid--2{grid-template-columns:repeat(2,1fr)}
}
.steps{list-style:none;padding:0;counter-reset:step}
.steps li{counter-increment:step;display:grid;grid-template-columns:2.2em 1fr;
  gap:var(--unit);padding-block:calc(var(--unit) * .6);
  border-top:var(--pane-edge) solid var(--edge-subtle-on-paper)}
.steps li::before{content:counter(step);font-family:var(--font-display);
  font-weight:var(--w-heading);font-variant-numeric:tabular-nums;color:var(--blue)}

/* Section reveal — motion 3: opacity + 8px, never a slide. */
@media (prefers-reduced-motion:no-preference){
  .reveal{opacity:0;transform:translateY(8px);
    transition:opacity var(--dur-section-reveal) var(--ease-standard),
               transform var(--dur-section-reveal) var(--ease-standard)}
  .reveal.is-in{opacity:1;transform:none}
}
