/* gb-marketing.css — the public marketing page and sign-in gateway stylesheet.
   Implements mock frames 2a (desktop homepage), 2b/2c (mobile Business Hub / Operations states) and
   2d (mobile navigation), on the approved GioBiz green identity (doc 20 §2, design brief §3, chief 6.7).

   🔒 EVERY COLOUR LIVES IN THE ONE :root BLOCK BELOW. No hex literal appears anywhere else in this file.
   That is a binding condition of chief ruling 6.7: MOCK/export/CLAUDE.md §3 leaves the door open for the
   business to keep the old ivory marketing theme, and the final say on brand is Henry's. If he says ivory,
   this is a token swap in one block — not a rebuild. Do not inline a colour further down "just this once";
   that is exactly what makes the reversal expensive again.

   Contrast is a hard requirement (doc 20 §18, doc 21 §17): every text colour here was chosen against its
   resolved background to clear WCAG AA. --gb-text-muted is the AA floor — do not lighten it. Minimum touch
   target is 44px throughout. */

:root {
  /* Brand */
  --gb-green-deep: #0A5A33;      /* primary actions, active states, heading accents */
  --gb-green-mid: #12784A;       /* hero panels, secondary green */
  --gb-green-soft: #E8F3EC;      /* badge and chip backgrounds */

  /* Surfaces */
  --gb-page: #F4F6F5;
  --gb-card: #FFFFFF;
  --gb-border: #E2E8E4;          /* hairline */
  --gb-border-strong: #C9D5CE;   /* interactive controls */

  /* Text */
  --gb-text-strong: #17211C;
  --gb-text-body: #3B4A43;
  --gb-text-muted: #55665C;      /* AA floor — never lighten */
  --gb-on-green: #FFFFFF;

  /* Status */
  --gb-warn-bg: #FFF4E0;
  --gb-warn-border: #E8C77A;
  --gb-warn-text: #6B4708;

  /* Shape */
  --gb-r-control: 11px;
  --gb-r-card: 16px;
  --gb-r-pill: 99px;

  /* Type */
  --gb-font: 'Manrope', system-ui, -apple-system, 'Segoe UI', sans-serif;

  /* Frame 2a's Goods panel is a green gradient with white type over it. Tokens, because the one
     :root block is binding and a gradient stop is still a colour. */
  --gb-panel-grad: linear-gradient(160deg, #0E6B3C, #0A5A33);
  --gb-on-panel: rgba(255, 255, 255, .78);
  --gb-on-panel-dim: rgba(255, 255, 255, .6);
  --gb-panel-inset: rgba(255, 255, 255, .18);
  --gb-panel-dot: rgba(255, 255, 255, .4);

  /* Frame 2a gives both gateway cards a soft lift. It is a COLOUR, so it lives here with the rest —
     the one :root block is a binding condition of chief ruling 6.7 and it does not get an exception
     because the colour happens to be inside a box-shadow. */
  --gb-shadow-card: 0 14px 32px -16px rgba(10, 50, 30, .16);

  /* A darkening overlay, never a translucent white one — white-on-translucent-white over green fails
     contrast, which the design brief calls out explicitly. */
  --gb-overlay: rgba(0, 0, 0, .2);
}

/* ── base ─────────────────────────────────────────────────────────────────────────────────────── */
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  font-family: var(--gb-font);
  background: var(--gb-page);
  color: var(--gb-text-body);
  -webkit-font-smoothing: antialiased;
  line-height: 1.55;
}
h1, h2, h3, h4 { margin: 0; color: var(--gb-text-strong); letter-spacing: -.02em; text-wrap: balance; }
p { margin: 0; }
img { max-width: 100%; display: block; }
a { color: var(--gb-green-deep); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Visible focus everywhere, for everyone. Doc 20 §18. */
:where(a, button, input, [tabindex]):focus-visible {
  outline: 3px solid var(--gb-green-mid);
  outline-offset: 2px;
  border-radius: 6px;
}

.gb-wrap { max-width: 1200px; margin: 0 auto; padding: 0 20px; }
.gb-sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
         clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* ── controls ─────────────────────────────────────────────────────────────────────────────────── */
.gb-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 48px; padding: 12px 22px;
  font: inherit; font-weight: 700; font-size: 15px;
  border-radius: var(--gb-r-control); border: 1.5px solid transparent;
  cursor: pointer; transition: background .18s, border-color .18s, color .18s;
  text-align: center;
}
.gb-btn-primary { background: var(--gb-green-deep); color: var(--gb-on-green); }
.gb-btn-primary:hover { background: var(--gb-green-mid); color: var(--gb-on-green); text-decoration: none; }
.gb-btn-ghost { background: var(--gb-card); color: var(--gb-green-deep); border-color: var(--gb-border-strong); }
.gb-btn-ghost:hover { border-color: var(--gb-green-mid); background: var(--gb-green-soft); text-decoration: none; }
.gb-btn-block { width: 100%; }

.gb-eyebrow {
  display: inline-block; font-size: 12px; font-weight: 800; letter-spacing: .14em;
  text-transform: uppercase; color: var(--gb-green-deep);
}

/* ── header ───────────────────────────────────────────────────────────────────────────────────── */
.gb-header {
  position: sticky; top: 0; z-index: 60;
  background: var(--gb-page); border-bottom: 1px solid var(--gb-border);
}
.gb-nav { display: flex; align-items: center; justify-content: space-between; height: 64px; gap: 16px; }
.gb-brand { display: flex; align-items: center; gap: 10px; font-weight: 800; font-size: 19px;
            color: var(--gb-green-deep); }
.gb-brand:hover { text-decoration: none; }
.gb-mark { width: 34px; height: 34px; border-radius: 9px; background: var(--gb-green-deep);
           color: var(--gb-on-green); display: grid; place-items: center; font-weight: 800; font-size: 18px; }
