/* 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 ─────────────────────────────────────────────────────────────────────────────────── */
/* jk1514 — `.gb-btn` sets display, which beats the [hidden] attribute (the F-2630 trap). A hidden button stays hidden. */
.gb-btn[hidden] { display: none; }
.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); }

/* O4 (Owen, 2026-09-14) — the Business Hub search box, on the first screen of a phone.
   Sits directly under the hub heading and above the five module links: typing is the faster of the two
   paths, so it comes first. See the long note at its markup in home.html for why it is a plain GET form.

   44px on BOTH controls, the minimum this stylesheet sets for itself at the top of the file — a thumb on a
   cheap Android is the measurement that matters, and a search box you cannot reliably hit is not a search
   box. The input is allowed to shrink (min-width:0) so a long placeholder can never push the Search button
   off the edge on a 320px screen; the button never shrinks. */
/* 🔴 F-2207 (owen, 2026-09-15) — THIS FORM MADE THE PUBLIC MARKETING HOME SLIDE SIDEWAYS ON A 360px PHONE,
   AND I WROTE IT (O4). Measured: documentElement clientWidth 360, scrollWidth 401.
   The card around it, `.gb-gateway`, is a grid item with the default `min-width:auto`, so it grows to its
   MIN-CONTENT. I measured every direct child's min-content against the 260px content track:
       gb-gateway-actions 199 · gb-preview 177 · h2 101 · gb-modlinks 93 · p 80 · eyebrow 65   ← all fit
       .gb-hubsearch      319                                                                  ← 59px too wide
   Every other child on that card shrinks. Mine did not, so the card rendered 381px inside a 320px track.

   ⚠️ `min-width: 0` ON THE INPUT WAS ALREADY HERE AND WAS NOT ENOUGH — and neither was `flex: 1 1 0`, which I
   injected into the live page and watched change NOTHING (scrollWidth stayed 401). WRAPPING is what lets the
   row give up its width. I know both of those because I tested each candidate in the real cascade before
   editing this file; a fix argued from the stylesheet and never injected is a guess with a diff attached. */
.gb-hubsearch {
  display: flex; gap: 8px; align-items: stretch;
  flex-wrap: wrap;
  margin-top: 10px; margin-bottom: 12px;
}
.gb-hubsearch__in {
  flex: 1 1 auto; min-width: 0;
  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-text-strong);
  font-family: inherit; font-size: 16px; /* 16px stops iOS zooming the page on focus */
  line-height: 1.2;
}
.gb-hubsearch__in::placeholder { color: var(--gb-text-muted); opacity: 1; }
.gb-hubsearch__in:focus-visible { border-color: var(--gb-green-mid); }
/* The button carries the WORD "Search", never a bare glyph: an icon alone leaves a first-time user
   guessing, and colour is never the only thing telling you what a control does. */
.gb-hubsearch__go { flex: 0 0 auto; min-height: 44px; }

/* ── THE LAST THREE UNDER-44 CONTROLS ON THIS PAGE (Owen 2026-09-15, F-0906 follow-up) ────────────────────
   I filed these and left them once, reasoning that a logo is a conventional affordance and that raising a
   site header is risky in launch week. Henry's rule is that a finding inside your lane gets FIXED, so I
   measured the neighbourhood instead of guessing — and the risk I was avoiding turned out not to exist:

     .gb-brand (header)  104x34  inside div.gb-wrap  h=64   -> 30px of slack
     .gb-brand (footer)  350x34  inside a 133px box         -> plenty
     "See full pricing"  105x18  inline, inside p.gb-disclaim h=84

   Every parent already stands taller than 44, so the hit areas grow INSIDE space that is already there and
   NOTHING MOVES. Measuring first is what turned "too risky four days out" into "safe and free".

   The logos get a real min-height because they are flex boxes and can simply be taller. The pricing link is
   inline INSIDE A SENTENCE, so it gets the padding-plus-equal-negative-margin treatment proven on the
   sign-in doors: the thumb gets its 44, the text does not move a pixel. inline-block is required or vertical
   padding is ignored for layout, which is why it measured 18px in the first place. */
.gb-brand { min-height: 44px; }
.gb-disclaim a {
  display: inline-block;
  padding: 13px 8px;
  margin: -13px -8px;
  /* 🔴 THE LINE BOX MUST NOT GROW. An inline-block establishes its own box, and by default that box's height
     feeds back into the line it sits on — my first attempt added 10px to the document (6400 -> 6410), which
     breaks the one promise this technique makes: the hit area grows, the layout does not. Pinning the
     line-height and the baseline keeps the inline-block sitting in exactly the line it always sat in. */
  line-height: inherit;
  vertical-align: baseline;
}

/* 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; }
/* 🔴 JOE #225 (1 Oct): these were soft green PILLS — "they look exactly like buttons. I pressed Search & categories and
   nothing happened." #196's rule: a thing you cannot press must not look like one. They are a feature LIST, so they
   now read as one: plain words with a tick, no background, no pill, no pointer. Jackson 2026-10-01. */
.gb-chips { display: flex; flex-wrap: wrap; gap: 4px 16px; margin-top: 14px; }
.gb-chip { background: none; color: var(--gb-green-deep); border-radius: 0; padding: 0; cursor: default;
           font-size: 13.5px; font-weight: 700; }
.gb-chip::before { content: '✓ '; font-weight: 800; }
.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); }
/* jk1485 - a step that IS a door looks like one: a whole-row target, the words in the link colour. */
a.gb-step--go { min-height: 44px; text-decoration: none; border-radius: 10px; margin: -4px -6px; padding: 4px 6px; }
a.gb-step--go .gb-step-s { color: var(--gb-green-deep); font-weight: 700; text-decoration: underline; }
a.gb-step--go:hover, a.gb-step--go:focus-visible { background: var(--gb-green-soft); }

/* 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;
  /* F-2227: 15px -> 16px. Under 16px iOS zooms the page on focus. This is the "Track a parcel - no account
     needed" box, the one flow Joe confirmed works end to end - so on an iPhone the one thing that works
     lurches the moment he touches it. The field already has min-height 48px, so the box does not grow. */
  /* line-height is PINNED to 23.25px — the value the 15px field already inherited (15 x 1.55) — so the box
     stays exactly 49.25px tall and the Track button below it does not move. 23px looked right at whole
     pixels but shrank the box 0.25px and the button rose; only an unrounded measurement caught it. */
  padding: 12px 15px; font: inherit; font-size: 16px; line-height: 23.25px;
  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; }
/* GIOBIZ_WORDING.md §1 short footer wording. Deliberately NOT the amber card used inside the app:
   a permanent standing statement in a footer is not an alert about the thing you are looking at, and
   dressing it as one would teach people to ignore the amber card where it actually matters.
   It is sized ABOVE the copyright line beside it so it reads as content rather than small print —
   the document forbids hiding safety wording away. Contrast checked: --gb-text on the footer
   background, not the muted grey used for the copyright. */
.gb-safety-foot { margin-top: 14px; font-size: 13.5px; line-height: 1.6; color: var(--gb-text); max-width: 68ch; }
.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; }
}

/* ── 🔴 F-2207 (owen, 2026-09-15) — THE SAME HEADER ROW, FIVE PIXELS OVER AT 360 ──────────────────────────
   The F-328/F-329 block above fixed this row and its own note records the proof: *"Measured after: innerWidth
   390, scrollWidth 390 … right edge at 370.4 — 19px of slack."* THAT WORK IS CORRECT AND I HAVE NOT TOUCHED
   IT. It was measured at 390, which was the floor at the time.

   🔑 THE CHIEF RULED THE FLOOR DOWN TO 360 TODAY — *"the cheapest Android a Nigerian seller actually uses is
   360 wide"* — and at 360 the same row runs over. Measured, with the composition named rather than guessed:
       Sign Up 86 + Sign In 79 + ☰ 44 + two 8px gaps = 225px, starting at x=140 → right edge 365 on a 360 screen.
   Five pixels. Not a regression by anyone: a width nobody had been asked to support until this morning.

   🔒 WHERE THE FIVE PIXELS COME FROM, AND WHERE THEY EMPHATICALLY DO NOT. The block above states its own
   constraint — *"NOTHING IS SHRUNK PAST THE FLOOR. font-size stays 15px and min-height stays 48"* — and this
   obeys it: HORIZONTAL spacing only. Vertical padding is untouched, so every control keeps its 48px height,
   and the ☰ keeps `min-width/min-height: 44px` exactly. I measured the alternative and rejected it: buying the
   five pixels from the hamburger would trade a visible overflow for a control a thumb misses, on the page every
   stranger in the country lands on. A layout fault you can see beats a tap target you cannot hit.
   Sign Up / Sign In go from 86/79 to about 78/71 — still far above the 44 floor.

   ⚠️ PROVEN IN THE LIVE CASCADE, NOT ARGUED FROM THIS FILE. Injected at 360: `gap:8px` alone changed nothing
   (it is ALREADY 8px above — the obvious candidate was a no-op), and only gap 6 + horizontal padding 10 brought
   documentElement scrollWidth to 360 with ZERO elements past the edge. */