.gb-navlinks { display: flex; align-items: center; gap: 24px; }
.gb-navlinks a { color: var(--gb-text-body); font-weight: 600; font-size: 15px; }
.gb-navlinks a:hover { color: var(--gb-green-deep); }
.gb-navcta { display: flex; align-items: center; gap: 10px; }
.gb-menu-btn {
  display: none; min-width: 44px; min-height: 44px; align-items: center; justify-content: center;
  background: var(--gb-card); border: 1.5px solid var(--gb-border-strong);
  border-radius: var(--gb-r-control); color: var(--gb-green-deep); font: inherit; font-size: 20px; cursor: pointer;
}

/* ── hub / operations segmented toggle (mobile default state = Business Hub, frame 2b) ────────── */
.gb-toggle { display: none; gap: 4px; padding: 4px; margin: 14px 0 4px;
             background: var(--gb-card); border: 1px solid var(--gb-border); border-radius: var(--gb-r-pill); }
.gb-toggle button {
  flex: 1; min-height: 44px; border: 0; border-radius: var(--gb-r-pill);
  background: transparent; color: var(--gb-text-body); font: inherit; font-weight: 700; font-size: 14.5px;
  cursor: pointer;
}
.gb-toggle button[aria-selected="true"] { background: var(--gb-green-deep); color: var(--gb-on-green); }

/* ── gateway cards (frame 2a hero — two equal cards, neither subordinate) ──────────────────────── */
.gb-hero { padding: 40px 0 8px; }
.gb-hero-lead { max-width: 900px; margin: 0 auto 32px; text-align: center; }
.gb-hero-lead h1 { font-size: clamp(28px, 3.8vw, 46px); line-height: 1.12; font-weight: 800;
                   letter-spacing: -.025em; text-wrap: pretty; }
.gb-hero-lead p { margin: 12px auto 0; max-width: 640px; color: var(--gb-text-muted);
                  font-size: 16.5px; line-height: 1.55; }