@media (max-width: 399px) {
  .gb-navcta { gap: 6px; }
  .gb-navcta .gb-btn { padding-left: 10px; padding-right: 10px; }
}

@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.
   ── 🔴 AA CONTRAST ON THE PRODUCT TILES (Owen 2026-09-15) ────────────────────────────────────────────────
   A contrast sweep called these six strings "unmeasurable — text on a photo". THEY ARE NOT ON A PHOTO:
   .gb-goods-tile is a flex COLUMN, so the <img> is the top row and the name/price are rows BELOW it on the
   tile's own tint. That is a colour stack, and a colour stack composites exactly. Compositing it against the
   WORST of the panel gradient's two endpoints (#0E6B3C) gave:
       "Fan" / "Pot" / "Digital scale"   3.35:1   (needs 4.5)
       "₦15,000" / "₦50,000" / "₦45,000" 4.42:1   (needs 4.5)
   Six real failures on the first screen a stranger sees, hiding behind the word "unmeasurable".

   🔑 THE TEXT COLOUR WAS NOT THE PROBLEM — THE TILE WAS. At the old .18 tint even PURE WHITE tops out at
   4.42, so no amount of brightening the label could ever have passed. The tint lightened the panel to
   rgb(57,134,95) and capped the whole scale. Measured the trade instead of guessing at it:
       tint .18 -> white 4.42 fail      tint .12 -> white 5.05 PASS      tint .02 -> 0.78-white 4.52 PASS
   So: tint to .12 AND make the name fully opaque. Both land at 5.05, a real margin rather than a squeak.

   🔒 SCOPED TO THE PREVIEW TILES. --gb-panel-inset and --gb-on-panel are SHARED tokens with other jobs on
   other surfaces; changing them to fix three product cards would move colours nobody asked me to move. The
   literals live here, where the failure is. */
.gb-preview-goods .gb-goods-tile { background: rgba(255, 255, 255, .12); border-color: transparent; }
.gb-preview-goods .gb-goods-name { color: #FFFFFF; }
.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; }

/* ch1013 — the bottom install bar (home.html #gb-installbar). HENRY 2026-10-13: "when user enter giobiz.com 3 to
   6 secs they see install button". Tokens only, per the one-:root rule at the top of this file.
   🔒 IT CANNOT WIDEN THE PAGE (F-328 measured 461 on a 390 phone once): it is `position: fixed` with BOTH left and
   right set, so its width is the viewport minus the gutters and nothing inside it can push the layout viewport;
   the text wraps (min-width: 0) instead of forcing width. It sits above the bottom edge with safe-area padding,
   and home.html pads the body by its height while it shows, so nothing at the foot of the page is ever trapped
   under it. Buttons are .gb-btn (48px tall, already above the 44 floor). */
.gb-installbar {
  position: fixed; z-index: 80;   /* under the ☰ sheet (90), above the sticky header (60) */
  left: 12px; right: 12px; bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  max-width: 640px; margin: 0 auto;
  display: grid; grid-template-columns: auto 1fr; align-items: center; gap: 10px 12px;
  padding: 14px; background: var(--gb-card); color: var(--gb-text-strong);
  border: 1.5px solid var(--gb-border-strong); border-radius: var(--gb-r-card);
  box-shadow: var(--gb-shadow-card);
  animation: gbInstallBarUp .32s ease-out;
}
.gb-installbar[hidden] { display: none; }
.gb-installbar-mark { flex: none; }
.gb-installbar-text { min-width: 0; font-size: 15px; line-height: 1.4; color: var(--gb-text-body); }
.gb-installbar-text b { color: var(--gb-text-strong); }
.gb-installbar-actions { grid-column: 1 / -1; display: flex; gap: 10px; }
.gb-installbar-actions .gb-btn { flex: 1 1 0; min-height: 48px; padding: 10px 14px; }
.gb-installbar-howto { grid-column: 1 / -1; margin: 0; padding: 10px 12px; border-radius: 10px;
                       background: var(--gb-green-soft); color: var(--gb-text-strong); font-size: 14px; line-height: 1.5; }
@media (min-width: 768px) {
  .gb-installbar { grid-template-columns: auto 1fr auto; }
  .gb-installbar-actions { grid-column: auto; }
  .gb-installbar-actions .gb-btn { flex: none; }
}
@keyframes gbInstallBarUp { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: none; } }
/* The site-wide reduced-motion rule above already stops this; said again here so it survives that rule moving. */
@media (prefers-reduced-motion: reduce) { .gb-installbar { animation: none; } }