.gb-gateways { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
.gb-gateway {
  background: var(--gb-card); border: 1px solid var(--gb-border); border-radius: var(--gb-r-card);
  padding: 30px; display: flex; flex-direction: column;
  box-shadow: var(--gb-shadow-card);
}
.gb-gateway h2 { font-size: clamp(22px, 2.6vw, 31px); font-weight: 800; letter-spacing: -.02em; }
.gb-modline { color: var(--gb-green-mid); font-weight: 800; font-size: 14.5px; }

/* 🔴 F-98 (Joe, 2026-09-07) — the module line, split into the two things it was pretending to be at once.
   On the Business Hub card those five words are now real controls and must LOOK pressable; on the
   Operations card they are a description of what you sign in to and must look like prose. Before this,
   both were the same bold green sentence, so a stranger pressed all ten words and got nothing from any of
   them. Colour was doing the work alone, which is the one thing the house rules say colour may never do —
   so the pressable set gets a border, a surface and a real target size, not just a hue.

   The five links are NOT a scroller. At 390px they wrap onto two rows and every one of them is on the
   first screen, which is the whole point of the fix: no gesture, nothing hidden, no "slide 1 of 5". */
.gb-modlinks {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-top: 2px;
}
.gb-modlink {
  display: inline-flex; align-items: center; justify-content: center;
  /* 44px is the minimum touch target this stylesheet sets for itself at the top of the file. A thumb on a
     cheap Android is the measurement that matters here, not the desktop pointer. */
  min-height: 44px; padding: 0 14px;
  border: 1.5px solid var(--gb-border-strong); border-radius: var(--gb-r-control);
  background: var(--gb-card); color: var(--gb-green-deep);
  font-weight: 800; font-size: 14.5px; line-height: 1;
  text-decoration: none; cursor: pointer;
  transition: background .15s ease, border-color .15s ease;
}
.gb-modlink:hover, .gb-modlink:focus-visible {
  background: var(--gb-green-soft); border-color: var(--gb-green-mid); text-decoration: none;
}
.gb-modlink:active { background: var(--gb-green-soft); }

/* The Operations five. Same words, no border, no box, muted — a caption, which is what it always was. */
.gb-modline-static { color: var(--gb-text-muted); font-weight: 700; }

.gb-gateway p { color: var(--gb-text-muted); font-size: 15px; }
/* 🔴 Henry named this gap: "the white space in between the sign in and explore button". Frame 2a is
   `display:flex; gap:11px; margin-top:auto`, both buttons `flex:1` and 52px tall with an 11px radius and
   16px/800 text. Mine were 10px apart, 48px tall, 15px/700 and allowed to WRAP — which on a narrow card
   dropped Sign In onto its own line and left a ragged block of white under Explore. The frame does not
   wrap them; they share the row and split it evenly. Scoped to the gateway cards so the site-wide .gb-btn
   keeps its own size everywhere else. */
.gb-gateway-actions { display: flex; gap: 11px; margin-top: auto; padding-top: 6px; }
.gb-gateway-actions .gb-btn { flex: 1 1 0; min-height: 52px; border-radius: 11px;
                              font-size: 16px; font-weight: 800; padding: 12px 16px; }
.gb-btn-arrow { width: 17px; height: 17px; flex: none; fill: none; stroke: currentColor;
                stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.gb-gateway-note { font-size: 13px; color: var(--gb-text-muted); }

.gb-preview {
  background: var(--gb-green-soft); border: 1px solid var(--gb-border);
  border-radius: 13px; padding: 16px; min-height: 132px;
}
.gb-preview h3 { font-size: 15.5px; }
.gb-preview p { margin-top: 6px; font-size: 14px; color: var(--gb-text-body); }
/* ── Goods Marketplace preview ──────────────────────────────────────────────────────────────────
   The live rules are `.gb-goods-strip` / `.gb-goods-tile`, below. Chief's earlier `.gb-preview-*` set was
   REMOVED here 2026-09-06: Thompson and I built this same strip independently within the same twenty
   minutes, his version is the better one, and leaving my dead rules behind would have left two vocabularies
   for one card — which is how a later editor changes the half nobody is using. */

/* ── module selector (frame 2a "INSIDE THE BUSINESS HUB" / frame 2b module strip) ──────────────── */
.gb-modstrip { display: flex; gap: 8px; overflow-x: auto; padding: 4px 0 8px; scroll-snap-type: x mandatory; }
.gb-modstrip button {
  flex: 0 0 auto; scroll-snap-align: start;
  min-height: 44px; padding: 10px 18px; border-radius: var(--gb-r-pill);
  border: 1.5px solid var(--gb-border-strong); background: var(--gb-card);
  color: var(--gb-text-body); font: inherit; font-weight: 700; font-size: 14.5px; cursor: pointer;
}
.gb-modstrip button[aria-selected="true"] {
  background: var(--gb-green-deep); border-color: var(--gb-green-deep); color: var(--gb-on-green);
}
.gb-modpanel {
  background: var(--gb-card); border: 1px solid var(--gb-border); border-radius: var(--gb-r-card);
  padding: 22px; margin-top: 10px;
}
/* F-38: the "Soon" marker on a module chip, and the line that leads its panel.
   It is a WORD, not a colour — the red-button rule cuts both ways, and a customer who cannot distinguish the
   chip's shade must still be able to read that the module is not open. The word survives at 200% zoom, in
   greyscale, and to a screen reader. */
.gb-soon {
  display: inline-block; margin-left: 6px; padding: 2px 7px;
  border-radius: var(--gb-r-pill); border: 1px solid var(--gb-warn-border);
  background: var(--gb-warn-bg); color: var(--gb-warn-text);
  font-size: 10.5px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
  vertical-align: 1px;
}
[aria-selected="true"] .gb-soon { background: var(--gb-card); border-color: var(--gb-card); }
.gb-notopen {
  margin-top: 10px; padding: 11px 14px; border-radius: 11px;
  background: var(--gb-warn-bg); border: 1px solid var(--gb-warn-border); color: var(--gb-warn-text);
  font-size: 14px;
}
.gb-notopen strong { font-weight: 800; }

.gb-modpanel h3 { font-size: 20px; }
.gb-modpanel > p { margin-top: 8px; color: var(--gb-text-muted); max-width: 62ch; }
.gb-chips { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 14px; }
.gb-chip { background: var(--gb-green-soft); color: var(--gb-green-deep); border-radius: var(--gb-r-pill);
           padding: 6px 13px; font-size: 13.5px; font-weight: 700; }
.gb-steps { display: grid; gap: 8px; margin-top: 14px; }
.gb-step { display: flex; gap: 11px; align-items: flex-start; }
.gb-step-n {
  flex: 0 0 auto; width: 25px; height: 25px; border-radius: 50%;
  background: var(--gb-green-deep); color: var(--gb-on-green);
  display: grid; place-items: center; font-size: 12.5px; font-weight: 800;
}
.gb-step-t { font-weight: 700; color: var(--gb-text-strong); font-size: 14.5px; }
.gb-step-s { font-size: 13.5px; color: var(--gb-text-muted); }

/* Required trust/legal wording. Never colour alone — it carries an icon and a heading too. */
.gb-warn {
  margin-top: 15px; padding: 13px 15px; border-radius: 12px;
  background: var(--gb-warn-bg); border: 1px solid var(--gb-warn-border); color: var(--gb-warn-text);
  font-size: 13.5px; line-height: 1.5;
}
.gb-warn b { display: block; margin-bottom: 3px; }

/* ── sections ─────────────────────────────────────────────────────────────────────────────────── */
.gb-sec { padding: 40px 0; }
.gb-sec-head { max-width: 640px; margin-bottom: 20px; }
.gb-sec-head h2 { font-size: clamp(22px, 2.6vw, 29px); margin-top: 6px; }
.gb-sec-head p { margin-top: 8px; color: var(--gb-text-muted); }

.gb-ops-row { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; }
.gb-op {
  background: var(--gb-card); border: 1px solid var(--gb-border); border-radius: 14px; padding: 17px;
  display: flex; flex-direction: column; gap: 7px;
}
.gb-op h3 { font-size: 15.5px; }
.gb-op p { font-size: 13.5px; color: var(--gb-text-muted); flex: 1; }
.gb-op a { font-weight: 700; font-size: 13.5px; }

.gb-how { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.gb-how-card { background: var(--gb-card); border: 1px solid var(--gb-border);
               border-radius: var(--gb-r-card); padding: 22px; }
.gb-how-card h3 { font-size: 13px; letter-spacing: .12em; text-transform: uppercase;
                  color: var(--gb-green-deep); margin-bottom: 13px; }

.gb-track-card {
  background: var(--gb-card); border: 1px solid var(--gb-border); border-radius: var(--gb-r-card);
  padding: 22px; display: grid; grid-template-columns: 1fr auto; gap: 18px; align-items: center;
}
.gb-track-form { display: flex; gap: 9px; flex-wrap: wrap; }
.gb-track-form input {
  min-height: 48px; min-width: 210px; flex: 1;
  padding: 12px 15px; font: inherit; font-size: 15px;
  border: 1.5px solid var(--gb-border-strong); border-radius: var(--gb-r-control);
  background: var(--gb-card); color: var(--gb-text-strong);
}
.gb-track-err { margin-top: 8px; font-size: 13.5px; color: var(--gb-warn-text);
                background: var(--gb-warn-bg); border: 1px solid var(--gb-warn-border);
                border-radius: 10px; padding: 8px 12px; }

.gb-plans { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.gb-plan { background: var(--gb-card); border: 1px solid var(--gb-border); border-radius: 14px; padding: 18px; }
.gb-plan h3 { font-size: 15.5px; }
/* The price line reserves its height in EVERY state — loading, loaded, unavailable — so switching
   states causes no reflow (frame 2g). */
.gb-plan .gb-amt { display: block; min-height: 34px; margin-top: 9px;
                   font-size: 25px; font-weight: 800; color: var(--gb-text-strong); }
.gb-plan .gb-per { font-size: 13.5px; font-weight: 600; color: var(--gb-text-muted); }
/* "view all plans" appears four times in the price band and measured 85x18 on a 390px phone - it is the
   only route from a price to the page that explains it. The TEXT stays 13.5px; the padding/negative-margin
   pair grows the TAP BOX to 44px without moving a pixel on screen, so nothing reflows and a thumb can
   still land on it. Enlarging the text instead would have shouted a secondary link over the price. */
.gb-plan .gb-per a { display: inline-block; padding: 13px 8px; margin: -13px -8px; }

.gb-trust { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.gb-trust-item { background: var(--gb-card); border: 1px solid var(--gb-border); border-radius: 13px; padding: 15px; }
.gb-trust-item h3 { font-size: 14.5px; }
.gb-trust-item p { margin-top: 5px; font-size: 13.5px; color: var(--gb-text-muted); }
.gb-disclaim { margin-top: 14px; font-size: 13.5px; color: var(--gb-text-muted); max-width: 90ch; }

.gb-closing {
  background: var(--gb-green-mid); border-radius: var(--gb-r-card); padding: 30px 24px; text-align: center;
}
.gb-closing h2 { color: var(--gb-on-green); font-size: clamp(21px, 2.6vw, 28px); }
.gb-closing-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin-top: 17px; }
.gb-closing-actions .gb-btn-primary { background: var(--gb-card); color: var(--gb-green-deep); }
.gb-closing-actions .gb-btn-primary:hover { background: var(--gb-green-soft); color: var(--gb-green-deep); }
.gb-closing-actions .gb-btn-ghost { background: transparent; color: var(--gb-on-green);
                                    border-color: var(--gb-on-green); }
.gb-closing-actions .gb-btn-ghost:hover { background: var(--gb-overlay); color: var(--gb-on-green); }

/* ── footer (doc 20 §16 — preserved exactly, nothing dropped at mobile width) ──────────────────── */
.gb-footer { background: var(--gb-card); border-top: 1px solid var(--gb-border); padding: 34px 0 26px; margin-top: 8px; }
.gb-foot-grid { display: grid; grid-template-columns: 1.2fr 1fr 1fr; gap: 26px; }
.gb-foot-grid h4 { font-size: 12px; letter-spacing: .12em; text-transform: uppercase;
                   color: var(--gb-green-deep); margin-bottom: 8px; }
.gb-foot-grid address { font-style: normal; font-size: 13.5px; color: var(--gb-text-muted); line-height: 1.65; }
.gb-foot-links { display: flex; flex-wrap: wrap; gap: 8px 20px; margin-top: 24px;
                 padding-top: 18px; border-top: 1px solid var(--gb-border); }
.gb-foot-links a { font-size: 14px; font-weight: 600; min-height: 44px; display: inline-flex; align-items: center; }
/* The mail link was the one control this page's own 44px rule never covered — 129x18, in the address block
   rather than the link nav, and it is the ONLY way a customer contacts us from the footer. Same technique,
   same reason: the box grows, the type does not. The other nineteen pages get this from
   `_lib/gb-footer-legal.css`; home.html keeps its rules here because its footer already lived here. */
footer address a[href^="mailto:"] { min-height: 44px; display: inline-flex; align-items: center; }
.gb-copy { margin-top: 12px; font-size: 13px; color: var(--gb-text-muted); }

/* ── mobile navigation sheet (frame 2d) ───────────────────────────────────────────────────────── */
.gb-sheet { position: fixed; inset: 0; z-index: 90; display: none; }
.gb-sheet[data-open="true"] { display: block; }
.gb-sheet-scrim { position: absolute; inset: 0; background: var(--gb-overlay); }
.gb-sheet-panel {
  position: absolute; inset: 64px 0 0 0; overflow-y: auto;
  background: var(--gb-page); border-top: 1px solid var(--gb-border);
  padding: 16px 20px calc(28px + env(safe-area-inset-bottom));
}
.gb-sheet h4 { font-size: 12px; letter-spacing: .12em; text-transform: uppercase;
               color: var(--gb-green-deep); margin: 14px 0 6px; }
.gb-sheet-row {
  display: flex; align-items: center; min-height: 52px;
  padding: 8px 4px; border-bottom: 1px solid var(--gb-border);
  font-weight: 700; font-size: 16px; color: var(--gb-text-strong);
}
.gb-sheet-row:hover { text-decoration: none; color: var(--gb-green-deep); }
/* F-328 — the sheet's Install App row is a <button> (it fires a prompt, it does not navigate), and a bare button
   arrives with UA defaults the class above does not override: buttonface background, an outset border on all four
   sides, Arial 13.3px, centred text, shrink-to-fit width. SCOPED to `button.` so the six anchor rows are not
   touched by one declaration — the menu must not change appearance because one row gained a handler. */
button.gb-sheet-row {
  width: 100%; background: none; border: 0; border-bottom: 1px solid var(--gb-border);
  /* `start`, not `left`: the six anchor rows compute `text-align: start`, and matching the neighbour exactly is
     worth more than matching its rendered effect — a receipt that compares computed values found the difference,
     and the next person comparing them should find none. */
  font-family: inherit; text-align: start; cursor: pointer;
}
.gb-sheet-legal { display: flex; flex-direction: column; margin-top: 6px; }
.gb-sheet-legal a { min-height: 44px; display: flex; align-items: center;
                    font-size: 14.5px; color: var(--gb-text-body); }
.gb-sheet-foot { margin-top: 14px; font-size: 13px; color: var(--gb-text-muted); }

/* ── responsive ───────────────────────────────────────────────────────────────────────────────── */
@media (max-width: 1023px) {
  .gb-gateways { grid-template-columns: 1fr; }
  .gb-ops-row { grid-template-columns: repeat(2, 1fr); }
  .gb-how { grid-template-columns: 1fr; }
  .gb-plans { grid-template-columns: repeat(2, 1fr); }
  .gb-trust { grid-template-columns: repeat(2, 1fr); }
  .gb-track-card { grid-template-columns: 1fr; }
  .gb-foot-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 767px) {
  .gb-navlinks { display: none; }
  .gb-menu-btn { display: inline-flex; }
  .gb-toggle { display: flex; }

  /* 🔴🔴 F-328 / F-329 (Joe, measured at 390 on 2026-09-10 02:07 and again 2026-09-11 09:46 — CYCLE 2) —
     THE FRONT DOOR WAS 460 WIDE ON A 390 PHONE AND ITS BUTTONS SAT 4 PIXELS ABOVE THE TOP OF THE SCREEN.
     He reported those as two faults. They are ONE, and this block is the whole of it.

     🔑 THE CHAIN, MEASURED FROM INSIDE THE PAGE (`tests/entry_c0911_frontdoor_header_fit.mjs`), not reasoned:
       1. `.gb-navcta` holds FOUR controls on a phone — Install App · Sign Up · Sign In · ☰ — and at 390 the row
          has only 350px (`.gb-wrap` takes 20 a side). `#gb-install` starts `display:none` and is revealed only
          when `beforeinstallprompt` fires, so on a profile where the browser offers installation (Joe's fresh
          one) the row carries four; on a profile where it does not, three. THAT is why this hid from two earlier
          measurement passes: they measured the kinder three-control row and read a clean 390.
       2. Four controls cannot fit 350, and `width=device-width, initial-scale=1` does NOT clip when that happens
          — Chrome EXPANDS the layout viewport to the content's unbreakable width. Measured: `window.innerWidth`
          comes back **461**, `documentElement.scrollWidth` **461**. So the overflow does not crop the header, it
          widens THE WHOLE PAGE to 461 on a 390 screen. **That is Joe's "460", and it was real.**
       3. With the row squeezed, `.gb-btn`'s labels wrapped onto two lines → each button became 72.1 tall.
          `.gb-nav` had a FIXED `height: 64px`, so 72.1 of content in a 64 box with `align-items: center` split
          the 8px overflow equally: (72.1 − 64) / 2 = 4.05 above the top edge. **That is Joe's "y = −4", to the
          pixel, and it is the same squeeze one layer down.**

     🔒 WHY "INSTALL APP" IS THE ONE THAT LEAVES THE ROW, AND NOT Sign Up OR Sign In. Measured, all four variants
        in `tests/entry_c0911_header_variants.mjs`: keeping four controls and merely tightening them still leaves
        the page at **454** — the row is over budget by a whole control, so no amount of padding recovers it. Of
        the four, `home.html` already calls Install App *"a secondary action"* in its own comment, while Sign In is
        how an existing customer gets in, Sign Up is the ONLY create-account door on this page (added 2026-09-10
        precisely because a stranger could not find one — Joe's finding 6), and ☰ is the whole of mobile
        navigation. **Install App is therefore the only one whose removal costs a phone user nothing** — and it is
        not removed, it MOVES: `home.html` now carries the same control as a row inside the ☰ sheet, revealed by
        the same `beforeinstallprompt` and firing the same deferred prompt. One handler, two places, no dead
        control, nothing deleted.

     🔒 AND NOTHING IS SHRUNK PAST THE FLOOR. `font-size` stays 15px and `min-height` stays 48 (CLAUDE.md: 44 is a
        FLOOR and nothing already at 48 may be reduced). Measured after: innerWidth **390**, scrollWidth **390**,
        row top **y = 7.58**, every control **48.85 tall**, right edge at 370.4 — 19px of slack, so a longer label
        or a wider font does not re-break it. `height` → `min-height` on `.gb-nav` is the structural half: even if
        some future content does overflow this row again, it grows the header instead of climbing off the screen. */
  .gb-nav { height: auto; min-height: 64px; }
  .gb-navcta { gap: 8px; }
  .gb-navcta .gb-btn { white-space: nowrap; padding: 12px 14px; }
  /* Moved to the ☰ sheet at this width, NOT removed — see `#gb-sheet-install` in home.html. The reveal script
     sets `style.display = ''` (i.e. "defer to the stylesheet"), so this rule wins without `!important`, and the
     desktop row is byte-identical. */
  #gb-install { display: none; }
  /* Mobile shows ONE state at a time. Business Hub is the default on every first load (frame 2b);
     Operations is reached only through the toggle (frame 2c). */
  body[data-gb-state="hub"] [data-gb-pane="ops"],
  body[data-gb-state="ops"] [data-gb-pane="hub"] { display: none; }
  /* 🔴 F-52 (Joe, measured at 390px on 2026-09-06 21:41 and again 2026-09-07 02:03) — THE TWO ENTRANCE CARDS
     ARE THE ONE THING THAT MUST NOT BE BEHIND A TAB.

     The rule above is right for the REST of the page: below 768px the deeper Business-Hub and Operations
     sections show one at a time, and the toggle picks which. But it also swallowed the hero's second
     gateway card, and with it four things a business owner needs before he decides anything:
       · the heading "Run Your Operations"
       · the module line "Business • Logistics • Dispatch • Rider • Warehouse"
       · the sales-and-stock illustration
       · and its disclaimer, "Illustrative only · no live account data before sign-in"

     Joe's sentence is the rule this restores: **"when you hide a section on small screens, you hide its
     disclaimers with it."** A disclaimer that only desktop readers get is not a disclaimer. And on a phone —
     which in Nigeria is nearly every business owner — he could not see what he is being asked to pay for.

     🔒 SCOPED TO THE HERO PAIR ONLY (`.gb-gateways > [data-gb-pane]`). Every other `[data-gb-pane]` section
     on the page still obeys the one-state rule above, so the toggle keeps its job. The two cards already
     stack one above the other below 1023px (`.gb-gateways { grid-template-columns: 1fr }`), so this needs no
     new layout and adds no markup — it stops a card being hidden, nothing more. */
  .gb-gateways > [data-gb-pane] { display: block !important; }
  .gb-hero { padding: 12px 0 4px; }
  .gb-ops-row, .gb-plans, .gb-trust { grid-template-columns: 1fr; }
  .gb-foot-grid { grid-template-columns: 1fr; }
  .gb-sec { padding: 28px 0; }
}
@media (min-width: 768px) {
  /* Above the mobile breakpoint both panes are shown; the toggle is a mobile affordance only. */
  [data-gb-pane] { display: block !important; }
}
@media (max-width: 359px) {
  .gb-gateway { padding: 18px; }
  .gb-wrap { padding: 0 14px; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
}

/* ── Goods Marketplace preview strip (frames 2a/2b) ──────────────────────────────────────────────
   Four real listings inside the Goods gateway card. The HEIGHT IS RESERVED HERE, before anything is
   fetched, because both frames say "no layout shift" in as many words - the card must not jump when the
   products land. An empty strip has no border, no background and no visible box of any kind: if
   gb-goods-preview.js finds nothing real to show it renders nothing, and the customer simply sees the
   card's copy. That is the whole of chief's F-33 ruling preserved - a box is never drawn for its own sake. */
.gb-goods-strip { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin: 12px 0 4px;
                  min-height: 133px; align-content: start; }
/* 🔴 THE RESERVE IS PER LAYOUT, and it was not — chief measured the gap on live v2330:
       phone   390px   3 tiles   133 → 133   shift 0px   ✅
       desktop 1280px  4 tiles   133 → 165   shift 32px  ❌
   My tiles are `aspect-ratio: 1/1`, so a wider viewport makes every tile TALLER. I calibrated 133px on a
   390px phone, proved "no layout shift" there, and shipped a number that was only ever true at the width I
   happened to measure. The card still jumped a third of its own height on every desktop.

   🔑 A reserved height is a promise about a SPECIFIC layout. Reserve it per layout, or it is true only at the
   width you measured — and it will be the width you measured, every time.

   The breakpoint is deliberately the SAME 430px `showCount()` uses to decide 3 tiles vs 4, so the reserve and
   the tile count can never disagree: change one and the other is already right. Both numbers below are
   measured on the rendered page, not derived on paper. */
.gb-goods-strip:not([data-gb-filled]) { min-height: 133px; }              /* phone row, measured at 390px */
@media (min-width: 431px) {
  .gb-goods-strip,
  .gb-goods-strip:not([data-gb-filled]) { min-height: 165px; }            /* 4-across row, measured at 1280px */
}
.gb-goods-tile { display: flex; flex-direction: column; gap: 4px; text-decoration: none; color: inherit;
                 background: var(--gb-card); border: 1px solid var(--gb-border); border-radius: 10px;
                 overflow: hidden; padding-bottom: 6px; min-height: 44px; }
.gb-goods-tile img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; display: block; background: var(--gb-green-soft); }
.gb-goods-name  { font-size: 11.5px; font-weight: 600; padding: 0 6px; white-space: nowrap;
                  overflow: hidden; text-overflow: ellipsis; }
.gb-goods-price { font-size: 11.5px; font-weight: 700; color: var(--gb-green-deep); padding: 0 6px; }
/* 🔴 THREE COLUMNS ON PHONES, and this is a MEASURED deviation from the four tiles frames 2a/2b draw.
   At 390px a six-figure price needs 62px inside a 59px tile and CLIPS: '₦200,000' rendered as a cut
   number. A truncated price is not a smaller price, it is a WRONG price - and the whole point of this strip
   is that everything in it is real. Four tiles fit honestly from 431px up, which is where the frame's layout
   is kept. Below that the third column buys the width a real Nigerian price needs.
   Measured after the change at 360 / 390 / 430: no price clipped at any of them. */
@media (max-width: 430px) { .gb-goods-strip { grid-template-columns: repeat(3, 1fr); } }

/* ── A1 — "From … per month" must never appear without an amount ─────────────────────────────────
   CLAUDE-CODE-CORRECTIONS A1, verified: with JavaScript ENABLED every card shows a real figure, but with
   JavaScript DISABLED the live page reads "Business Operation From per month" - a sentence with its number
   missing. That is what a crawler, a dead script and a phone on a failing connection all get, and the page's
   own disclaimer already promises the opposite: "if an amount cannot be loaded we show a link to the pricing
   page rather than a figure we are not sure of."

   So the words are OPT-IN ON SUCCESS rather than opt-out on failure. They are hidden by default - which is
   also the no-JS state - and revealed only when pricing-config has actually bound an amount to that card.
   Nothing to hide means nothing can be forgotten: with no JavaScript at all the card reads
   "Business Operation · view all plans", which is true and complete. */
.gb-plan .gb-from, .gb-plan .gb-per-label { display: none; }
.gb-plan[data-gb-priced] .gb-from, .gb-plan[data-gb-priced] .gb-per-label { display: inline; }

/* ── F-52 — the Operations card's blanked dashboard (frame 2a) ───────────────────────────────────
   The frame shows the SHAPE of the console with every figure replaced by dashes, and says so underneath.
   The note is not decoration: without it the dashes would read as data that failed to arrive, which is the
   exact fault the Goods card's empty tiles were removed for. It stays visible at every width, and it is
   never lighter than the rows it explains. */
.gb-opsdash { background: var(--gb-card); border: 1px solid var(--gb-border); border-radius: 12px;
              padding: 12px 12px 10px; margin-bottom: 12px; }
.gb-opsdash-head { display: flex; flex-direction: column; gap: 1px; margin-bottom: 8px; }
.gb-opsdash-title { font-weight: 800; font-size: 14px; }
.gb-opsdash-sub { font-size: 11.5px; color: var(--gb-text-muted); font-weight: 600; }
.gb-opsdash-row { display: flex; align-items: center; justify-content: space-between; gap: 10px;
                  padding: 9px 10px; border: 1px solid var(--gb-border); border-radius: 8px;
                  margin-bottom: 6px; font-size: 13px; font-weight: 600; }
.gb-opsdash-blank { color: var(--gb-text-muted); letter-spacing: 2px; font-weight: 700; }
.gb-opsdash-note { font-size: 11.5px; color: var(--gb-text-muted); margin: 8px 2px 0; font-weight: 600; }

/* ── F-53 — a named control on every module, disabled where there is nowhere to go ───────────────
   §2: a control that cannot work yet is DISABLED and EXPLAINED on screen - never enabled-and-inert, and
   never silently missing where the frame drew it. The reason sits beside the button, not in a tooltip, so
   it is readable by the person who cannot use it. Disabled styling is deliberately obvious rather than
   subtle: a control that merely looks quiet reads as broken. */
.gb-actions-off { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
.gb-actions-off .gb-btn[disabled] { opacity: .55; cursor: not-allowed; pointer-events: none;
                                    border-style: dashed; }
.gb-btn-why { font-size: 12px; color: var(--gb-text-muted); font-weight: 600; margin: 0; }

/* ── ModulePreviewSelector, 5-up on a phone (chief's ruling 2026-09-06) ──────────────────────────
   Replaces the separate ModuleShortcutStrip I added earlier the same day. That strip fixed the real defect —
   `Jobs` and `Property` were off the right edge of every phone — but it created a smaller one: the chips
   carry `SOON` on Services and Property and the tiles did not, so the same module was described two ways on
   one screen and the newer control told the customer less. Chief: "a duplicated control is a nuisance; a
   duplicated control carrying LESS truth than its twin is a defect."

   So there is now ONE control, and 2b's sentence reads as one strip rather than two:
   "Module strip is 5-up at ≥360 and horizontally scrollable at 320."

   THREE LAYOUTS, and the breakpoints are measured rather than picked:
     < 360px   the mock's own 320 case — the base rules below stay a scrolling flex row.
     ≥ 360px   5-up grid. Five columns fit here: at 360 the row's client width is 320, so each column is
               57.6px, and the longest label ("Property", 11px/800) measures ~48px.
     ≥ 660px   back to comfortable text pills. The pill row's natural content is 596px and the row's client
               width passes that at ~636px of viewport, so from 660 all five fit as pills unaided and the
               compact treatment is no longer buying anything. */
/* The icon belongs to the COMPACT layout only, so it is off by default and switched on inside the 5-up range.
   Getting this wrong is not cosmetic: an <svg> with no width in a flex button takes its default intrinsic
   size, and at 320px that produced 170px-tall chips — more than twice the height of the 74px ones at 360.
   My own check passed it, because I asserted a 44px FLOOR on the touch target and never a ceiling. A range
   needs both ends or it only catches the failure you already thought of. */
.gb-modstrip-5up button svg { display: none; }
@media (min-width: 360px) {
  .gb-modstrip-5up button svg { display: block; }
  .gb-modstrip-5up {
    display: grid; grid-template-columns: repeat(5, 1fr); gap: 7px;
    overflow-x: visible;             /* nothing to scroll once everything fits — and a scroll container here
                                        would keep a focused chip scrollable off-screen for no reason */
    scroll-snap-type: none; padding: 4px 0 8px;
  }
  .gb-modstrip-5up button {
    flex: initial; display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 5px; min-height: 64px;      /* the frame's own height, and well over the 44px touch floor */
    padding: 9px 2px; border-radius: 11px;
    border-width: 1px; border-color: var(--gb-border);
    font-size: 11px; font-weight: 800; line-height: 1.15; text-align: center;
    color: var(--gb-text-strong);
  }
  .gb-modstrip-5up button svg {
    width: 18px; height: 18px; flex: none;
    fill: none; stroke: var(--gb-text-body); stroke-width: 2;
    stroke-linecap: round; stroke-linejoin: round;
  }
  .gb-modstrip-5up button[aria-selected="true"] svg { stroke: var(--gb-on-green); }
  /* The badge drops onto its own line here — there is no room beside a label in a 57px column, and the word
     must survive, not be truncated. It is the only thing telling a customer the module is not open. */
  .gb-modstrip-5up .gb-soon {
    margin-left: 0; padding: 1px 5px; font-size: 8.5px; letter-spacing: .04em; vertical-align: 0;
  }
}
/* Icons are decoration once the label is readable at full size, so they go away with the compact layout. */
@media (min-width: 660px) {
  .gb-modstrip-5up { display: flex; gap: 8px; }
  .gb-modstrip-5up button { flex: 0 0 auto; flex-direction: row; gap: 8px; min-height: 44px;
                            padding: 10px 18px; border-radius: var(--gb-r-pill); border-width: 1.5px;
                            border-color: var(--gb-border-strong); font-size: 14.5px; font-weight: 700;
                            color: var(--gb-text-body); }
  .gb-modstrip-5up button svg { display: none; }
  .gb-modstrip-5up .gb-soon { margin-left: 0; padding: 2px 7px; font-size: 10.5px; letter-spacing: .06em; }
}
.gb-modstrip-5up button:focus-visible { outline: 3px solid var(--gb-green-mid); outline-offset: 2px; }

/* ── the Goods panel, frame 2a ─────────────────────────────────────────────────────────────────
   Henry, 2026-09-06: the front page "looking like a child's play". This panel was the main reason —
   the frame draws a green product panel with an icon, a title, a sub-line, position dots and a caption,
   and I had shipped a pale box with a paragraph in it. Scoped to `.gb-preview-goods` so the Operations
   dashboard illustration in the other card is untouched. */
.gb-preview-goods {
  background: var(--gb-panel-grad); border-color: transparent; padding: 20px; border-radius: 14px;
}
.gb-preview-goods h3, .gb-preview-goods p { color: var(--gb-on-green); }
.gb-preview-goods > p { color: var(--gb-on-panel); font-size: 13.5px; margin-top: 12px; }
.gb-preview-head { display: flex; align-items: center; gap: 9px; margin-bottom: 14px; }
.gb-preview-headtext { flex: 1 1 auto; min-width: 0; }
.gb-preview-head h3 { font-size: 15.5px; font-weight: 800; line-height: 1.25; }
.gb-preview-sub { display: block; font-size: 12px; color: var(--gb-on-panel); }
.gb-preview-icon {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 34px; height: 34px; border-radius: 9px; background: var(--gb-panel-inset);
}
.gb-preview-icon svg { width: 17px; height: 17px; fill: none; stroke: var(--gb-on-green);
                       stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
/* Position dots are DECORATION and are aria-hidden: the strip is not a carousel a person can steer, so
   they must never read as controls to a screen reader or invite a tap that does nothing. */
.gb-preview-dots { display: flex; align-items: center; gap: 5px; flex: none; }
.gb-preview-dots i { width: 4px; height: 4px; border-radius: 999px; background: var(--gb-panel-dot); }
.gb-preview-dots i.on { width: 16px; background: var(--gb-on-green); }
.gb-preview-cap { margin-top: 12px; font-size: 11px; font-weight: 700; letter-spacing: .04em;
                  color: var(--gb-on-panel-dim); }
/* The tiles sit on green now, so their chrome follows. */
.gb-preview-goods .gb-goods-tile { background: var(--gb-panel-inset); border-color: transparent; }
.gb-preview-goods .gb-goods-name { color: var(--gb-on-panel); }
.gb-preview-goods .gb-goods-price { color: var(--gb-on-green); }

/* The Operations panel head, matched to the Goods one (frame 2a draws both the same way). The icon sits on
   a pale green inset here rather than a white one, because this panel is on the light surface. */
/* `flex-direction: row` is explicit and load-bearing: the ORIGINAL .gb-opsdash-head rule earlier in this
   file sets `column`, and adding a second rule without resetting it left the icon, the title and the
   dots stacked and centred — visible in the render, invisible in the CSS. A later rule overrides the
   properties it names and inherits the rest; it does not replace the earlier block. */
.gb-opsdash-head { display: flex; flex-direction: row; align-items: center; gap: 9px; margin-bottom: 14px; }
.gb-opsdash-headtext { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.gb-preview-icon-ops { background: var(--gb-green-soft); }
.gb-preview-icon-ops svg { stroke: var(--gb-green-deep); }
.gb-preview-dots-ops i { background: var(--gb-border-strong); }
.gb-preview-dots-ops i.on { background: var(--gb-green-deep); }

/* ── the card's vertical rhythm, frame 2a exactly ──────────────────────────────────────────────
   Henry, 2026-09-06: "the mock is more arranged". He is right, and this is the whole of why. The frame
   spaces this card DELIBERATELY — 10px under the eyebrow, 8 under the heading, 12 under the module line,
   20 under the description, 20 under the panel — so the eye groups the heading with its module line, and
   separates that group from the description and the panel. I had `gap: 14px` on the column: one uniform
   distance between everything, which is the layout equivalent of saying every word at the same volume.
   Nothing was broken and nothing measured wrong; it simply read as unarranged, which is what he saw. */
.gb-gateway > .gb-eyebrow { margin-bottom: 10px; }
.gb-gateway > h2 { margin-bottom: 8px; }
.gb-gateway > .gb-modline { margin-bottom: 12px; }
/* F-98: on the Business Hub card the module line is now a row of five real controls rather than a
   sentence, so it inherits the same 12px the frame gives that slot — the rhythm Henry named is unchanged,
   only the thing sitting in the slot is. */
.gb-gateway > .gb-modlinks { margin-bottom: 12px; }
.gb-gateway > p { margin-bottom: 20px; }
.gb-gateway > .gb-preview { margin-bottom: 20px; }
.gb-goods-strip { gap: 9px; }

/* THOMPSON 2026-09-11 — the Install how-to under the ☰ sheet's Install App row (shown only when the browser
   cannot offer its own install prompt). Plain sentence, same type as the rows, never a control. */
.gb-sheet-note { margin: 4px 0 10px; padding: 10px 12px; border-radius: 10px; background: var(--gb-green-soft, #eaf3ee); color: var(--gb-ink, #14211c); font-size: 14px; line-height: 1.5; }
