/* Montserrat - the canonical FuelFox display face, pulled from the design
   system (~/Desktop/design-system/fonts) on Jamey's call that "Geometos is a
   bad design choice anyway". Self-hosted, so it adds no network request and
   Open Sans still appears nowhere in this output. Geometos's six @font-face
   rules are gone; the .woff2 files stay on disk, unreferenced.

   ONE @font-face, not three, and the weight RANGE is the load-bearing part.
   The design system ships montserrat-{600,700,800}.woff2 as three
   BYTE-IDENTICAL copies (all md5 c154477b9affa3a0a47f894c8b80c03c) of a
   single VARIABLE font - the per-weight filenames are a fiction. Read out of
   the fvar table of the matching Montserrat[wght].ttf, its `wght` axis is
   min 100, max 900, and its DEFAULT IS 100 (Thin).

   So the obvious translation of those filenames is a trap:
     @font-face { font-family:'Montserrat'; font-weight:700;
                  src:url('/fonts/montserrat-700.woff2') }
   pins the face to a single weight, which leaves the axis at its default and
   renders HAIRLINE THIN - and the browser will not even synthesise bold to
   cover it, because the face claims to already BE 700. `font-weight: 100 900`
   is what tells the browser to drive the axis from the used weight instead.
   The design system's own fonts/README records this same trap biting the iOS
   app ("a naive check cannot tell success from failure").

   Only one copy is vendored here, renamed for what it actually is, so nobody
   adds a second face pointing at a phantom weight file. */
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/fonts/montserrat-variable.woff2') format('woff2');
}

/* Hanken Grotesk - the design system's `--font-body`, replacing Carrois
   Gothic on Jamey's call. The reason is not taste: Carrois Gothic ships ONE
   weight. Measured rather than assumed - the same string at 400 and at 700
   renders to an IDENTICAL 413.01px, where Montserrat moves 4.6% between the
   two. So every `font-weight: 600/700` on body text was the browser SMEARING
   the regular outline, which is the fuzziness Jamey spotted on /support/'s
   list labels. `document.fonts.check()` cannot detect this: it returns true
   for any weight once any face of the family has loaded.

   Two @font-face rules, one per subset, copied from the design system's own
   typography.css including its unicode-ranges - so a page of plain English
   fetches only the 34KB latin file and never the latin-ext one. One variable
   WOFF2 spans 100-900, so unlike Montserrat there are no per-weight
   filenames to get wrong.

   The design system's weight rule, which this file now follows: 300/400 for
   body and paragraphs, 500 for UI labels and emphasis, and 600+ reserved for
   Montserrat. That is why `<strong>` in body copy is 500 here and not 700 -
   real 500 beats faux 700, and 600+ in the body face would compete with the
   headings. */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/fonts/hanken-grotesk-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/fonts/hanken-grotesk-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --ff-orange:     #e74025;
  --ff-text:       #404040;
  --ff-ground:     #f4f4f4;
  --ff-dark-red:   #9b1b1e;
  --ff-muted:      #adb4b9;
  --ff-hero-wash:  rgba(109, 24, 25, 0.77);
  /* The live site washes photo bands with three different overlays, and
     they are three different colours - not one value rounded three ways.
     Measured off the live pages' own computed `background-image`:
       --ff-hero-wash   rgba(109,24,25,.77)  home #section-3-2, every
                        inner page's hero, and #section-86-9 (the CTA band)
       --ff-wash-red    rgba(155,27,30,.77)  home #section-102-2 ("Hitting
                        the Road Near You") and /opportunity/'s
                        #section-120-12 ("Fleet Owners Benefit..."). A
                        lighter, redder maroon than the hero's - 46 points
                        of red apart.
       --ff-wash-shade  rgba(0,0,0,.61)      /support/ #section-56-14 and
                        /opportunity/ #section-182-12. Not a brand colour
                        at all: a neutral darkener over a photo.

     THIS COMMENT USED TO SAY --ff-wash-red WAS home #section-102-2 "only",
     AND THAT WAS WRONG. Live washes #section-120-12 with the same hue at
     alpha .73, not .77 - a FOURTH value, found by the background-art sweep
     after this file had already asserted there were three.

     .73 IS DELIBERATELY NOT GIVEN ITS OWN TOKEN, and that is a judgement
     call, not an oversight. Composited over the actual photograph
     (Your-Fuel-Delivered-Truck-8.5x11-01.webp, sampled at 200x154 = 30800
     pixels) the two alphas differ by a mean deltaE2000 of 1.13, a 95th
     percentile of 1.82 and a maximum of 2.78 - i.e. 96% of the frame sits
     under the ~2 that a difference has to reach before it reads as a
     different colour on textured photography, and the largest per-channel
     gap anywhere in the frame is 9 of 255. The two bands are also on
     different pages, so no one ever sees them side by side, which is the
     only viewing condition in which a sub-2 deltaE is legible at all.
     White text clears AA over both (worst pixel 5.07:1 at .77, 4.61:1 at
     .73; the only text on that band is a 40px/800 h2, where the bar is 3:1).
     A --ff-wash-red-73 would therefore be a token whose sole distinguishing
     property is invisible, and one more place to edit when the deferred
     rebrand pass moves #9b1b1e to #9c182f. */
  --ff-wash-red:   rgba(155, 27, 30, 0.77);
  --ff-wash-shade: rgba(0, 0, 0, 0.61);
  /* The live site's real elevation shadow (its two other box-shadows are
     the header's 0 0 10px rgba(0,0,0,0.3), used raw in Task 3, and this
     one). `rgba(0,0,0,0.11)` - what this token held before - is not a
     shadow anywhere on the live site: all twelve uses of that colour are
     `border-bottom-color`, a hairline. Kept below as --ff-hairline so
     that real value still has an honest name. */
  --ff-shadow:     0 4px 17px -10px #1e1e1e;
  --ff-hairline:   rgba(0, 0, 0, 0.11);
  /* A SEPARATE token from --ff-hairline, on purpose. Live draws both the
     form-field border and its section-divider hairlines in rgba(0,0,0,.11),
     and this file reused one token for both - but they are not the same
     kind of thing under WCAG. A divider is decoration and has no contrast
     requirement; a text field's border is the only thing that says where
     the control is, which puts it under 1.4.11 Non-text Contrast at 3:1
     against what sits either side of it. rgba(0,0,0,.11) over white
     composites to #e3e3e3 and measures 1.283:1 - not close.

     rgba(0,0,0,0.45) composites to #8c8c8c: 3.363:1. Both surfaces this
     form sits on are white - the e-book card inside the hero and CTA
     bands, and the page itself on /get-started/ - and the field interior
     is white too, so that one number covers both sides of the border in
     both places. It also still clears the bar (3.057:1) if a field ever
     lands on the `--ff-ground` grey. rgba(0,0,0,0.42) is the arithmetic
     minimum (3.033:1) and was rejected for having no margin at all - a
     hair of rounding either way and it fails. At 1px it still reads as a
     hairline rather than a heavy box; the weight is unchanged, only the
     value. */
  --ff-field-border: rgba(0, 0, 0, 0.45);
  /* The footer's ground. `--color-bg-base` from the design system
     (~/Desktop/design-system/tokens/colors.css), which is also the only
     background its brand guidelines permit the on-dark logo variant on:
     "valid on --color-bg-base (#161412) or darker. Nowhere else."
     Taken now because Jamey moved the footer off orange; the rest of the
     colour migration is still deferred. */
  --ff-footer-ground: #161412;
  /* Placeholder and de-emphasised helper text. 4.83:1 on white - over the
     4.5 bar, because a placeholder that states the question is content, not
     decoration. */
  --ff-placeholder: #6b6b6b;
  --ff-radius:     8px;
  --ff-container:  1300px;
  --ff-display:    'Montserrat', 'Hanken Grotesk', system-ui, sans-serif;
  --ff-body:       'Hanken Grotesk', system-ui, -apple-system, sans-serif;
}

/* Vendored element-selector overrides -------------------------------------
   shared/css/stylesheet.css is fuelfox.net's legacy theme and carries 118
   bare element selectors written for a different layout. Franchising's
   markup is deliberately semantic, so it catches nearly all of them. The
   first audit pass here filtered by property name and printed one line
   per selector, which hid that a single selector can carry several
   declarations, and it only searched single-element selectors, which
   missed compound ones like `nav ul`. Re-verified in full; these are the
   complete rule bodies that actually touch the header and nav:

     img    { width:100%; max-width:none; height:auto; display:block }
     nav    { background:#fff; width:100%; height:90px; position:fixed;
              z-index:999; box-shadow:0 0 10px rgba(0,0,0,0.1) }
     nav ul { list-style:none; margin:0; position:absolute; top:33px;
              right:25px; display:flex; flex-wrap:wrap }
     header { margin:90px 0px 0px }

   `header`'s margin is what was pushing the fixed header 90px down from
   top:0 - `getComputedStyle` reporting `top: 0px` was honest the whole
   time; a margin, not a positioning quirk, was doing the pushing.

   There is also a second, narrower `nav ul` at `@media (max-width:1350px)`
   (`text-align:center; background:#231f20; width:100%; height:100%;
   padding:20px 0 0; position:fixed; top:90px; right:0; display:none`).
   1350px is wider than our own <991px breakpoint, so a version of this
   reset that only fired inside our own media query left a real gap:
   992-1350px (a 13" laptop, or any half-tiled window) got the vendor's
   #231f20 background under #404040 link text - about 1.6:1 contrast,
   nowhere near WCAG's 4.5:1, and neither desktop testing (above 1350px)
   nor sub-991px testing caught it, because the CSS is correct at both of
   those widths - the bug lived entirely in the gap between them. Fixed by
   making the `nav ul` reset unconditional (below) so it applies at every
   width the vendor rule could otherwise reach, in either of its forms;
   the sub-991px query then only layers the dropdown's own needs
   (position:absolute, its own white background) on top of that neutral
   baseline, rather than re-establishing the baseline itself.

   `section { position:relative }` is also bare and matched, and benign -
   left alone. `footer { background:#1e1a1b; width:100%; padding:30px 5%
   20px }` is bare and matched too, and deliberately NOT neutralized here:
   its dark background happens to land close to what the live franchising
   footer wants, but that would be arriving by accident, not by choice.
   Task 7 owns the footer and should decide on that background on purpose
   rather than silently inherit it.

   Warning for whoever writes that: `.ff-footer nav` and `.ff-footer nav
   ul` above sit at specificity (0,1,1) and (0,1,2). A single class like
   `.ff-footer-nav { position: relative }` is only (0,1,0) - it will lose
   to this reset silently, regardless of source order, the same way the
   vendor's bare tag selectors lost to nothing for months before this task.
   Qualify through `.ff-footer` (e.g. `.ff-footer .ff-footer-nav`) or match
   this reset's specificity, don't fight it head-on. */

.ff-header {
  margin: 0;
}

.ff-header nav,
.ff-footer nav {
  background: none;
  width: auto;
  height: auto;
  position: static;
  z-index: auto;
  box-shadow: none;
}

.ff-header nav ul,
.ff-footer nav ul {
  background: none;
  position: static;
  width: auto;
  height: auto;
  top: auto;
  right: auto;
  text-align: left;
}

main img {
  width: auto;
  max-width: 100%;
  height: auto;
  display: block;
}

/* shared/css/stylesheet.css sets `overflow-x: hidden` on both `html` and
   `body` with no `overflow-y`. Any non-`visible` computed overflow makes
   an element a scroll container, and `position: sticky` resolves against
   the *nearest* scrolling ancestor, not the viewport, once one exists.
   `.ff-header`'s parent is `body`, so body - despite never actually
   scrolling itself (confirmed: its own scrollTop stayed 0 through a real
   scrollTo() test while window.scrollY moved normally via `html`, the
   real scrollingElement) - was sticky's reference instead of the
   viewport, and the header scrolled away with the page instead of
   staying pinned.

   Overriding only `overflow-y` back to `visible` does NOT fix this: CSS
   forces a `visible` value on one axis to compute to `auto` whenever the
   other axis is non-`visible` (so `overflow-x: hidden` + `overflow-y:
   visible` computes to `overflow-y: auto` anyway, regardless of cascade
   order) - confirmed by testing it first and finding overflow-y still
   computed to `auto`. Both axes have to be `visible` at once to avoid
   that coupling, which means giving up the horizontal-scrollbar guard
   `overflow-x: hidden` was there for - only for `body`, only on
   fuelfoxfranchising.com (franchising.css never loads on fuelfox.net;
   shared/css/stylesheet.css itself is untouched). Checked every width
   probed for this fix (see the fix-round-3 report) for horizontal
   overflow with this change in place before committing to it - none
   found - but flagging plainly that this trades away a defensive rule,
   not just neutralizes an accidental one. */
body {
  overflow: visible;
}

body {
  font-family: var(--ff-body);
  font-size: 15px;
  line-height: 1.6;
  color: var(--ff-text);
}

/* `text-transform` and `font-weight` are declared here because shared/css
   sets both on bare `h1`-`h4` (`font:900 76px/78px "Open Sans";
   text-transform:uppercase`) and this rule once named only family, colour
   and line-height - so the vendor's values were the ONLY declarations for
   the two it skipped, and every heading on all nine pages computed to
   UPPERCASE at weight 900. Measured on fuelfoxfranchising.com:
   `font-weight: 800`.

   *** THE CAPS ARE NOW LOAD-BEARING CSS. READ THIS BEFORE TOUCHING IT. ***

   This rule said `text-transform: none` right up until the Montserrat swap,
   and every heading still rendered in caps - because GEOMETOS HAS NO
   LOWERCASE GLYPHS AT ALL. It maps lowercase codepoints onto cap forms
   (`abcdefg` and `ABCDEFG` both measured 336x102 at 64px, where Carrois
   Gothic measured 220 vs 248), so the caps came from the TYPEFACE and the
   `text-transform` value was inert decoration.

   Montserrat has a full lowercase. Swapping the face therefore silently
   converts every heading on the site to title-case unless the caps are
   asked for explicitly - a change on every page that looks like nothing in
   the diff, because the diff is one font name.

   DECISION: preserve the caps. `text-transform: uppercase` below is that
   decision, written as one declaration on purpose. The live site renders
   these headings in caps and this pass is "finish matching live first", so
   caps is the no-surprise default; but now that the face CAN set lowercase,
   title-case is a real option for the rebrand pass and reversing it is a
   ONE-WORD edit here (`uppercase` -> `none`), not a hunt.

   Fourth instance of this plan's standing failure (Task 5's
   `.submit-button`, Task 6's `p`/`li`, Task 6d's grid margins): a rule you
   otherwise fully override still leaks any property you never declare. */
h1, h2, h3, h4, .ff-display {
  font-family: var(--ff-display);
  color: var(--ff-text);
  line-height: 1.15;
  font-weight: 800;
  text-transform: uppercase;
}

a { color: var(--ff-orange); }

/* Layout ------------------------------------------------------------------
   The live site has no spacing system - its section padding is per-section
   rules on Oxygen-generated ids. This is a small scale invented to replace
   them, deliberately plain so the components carry the design.

   The container is a DEFAULT plus an opt-out, not a list of tags. It was
   first written as `main > section, main > h1` - the two element types the
   home page happened to have - which silently left every other top-level
   child edge-to-edge with zero horizontal padding: body copy literally
   touching the viewport edge on 8 of 10 pages (18 of the 21 top-level
   children of /privacy-policy/, the whole `div.ff-faq` accordion, the
   whole `form.ff-form` on /get-started/). A whitelist can only ever
   describe the markup that existed when it was written, and the failure
   mode when a new page adds a different top-level element is invisible in
   the build and in `validate`/`links` - nothing 404s, the text is just in
   the wrong place. So: everything is contained, and running full-bleed is
   something an element has to ASK for.

   The one asker today is `.ff-hero` (0,1,0), which beats this rule's
   (0,0,1) and re-does the same centering with padding math so its photo
   can reach the viewport edge. Anything else that wants to bleed must
   likewise out-rank (0,0,1) - a single class is enough; a bare tag
   selector is not.

   `main > img` is spelled out only to reach (0,0,2) and so out-rank the
   `main img { max-width: 100% }` override further up this file, which
   otherwise wins on specificity regardless of source order and lets a
   top-level image bleed. Measured, not assumed: with `main > *` alone,
   /about/, /opportunity/ and /support/ each still reported their `<img>`
   wider than the container at a 1400px viewport. It wants exactly the
   same box as every other child, hence the shared declaration block
   rather than a competing patch rule.

   The `min(100%, ...)` matters only for those images, and it is the
   second half of that same override: a block box with `width: auto`
   already shrinks to its containing block, so a bare `max-width: 1300px`
   is enough for every other child - but an `<img>` sizes from its
   intrinsic width (Truck-2.webp is 1600px wide), so replacing the 100%
   cap with a flat 1300px sent all three images straight out through the
   right edge of every viewport under 1300px. Measured too: at 992px they
   overflowed the document before the `min()` went in. */

main > *,
main > img {
  max-width: min(100%, var(--ff-container));
  margin-inline: auto;
  padding-inline: 24px;
}

main > section { padding-block: 56px; }

/* The live site's shaded bands are not positional: fuelfoxfranchising.com
   assigns each section's background by content (hero photo, brand-color
   block, pattern image, or plain white), not by index. Its 4-section About
   page carries one gray band, on the last section; its 6-section Opportunity
   page carries none at all. nth-of-type(even) would invent a pattern the
   original site doesn't have. `.ff-band` is the honest equivalent - later
   component tasks apply it to whichever sections should carry the tint.

   A band has to BLEED. Left as a plain `background` declaration, `.ff-band`
   inherited the container above (max-width 1300, margin-inline auto) and
   painted the tint only inside that column - a floating grey rectangle with
   white gutters, which the live site does nowhere. Measured on
   fuelfoxfranchising.com/about/ at a 1475px viewport: its one #f4f4f4
   section, `#section-72-10`, sits at `x=0 w=1475` with an inner wrap at
   `x=88 w=1300`. The band bleeds; the content column does not.

   No markup change is needed for that. `.ff-header` and `.ff-hero` already
   solve the identical problem with padding math, and this is the THIRD user
   of it - treat it as
   the established pattern for "full-bleed bar, centred content", not as a
   one-off. Reproduced against the real banded section in this build, not
   trusted from a devtools patch: see the task-6 report.

   `.ff-cta-explore` used to ride this rule as a second, grey CTA band.
   Task 6c replaced it with `.ff-cta-band`, which is not tinted - it is the
   hero composition reused.

   THE BLEED RULE. Task 6d made this the single home of that math for every
   element on the site that wants it. It had been copied three times
   (`.ff-header`, `.ff-hero`/`.ff-cta-band`, and this grey band); adding the
   five section grounds below would have made it eight copies of one
   `calc()` across four rules. Now the selector list is the thing that
   grows and the declarations are written once - `.ff-header` and
   `.ff-hero`/`.ff-cta-band` no longer carry their own copies, they are
   entries here.

   `.ff-header` is not a `main` child, so it is listed bare; everything
   else is `main > .class` (0,1,1), which out-ranks both `main > *` (0,0,1)
   and `main > img` (0,0,2) from the container rule above. Block padding is
   deliberately NOT in here: the header wants 16px, the hero and CTA band
   64px, and the grounds 56px, so each declares its own. */
.ff-header,
.ff-hero,
.ff-cta-band,
.ff-footer,
main > section.ff-band,
main > .ff-ground-orange,
main > .ff-ground-red,
main > .ff-ground-wash,
main > .ff-ground-shade,
main > .ff-ground-pattern,
main > .ff-photo-band {
  max-width: none;
  margin-inline: 0;
  /* The `+ 24px` aligns banded content with contained content. Without it the
     two systems land 24px apart: a contained child is a 1300px box centred by
     `margin-inline: auto` and THEN padded 24px inside, so its text starts at
     `(100% - 1300px)/2 + 24`, while a band that stops at the container edge
     starts its text at `(100% - 1300px)/2`. Measured on the home page at a
     1386px viewport before this: banded text x=43, plain-section text x=67, a
     stagger running the whole length of the page.
     Below 1300px both arms collapse to the same 24px, so they stay aligned at
     every width - checked at 1320 and 1200 as well as the wide case. The
     content column is therefore 1252px in a band, which is exactly what a
     contained 1300px box with 24px padding gives. */
  padding-inline: max(24px, calc((100% - var(--ff-container)) / 2 + 24px));
}

main > section.ff-band {
  background: var(--ff-ground);
}

/* Standalone photo band --------------------------------------------------
   Live's `#section-14-2`, the full-bleed picture sitting directly under the
   home hero. Measured on fuelfoxfranchising.com: `min-height: 550px`,
   `background-size: cover`, `background-position: 50% 100%`, NO wash layer
   and no content of any kind - the section is empty markup.

   It is a `<div>`, not a `<section>`. An empty `<section>` with no
   accessible name is a region that announces nothing, and an `<img>` here
   would need an `alt` describing a picture that carries no information the
   page does not already state - so this is decoration, spelled as
   decoration. It joins THE BLEED RULE above for its padding math even
   though it has no content to align; skipping that would leave it a
   1300px-wide rectangle with white gutters instead of a band.

   `background-position` is `50% 100%` (bottom-centre), not `center`. The
   photograph is a wide render of the corporate site's home page and its
   subject sits along the lower edge; centring it crops the subject away at
   this aspect ratio. Measured off live, not chosen. */
main > .ff-photo-band {
  min-height: 550px;
  background-image: var(--ff-ground-img);
  background-repeat: no-repeat;
  background-position: 50% 100%;
  background-size: cover;
  background-origin: padding-box;
}

/* The source is 2000x716, a 2.79 ratio. Held at live's 550px a phone crops
   it to a 375-wide slice of a 1537-wide frame, which throws away most of
   what the picture is - and the picture is the whole section. Live does
   exactly that (its `min-height: 550px` carries no breakpoint), so this is
   a deliberate improvement on it rather than a match: 220px still crops,
   but keeps roughly two-thirds of the frame instead of a quarter. */
@media (max-width: 767px) {
  main > .ff-photo-band { min-height: 220px; }
}

/* Section grounds ---------------------------------------------------------
   Task 6 surveyed the live pages for banding by grepping each section for
   `rgb(244,244,244)` and concluded home bands nothing. True, and the wrong
   question: grey is a minor member of the live site's vocabulary, not the
   whole of it. Re-measured by ANY non-white ground, six of home's ten
   sections carry one. The full survey and the heading-by-heading mapping
   are in the task-6d report; the kinds it found are these five.

   Every one of them is a `main` child that opts out of the container via
   THE BLEED RULE above, so the ground reaches the viewport edge while the
   copy stays in the 1300px column - measured on live, e.g. /about/'s
   `#section-72-10` sits at x=0 w=1475 with its inner wrap at x=88 w=1300.

   `background-color` / `background-image` longhands rather than the
   `background` shorthand: the pattern and photo grounds compose several
   layers and a shorthand in one rule would silently blank the others.
   shared/css's universal reset (`... section ... { background: transparent
   }`, (0,0,1)) is beaten by all of these on specificity, not source order.

   The photo grounds set no `background-color` fallback on purpose: the
   wash gradient is its own bottom layer, so a 404 on the photo degrades to
   flat wash-over-white - rgb(178,79,82) for --ff-wash-red, rgb(99,99,99)
   for --ff-wash-shade - both of which still carry white text above 5:1.

   Adjacent same-ground children collapse the seam between them. The port
   split some live sections into two or three of ours (home's one orange
   `#section-43-2` is three of our sections; /opportunity/'s dark-red
   `#section-120-12` is a top-level `<h2>` plus the `<ol.ff-benefits>` that
   follows it), and without this they would read as separate bands stacked
   with 112px of their own padding between. `margin-block: 0` belongs to
   the same problem: a margin paints nothing, so the vendored
   `h2 { margin: 0 auto 30px }` would have opened a 30px white stripe
   straight through the middle of /opportunity/'s dark-red band. */

main > .ff-ground-orange,
main > .ff-ground-red,
main > .ff-ground-wash,
main > .ff-ground-shade,
main > .ff-ground-pattern {
  padding-block: 56px;
  margin-block: 0;
}

main > .ff-ground-orange + .ff-ground-orange,
main > .ff-ground-red + .ff-ground-red {
  padding-block-start: 0;
}

/* Solid brand colour. Both values are exact matches for tokens already on
   hand, checked digit by digit rather than eyeballed: live's orange band
   computes `rgb(231,64,37)` = #e74025 = --ff-orange, and its dark-red band
   `rgb(155,27,30)` = #9b1b1e = --ff-dark-red. (The footer's orange is
   rgb(232,65,37), one point off on every channel - a different colour, and
   Task 7's to name.) */
main > .ff-ground-orange { background-color: var(--ff-orange); }
main > .ff-ground-red    { background-color: var(--ff-dark-red); }

/* Photo under a flat wash. Two kinds, three uses, three different photos -
   so the wash comes from the kind and the photo from a `--ff-ground-img`
   token set by the small classes below, instead of three near-identical
   copies of this rule. */
main > .ff-ground-wash,
main > .ff-ground-shade {
  background-image:
    linear-gradient(var(--ff-ground-wash), var(--ff-ground-wash)),
    var(--ff-ground-img);
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  background-origin: padding-box;
}

main > .ff-ground-wash  { --ff-ground-wash: var(--ff-wash-red); }
main > .ff-ground-shade { --ff-ground-wash: var(--ff-wash-shade); }

/* The photo tokens. Task 6d introduced these for the two `.ff-ground-*`
   bands; Task 6e made them the site's ONE way to name a band's photo, so
   `.ff-hero` reads the same `--ff-ground-img` and the seven interior-page
   heroes pick their picture by adding one of these classes. Two consumers,
   one vocabulary - a second mechanism for the heroes would have meant two
   places to look up "which photo is on /support/".

   Deliberately unscoped (no `main >`), because the consumers disagree about
   their ancestor chain: the grounds are always `main` children, `.ff-hero`
   is listed bare in THE BLEED RULE and could be used outside `main`. A bare
   class is (0,1,0), and nothing else on the site declares `--ff-ground-img`
   at all, so there is no cascade race to lose - see the note on the hero's
   `var()` fallback below for why that stays true. */
.ff-ground-img-truck    { --ff-ground-img: url('/img/2023/02/Truck-2.webp'); }
.ff-ground-img-safety   { --ff-ground-img: url('/img/2023/02/header_safety-scaled.webp'); }
.ff-ground-img-benefits { --ff-ground-img: url('/img/2023/02/header_benefits-scaled.webp'); }
.ff-ground-img-banner   { --ff-ground-img: url('/img/2023/02/hero-banner-fuel-fox.webp'); }
/* The two WordPress upload names live's /opportunity/ and /support/ heroes
   still carry. Renaming the files would be a passthrough-copy change with
   no visual payoff, so the classes carry readable names instead: `-fleet`
   is the row of parked trucks with a rep beside the lead one, `-tanks` the
   row of trucks in front of the storage tanks. */
.ff-ground-img-fleet    { --ff-ground-img: url('/img/2023/02/53405414_588834984930849_7992223810394783744_n.webp'); }
.ff-ground-img-tanks    { --ff-ground-img: url('/img/2023/02/101795750_910731746074503_4499246901354299392_n.webp'); }
/* The photograph behind the hero and the CTA band, named so /opportunity/'s
   dark-red band can ask for it by name instead of repeating the URL. The
   hero itself still carries this file as a `var()` FALLBACK rather than as
   this class - see the note in `.ff-hero` for why that has to stay a
   fallback. */
.ff-ground-img-delivered  { --ff-ground-img: url('/img/2023/02/Your-Fuel-Delivered-Truck-8.5x11-01.webp'); }
/* The home page's staggered pair. These moved onto `--ff-ground-img` when
   the split photo panels arrived: `.ff-photo` had been the one background
   consumer on the site that named its picture with its own
   `background-image` declaration, which meant two vocabularies for "which
   photo is this", and two classes carrying the same URL the moment a panel
   wanted a file a hero already used. */
.ff-ground-img-growth     { --ff-ground-img: url('/img/2023/02/projected-annual-growth-rate.webp'); }
.ff-ground-img-reputation { --ff-ground-img: url('/img/2023/02/Own-a-Business-Fueled-by-an-Excellent-Reputation.webp'); }

/* The pattern: a 798x648 topographic-contour drawing in near-white, drawn
   once at natural size in the bottom-right corner - live's
   `background-size: auto; background-repeat: no-repeat;
   background-position: 100% 100%`, which is what `right bottom` spells.
   It is decoration on white, not a tint: the live sections carrying it
   keep `color: rgb(64,64,64)`, so no text reversal goes with it.

   `background-origin: padding-box` is the initial value and is declared
   anyway - it is what puts the motif flush in the corner rather than
   inset by this rule's own 56px of block padding, and it is one of the
   eight longhands shared/css's `background: transparent` reset touches. */
main > .ff-ground-pattern {
  background-color: #ffffff;
  background-image: url('/img/2023/02/bg-pattern-1.webp');
  background-repeat: no-repeat;
  background-position: right bottom;
  background-size: auto;
  background-origin: padding-box;
}

/* A band that holds a height ----------------------------------------------
   Home's "Hitting the Road Near You". Jamey read ours as auto-sized against
   a live band that looks locked to a height, and estimated live at ~600px.

   What live actually does is neither: `#section-102-2` has NO `min-height`,
   no `vh` and no percentage. Its inner wrap carries `padding: 150px 20px`
   with `align-items: center`, so the band is 300px of padding around
   whatever the copy needs - 493px at a 1535px viewport, and taller the
   moment the copy wraps to another line. Checked by reading the live rule
   itself, not by measuring one width and calling it fixed, because a
   single measurement cannot tell a locked height from a padded one.

   Reproduced as a floor rather than as 150px of padding, which is what the
   review asked for and is also the more robust of the two: 493px is what
   live comes out at on a desktop, and a `min-height` gives that number
   without letting it become 300px of dead space on a phone where the same
   copy runs to eight lines. `align-content: center` is the vertical
   centring live gets from `align-items: center` on a flex column - the same
   treatment `.ff-hero-solo` already carries.

   `display: grid` makes each child its own row, so `align-content` has a
   track group to centre. That does stop adjacent margins collapsing; the
   children here are a kicker, a heading and one paragraph, and their
   spacing is measured below rather than inherited from a collapse. */
main > .ff-band-tall {
  display: grid;
  align-content: center;
  min-height: 493px;
  /* Live's `#section-102-2` is the ONE parallax band on the site:
     `background-attachment: fixed`, checked in the live rule itself rather
     than inferred from the render. Scoped to `.ff-band-tall` and not to
     `.ff-ground-wash`, because /opportunity/'s wash band is not fixed on
     live and `.ff-ground-wash` now has two users.

     `fixed` re-anchors the positioning area to the VIEWPORT, so
     `background-size: cover` covers the viewport rather than this 493px
     box and the crop is a different one - that is the effect, not a bug,
     and it is what live serves. Two known costs, both accepted rather than
     discovered: `fixed` forces the band out of the page's normal scroll
     compositing on some mobile browsers, and iOS Safari ignores it
     outright, painting the band exactly as `scroll` would. Neither breaks
     the band - the wash, the height and the copy are unchanged either way
     and the photo simply stops moving - which is why it is safe to serve
     the declaration everywhere instead of gating it behind a query. */
  background-attachment: fixed;
}

/* NINTH sighting of the standing lesson, and the first one this file caused
   rather than inherited. shared/css sets `h2 { margin: 0 auto 30px }` and
   `p { margin: 0 auto 30px }`; this file's heading scale and `main p` both
   answer the BLOCK margins and neither mentions the inline ones. Under the
   block layout this band used to have, `margin-inline: auto` on a
   full-width block box resolves to 0 and the leak is invisible. Turn the
   band into a grid and it stops being invisible: an auto inline margin on a
   grid item overrides `justify-self: stretch`, so the item shrinks to
   fit-content and the autos CENTRE it. Measured immediately after the
   change at a 1400px viewport - the kicker had moved to x=501 and the
   heading to x=378 while the paragraph, too long to shrink, stayed at x=74.
   Three left edges in one band, from a declaration nothing in this file
   made.

   Zeroed on the children rather than fought with `justify-self`, because
   `justify-self` cannot win: auto margins are resolved first and take the
   free space regardless. (0,1,1) beats both vendored bare-tag rules. */
main > .ff-band-tall > * {
  margin-inline: 0;
}

@media (max-width: 767px) {
  main > .ff-band-tall { min-height: 380px; }
}

/* Reversed copy. A separate hook from the ground classes because it is a
   separate decision - the pattern ground is not reversed, and a future
   ground might not be either - and because the alternative is repeating a
   five-selector list on each of the four rules below.

   Enumerated, not assumed. `main p`, `main ul li` and `main ol li` already
   take `color: inherit` (Task 6), so body copy and list text follow the
   band's own colour with nothing further. These four do NOT:
     h1-h4        this file pins them to `color: var(--ff-text)`
     .ff-kicker   this file pins it to `color: var(--ff-orange)`
     a            shared/css `a { color:#e74124 }` (0,0,1) and this file's
                  `a { color: var(--ff-orange) }` - orange link text on the
                  orange band is invisible, 1.0:1
     ::marker     this file colours `main ul li::marker` orange, same trap.
                  No `<ul>` sits in a reversed band today; declared so that
                  adding one cannot ship invisible bullets.
   Live renders the one link inside the orange band as a white pill with
   orange text (`.ct-link-button`, padding 16px 32px, radius 8px). This
   port distinguishes `.ff-cta` buttons from bare links, and that link is
   authored as a bare link, so it is reversed to white-and-underlined
   rather than promoted to a button - a divergence, recorded rather than
   silent. `:not(.ff-cta)` keeps a real `.ff-cta` in a band on its orange
   pill; the CTA band's own reversal, further down, is unaffected. */
main > .ff-reverse {
  color: #ffffff;
}

main > .ff-reverse h1,
main > .ff-reverse h2,
main > .ff-reverse h3,
main > .ff-reverse h4,
main > .ff-reverse .ff-kicker {
  color: #ffffff;
}

main > .ff-reverse ul li::marker {
  color: #ffffff;
}

main > .ff-reverse a:not(.ff-cta) {
  color: #ffffff;
  text-decoration: underline;
}

main > .ff-reverse a:not(.ff-cta):hover,
main > .ff-reverse a:not(.ff-cta):focus-visible {
  color: #ffffff;
  text-decoration: none;
}

/* shared/css's universal `* { outline: none }` reaches these links too. */
main > .ff-reverse a:not(.ff-cta):focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
}

.ff-kicker {
  font-family: var(--ff-body);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ff-orange);
  margin-block-end: 8px;
}

h1 { font-size: clamp(32px, 5vw, 56px); }
h2 { font-size: clamp(26px, 3.4vw, 40px); }
h3 { font-size: clamp(20px, 2.2vw, 26px); }
h4 { font-size: 18px; }

/* Section headings are ORANGE on light grounds ----------------------------
   Task 1 gave every heading one colour, `--ff-text`. That is not what the
   live site does, and it is the single biggest reason the port reads flat
   next to it. Live's section-level `h2` takes exactly three colours, and
   which one it takes is decided by the ground underneath:

     ground                            live h2 colour   pages measured
     white / the contour pattern       rgb(231,64,37)   home, /about/,
                                                        /opportunity/, /support/
     grey #f4f4f4  (`.ff-band`)        rgb(64,64,64)    /about/
     brand solid or photo-wash         rgb(255,255,255) all of them

   Every row above was measured by walking each heading's ancestor chain to
   the first painted background rather than eyeballed - the trap Ruling C14
   set was aggregating this colour by frequency across contexts, and the
   grey-band row is exactly the case that would have been rounded away.
   /about/'s "It's Time to Fuel Your Future with FuelFox" is a 30px/800 h2
   sitting on #f4f4f4 and it is `rgb(64,64,64)`, not orange - so the grey
   band is a real exemption, not a hypothetical one.

   `h3` and `h4` are NOT included. Live keeps them grey (its step-card
   titles are `rgb(64,64,64)` at 20px/700); the one orange h3 on the site is
   the lead step card's own title, which this file colours by hand where it
   lives rather than by promoting every h3.

   SPECIFICITY, which is what actually keeps the reversed cases reversed -
   this rule is (0,0,2) and every exemption out-ranks it, so none of them
   depends on source order:
     main > .ff-reverse h2                      (0,1,2)  white   - already above
     main > .ff-reverse   (a self-reversed h2)  (0,1,1)  white   - already above
     .ff-cta-band-copy h2                       (0,1,1)  white   - already above
     main > section.ff-band h2                  (0,1,3)  grey    - below
     .ff-ebook h2                               (0,1,1)  dark    - below

   ONE CONSEQUENCE, stated rather than discovered later: `.ff-reverse` is
   now mandatory on the four reversing grounds. A `.ff-ground-orange`
   without it used to render grey-on-orange (bad, but visible); it would now
   render orange-on-orange (invisible). All eight uses in this build carry
   it - grepped, not assumed. */
main h2 { color: var(--ff-orange); }

/* The two light-ground exemptions. `.ff-ebook h2` is the card heading
   inside the hero form and the CTA band's card - live renders it dark on
   the white card, and it is a component title rather than a section
   heading. It had no `color` declaration of its own before this rule
   existed, so it was riding the base heading colour; now it has to say so. */
main > section.ff-band h2,
.ff-ebook h2 {
  color: var(--ff-text);
}

/* Eyebrow: a section h2 demoted to a label -------------------------------
   Live opens several sections with two headings in a row - a small dark
   one, then the real one. Both are `<h2>` in its markup (`headline-44-2`
   is 17px/600, `headline-295-2` is 30px/800), so this port keeps the
   element and changes only the size, rather than demoting a heading to a
   `<p>` and rewriting the document outline to get a smaller font.

   Distinct from `.ff-kicker`, which is orange on white by design and is
   already spoken for on eight pages. An eyebrow is grey on white (live's
   "FRANCHISE INVESTMENT START-UP COSTS" measures rgb(64,64,64)) and white
   on a reversed band. `main .ff-eyebrow` is (0,1,1), which beats both the
   base heading rule and the orange rule above; the reversed override is
   (0,2,1). An eyebrow is an `<h2>`, so it takes its caps from the base
   heading rule's `text-transform: uppercase` - see the caps note there.
   Under Geometos it needed no `text-transform` at all, because that face
   had no lowercase to transform. */
main .ff-eyebrow {
  font-size: 17px;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: 0.06em;
  color: var(--ff-text);
  margin: 0 0 12px;
}

main > .ff-reverse .ff-eyebrow { color: #ffffff; }

/* A second heading INSIDE a section, under the section's own h2. Live sets
   it 25px against the section heading's 30px; this file's h3 step is the
   same relationship at this file's own scale, so the subhead borrows it
   rather than minting a third size. */
main .ff-subhead {
  font-size: clamp(20px, 2.2vw, 26px);
  margin: 40px 0 15px;
}

/* Header ------------------------------------------------------------------
   Live rule (fuelfoxfranchising.com #_header-2-9, sticky-active state):
   position: fixed; top: 0; left: 0; right: 0; z-index: 999;
   background-color: #ffffff; box-shadow: 0px 0px 10px rgba(0,0,0,0.3).
   We use `position: sticky` instead of the live site's `fixed` - fixed is
   only there because Oxygen emitted it, not because the design needs it.
   A fixed header is pulled out of flow, so something else has to reserve
   its height (`body { padding-top }`) and the two values - header height
   and body offset - have to be kept in sync by hand. That is a two-place
   invariant nobody can actually keep in sync: this header's own height
   isn't constant (it wraps at some widths - see the nowrap rules below),
   and a single hardcoded padding-top already drifted out of sync with it
   once. Sticky occupies its box in normal flow like a static element, so
   there's no offset to invent or maintain - visually it's the same bar,
   pinned at the top and staying there on scroll.

   Checked before switching: `position: sticky` fails silently if a
   scrolling ancestor clips it. `.ff-header`'s only ancestors are `body`
   and `html`; shared/css/stylesheet.css sets `overflow-x: hidden` on both
   with no `overflow-y`, which computes `overflow-y: auto` on both (the
   CSS spec forces a `visible` value on either axis to `auto` when the
   other axis isn't `visible`). Verified in the browser that this does not
   break sticking - the page still scrolls as one normal document, not as
   an internally-scrolling box - see the fix-round-3 report section for
   the scroll test. No wrapper element sits between them, and neither has
   a transform/filter/will-change/perspective that would give `.ff-header`
   a different containing block than intended.

   There is no wrapper element inside .ff-header to hang max-width on the
   way main > section does, so the container width is centered with padding
   math instead, keeping the painted bar full-bleed. */

.ff-header {
  position: sticky;
  inset-block-start: 0;
  z-index: 999;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px 24px;
  background: #ffffff;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
  /* 10px, not the 16px this header shipped with, because the logo artwork
     brings ~6px of its own. See `.ff-logo img` for the clearance arithmetic. */
  padding-block: 10px;
  /* `padding-inline` is THE BLEED RULE's, near the container above. */
}

.ff-logo {
  display: inline-flex;
  flex-shrink: 0;
}

/* The site-wide vendored `img { width: 100%; height: auto }` rule (legacy
   Oxygen CSS, out of scope here) would otherwise stretch the logo to fill
   its flex item instead of the size set in header.njk's width/height
   attributes - pin it back to that intended box.

   WIDTH ONLY, WITH `height: auto`. The box this replaced was `200px x 48px`
   against artwork whose viewBox is 300x96, i.e. a 4.167 box holding a 3.125
   image. An `<img>` letterboxes rather than distorting, so the visible mark
   scaled to min(200/300, 48/96) = 0.5 and rendered 150px wide inside a 200px
   box - 62% of the live site's 240px logo, which is what Jamey saw as "too
   small". Naming one axis and leaving the other `auto` makes a mismatch of
   that kind unrepresentable, and it is also the brand guidelines' first
   Misuse entry ("Stretch or distort any variant").

   SIZE, derived rather than copied - the new artwork is a different shape
   again and no old number transfers. Measured off horizontal-logo.svg by
   taking the union bbox of all 16 leaf shapes in root viewBox units:

     viewBox   0 0 1701 600                ratio 2.835
     ink       1578.9 x 508.1 at (65,45.4) ratio 3.107
     baked pad 45.4 top / 46.5 bottom / 65 left / 57.1 right

   So ~15% of the box height is whitespace the artwork carries itself, and
   the *ink* ratio 3.107 is essentially the live logo's box ratio (240/77 =
   3.117) - the two lockups differ almost entirely in that padding, not in
   proportion.

   227px wide puts the ink at 210.5 x 67.8 and the box at 227 x 80.1. Two
   brand rules bound this, both from
   ~/Desktop/design-system/docs/BRAND_GUIDELINES.md:
     - Minimum Sizes: horizontal lockup >= 120px wide. Ink is 210.5. OK.
     - Clearance Zone, app-chrome exception (approved 2026-07-31): in
       headers/toolbars/nav bars clearance may drop to 16px on all sides.
       Vertical is the binding axis. The artwork's own 45.4/600 units give
       6.06px at this scale, so the header must add >= 9.94px - hence
       `padding-block: 10px` above, for 16.06px of real clearance.

   Header height therefore lands at 10 + 80.1 + 10 = 100.1px against the old
   80px and the live site's 97px. It is 3px over live because our box carries
   that baked padding and live's did not; the ink inside it (210.5 x 67.8) is
   a single-line droplet-plus-wordmark lockup, so it reads at least as large
   as live's 240 x 77 three-line lockup despite the smaller number.

   Below 992px the nav collapses to a toggle and the header gets tight, so
   the logo steps down there - see the media query at the end of this block. */
.ff-logo img {
  display: block;
  width: 227px;
  max-width: 100%;
  height: auto;
}

.ff-nav ul {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* 22px between items, up from 14. At 14 the labels crowded each other into
     one grey band; the eye needs a gap wider than the word-space inside the
     labels to read them as separate targets. */
  gap: 8px 22px;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* shared/css/stylesheet.css has `nav ul li { margin: 0 0 0 50px }` and
   `nav ul li:last-child { margin: 0 0 0 10px }`; mediaqueries.css adds
   `@media (max-width:1600px/1450px) nav ul li { margin-left: ... }` and,
   critically, `@media (max-width:1350px) nav ul li { margin: 20px 0 }`.
   None of that is visible in a plain getBoundingClientRect() check on the
   <li> - margin isn't part of that box - but it IS part of a flex item's
   outer size, so it inflates #ff-nav-menu's own cross-axis auto-height:
   at some widths inside 1350px the container measured ~67px against a
   ~41px tallest child, entirely independent of align-items/gap/align-
   content, until this margin reset was added. `display: block` replaces
   the default `list-item` display, which is unrelated to the margin bug
   but reasonable hygiene once `list-style: none` already removes the
   marker - kept for that reason, not because it fixed this. `#ff-nav-menu`
   is this task's own id, so it's specific enough to beat every variant of
   the vendor's `nav ul li[:last-child]` selectors without relying on
   source-order tie-breaks. */
.ff-nav ul li {
  display: block;
}

#ff-nav-menu li {
  margin: 0;
}

/* `line-height` and `padding` are Task 8's additions, and both are the
   standing lesson again - this rule out-ranks `nav ul li a` (0,0,4) but only
   for the properties it names, and it named neither.

     stylesheet.css    nav ul li a { font: 700 14px/14px "Open Sans";
                                     padding: 0 0 40px 0 }
     mediaqueries.css  @media (max-width:1350px) nav ul li a
                                   { font: 700 25px/25px "Open Sans" }

   So the line-height of every header nav link was the vendor's, and it
   JUMPED at 1350px - measured 14px at a 1400px viewport and 25px at 1350,
   which took `#ff-nav-menu` from 30px tall to 41px for no reason a reader
   could see. 1350 is a vendored breakpoint no franchising rule uses, so the
   change landed in the middle of the ordinary desktop band. 1.2 is declared
   here so the value is this file's at every width.

   The padding was worse and width-independent: `0 0 40px` gave each link a
   box 56px tall inside what was then an 80px header, so the anchor's hit
   area (and its focus ring) hung 10px BELOW the header's own painted edge,
   over whatever band came next. (The header is 100px now - see
   `.ff-logo img` - but the reset is what keeps that from mattering.) */
/* Montserrat, not the body face - Jamey's call. The nav is chrome that names
   the brand's own sections, so it belongs to the display voice with the
   headings rather than to running copy. `--ff-display` is Montserrat since
   Geometos was retired; naming the token rather than the family keeps this
   rule correct through the deferred rebrand. */
.ff-nav a {
  font-family: var(--ff-display);
  font-weight: 700;
  /* 14px. This rule went to 12px earlier purely to stop the header wrapping
     once the family became Montserrat, which sets optically larger than
     Carrois at the same size - a squeeze dressed up as a correction, and
     Jamey read it as too small, which it was. 14px matches live's own nav
     size; the room it needs is found by trimming the logo in the narrow band
     below (see the 1080px query), not by shrinking the type. */
  font-size: 14px;
  line-height: 1.2;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--ff-text);
  text-decoration: none;
  white-space: nowrap;
  padding: 0;
}

.ff-nav a:hover,
.ff-nav a:focus-visible {
  color: var(--ff-orange);
}

/* Both header pills follow the nav into Montserrat. They are chrome that
   names the brand's own actions, so they belong to the display voice with
   the nav links beside them - and with the nav on Montserrat and these two
   still on the body face, the row read as two typefaces pretending to be
   one. Naming --ff-display rather than the family keeps this correct through
   the deferred rebrand. */
.ff-header-phone {
  font-family: var(--ff-display);
  font-weight: 700;
  /* Fully rounded, not the 8px `--ff-radius` the rest of the site uses. It is
     the only pill left in the header now that Get Started is a plain link,
     and a lone 8px rectangle beside seven text links read as a box rather
     than a button. A capsule reads as pressable at a glance. */
  border-radius: 999px;
}

.ff-header-phone {
  margin: 0;
  flex-shrink: 0;
}

/* `inline-flex` with centring on both axes, not `inline-block`. As an
   inline-block the CTA's label sat off-centre in its own pill: the line box
   is positioned by baseline and leading, not by the box, so 12px uppercase
   text in a 34px pill rides high. The phone pill next to it was already
   inline-flex and centred, which is why only one of the two looked wrong.
   `min-height` matches the pair, so they read as one control family rather
   than two buttons that happen to be adjacent. */
.ff-header-phone a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 42px;
  border-radius: 999px;
  padding-inline: 22px;
  background: var(--ff-orange);
  color: #ffffff;
  padding: 8px 16px;
  text-decoration: none;
  white-space: nowrap;
}

/* The phone glyph, note 7. Live's pill carries one and ours did not; the
   number reads as a number either way, but the icon is what makes it read as
   a call to action.

   Lucide (`phone`, lucide-static v1.39.0, ISC), inlined in header.njk -
   Jamey's stated in-house preference over Font Awesome, and deliberately NOT
   a CDN <script>. Nothing in shared/css touches `svg`: it is absent from that
   sheet's 84-element reset and its only `svg` mentions are `url(...)`
   backgrounds, so unlike every other element in this header the glyph needs
   no vendored-leak answer. `stroke: currentColor` inherits the pill's white.

   `inline-flex` overrides the `inline-block` above for this anchor only; the
   Get Started pill shares that rule, has no icon, and is left alone. The
   glyph is drawn at 17px with `stroke-width: 2.25` rather than Lucide's
   default 2 - the stroke scales with the icon, so a 24px-viewBox 2px stroke
   would land at 1.42px here and read thin against 700-weight text. 2.25
   gives 1.59px. Checked rendered, not just computed.

   It is decorative beside a visible number, so `aria-hidden="true"` and no
   accessible name - otherwise a screen reader announces "phone" twice. */
.ff-header-phone a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.ff-phone-icon {
  display: block;
  flex-shrink: 0;
}

/* The pill darkens on hover. This rule listed a third selector,
   `.ff-nav a.ff-cta:hover`, until Get Started became a plain nav link - and
   removing it left a DANGLING COMMA, so the list ran on past the comment
   below and swallowed `.ff-nav-toggle { display: none }`. The effect was that
   hovering the phone pill set `display: none` ON THE PILL: it vanished, the
   cursor was then over nothing, it came back, and it flashed. Jamey caught it
   in a second; no gate could have - the CSS is valid, the markup validates,
   every link still resolves, and nothing renders wrong until you move a
   mouse.
   The declaration block was also lost with the comma. Restored. */
.ff-header-phone a:hover,
.ff-header-phone a:focus-visible {
  background: var(--ff-dark-red);
}


/* Mobile nav toggle. Ships `hidden` in the markup; nav.js clears the
   attribute on load. The `:not([hidden])` guard below is what keeps that
   contract intact at narrow widths: as long as the attribute is present
   (no JS), the toggle stays off and #ff-nav-menu falls through to its
   normal, always-visible flex layout above - never the collapsed one. */
.ff-nav-toggle {
  display: none;
  align-items: center;
  gap: 8px;
  border: 0;
  background: none;
  font-family: var(--ff-body);
  font-weight: 700;
  font-size: 14px;
  color: var(--ff-text);
  cursor: pointer;
  padding: 8px;
}

.ff-nav-toggle-bar,
.ff-nav-toggle-bar::before,
.ff-nav-toggle-bar::after {
  display: block;
  width: 22px;
  height: 2px;
  background: var(--ff-text);
}

.ff-nav-toggle-bar {
  position: relative;
}

.ff-nav-toggle-bar::before,
.ff-nav-toggle-bar::after {
  content: "";
  position: absolute;
  left: 0;
}

.ff-nav-toggle-bar::before { top: -6px; }
.ff-nav-toggle-bar::after { top: 6px; }

@media (min-width: 992px) {
  /* The 992-991 pair of breakpoints marks the same seam as the mobile
     collapse below: at and above 992px the full nav is always visible (no
     toggle), so it must never wrap to a second line - a wrapped header has
     no fixed height, and the site briefly shipped with a hardcoded
     body-padding compensating for a height that changed with the
     viewport. Forcing nowrap here, on top of the tightened gap/font-size/
     padding values above, is what keeps the header a single, predictable
     row across the whole desktop band instead of a moving target. Scoped
     to min-width:992px rather than applied unconditionally, because below
     991px a visitor with JavaScript off never gets the toggle button (see
     the block below) and needs the nav to be free to wrap - forcing
     nowrap there would trade a wrapped nav for a horizontally-clipped
     one. */
  .ff-header,
  .ff-nav ul {
    flex-wrap: nowrap;
  }
}

@media (max-width: 991px) {
  /* The logo steps down with the nav. At and below 991px the header is
     logo + toggle + phone pill on one row (or, with JS off, those three free
     to wrap), and a 227px logo is the widest thing in it. 176px keeps the ink
     at 163.3 x 52.6 - still comfortably over the 120px minimum-width rule -
     and takes the header from 100px to 82px, which matters more here than on
     desktop because this is a sticky bar eating a phone viewport.
     `width` only again; `height: auto` is inherited from the base rule. */
  .ff-logo img {
    width: 176px;
  }

  /* The unconditional `.ff-header nav ul, .ff-footer nav ul` reset above
     already neutralizes the vendor's nav-ul background at every width;
     this block only adds what the collapsed/open dropdown itself needs -
     its own positioning and its own white background - on top of that
     neutral baseline. */
  .ff-nav-toggle:not([hidden]) {
    display: inline-flex;
  }

  .ff-nav-toggle:not([hidden]) ~ .ff-nav #ff-nav-menu {
    display: none;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    position: absolute;
    inset-inline: 0;
    top: 100%;
    background: #ffffff;
    padding: 8px max(24px, calc((100% - var(--ff-container)) / 2)) 16px;
    box-shadow: var(--ff-shadow);
  }

  .ff-nav-toggle:not([hidden]) ~ .ff-nav #ff-nav-menu.ff-nav-open {
    display: flex;
  }

  /* Tap targets, Task 8. In the desktop bar these links are a 13px line in a
     row - the whole `<li>` is the target and the row is 40px+ tall. In the
     dropdown each link is its own row, and at `line-height: 1.2` that row was
     15.6px: a 16px target, well under the ~44px a thumb needs. `display:
     block` plus 14px of block padding takes it to 44 (15.6 + 28), and the
     list's own `gap` drops from 16px to 4px so seven rows plus the CTA still
     open inside a phone screen rather than running off the bottom of it.

     The padding is split off from the `display` so it can also reach the Get
     Started pill, which measured 32px in the open panel (8px of block padding
     around the same 15.6px line) - the one link in the menu that matters most
     and the only one that was under the bar. `padding-block` as a longhand,
     not the `padding` shorthand: the pill's own `padding: 8px 16px` supplies
     the 16px of inline padding that makes it pill-shaped, and a shorthand
     here (this selector is (1,2,1) and beats it) would have flattened that to
     zero. `display: block` stays off the pill for the same reason - it is an
     inline-block button, not a row of text. */
  .ff-nav-toggle:not([hidden]) ~ .ff-nav #ff-nav-menu a {
    padding-block: 14px;
  }

  .ff-nav-toggle:not([hidden]) ~ .ff-nav #ff-nav-menu a:not(.ff-cta) {
    display: block;
  }
}

/* Hero, and the footer CTA band that reuses it ------------------------------
   The live site builds these out of the same parts: `#section-3-2` (the home
   hero) and `#section-86-9` (the bottom-of-page CTA, present on every page)
   are the same composition with a different heading level. Measured side by
   side in a 1400px iframe on fuelfoxfranchising.com:

     #section-3-2   x=0 w=1400 h=849  padding 0  color #fff
     #section-86-9  x=0 w=1400 h=837  padding 0  color #fff
     both: background-image = linear-gradient(rgba(109,24,25,.77),
           rgba(109,24,25,.77)), url(...Your-Fuel-Delivered-Truck-8.5x11-01
           .webp); background-size: auto, cover; position 50% 50%
     both: inner wrap max-width 1300, columns 756 / 504 (exactly 60/40),
           each column padded 20px, so copy text is 716 wide and the white
           card is 464
     both cards: background #fff, border-radius 36px 0, padding 32px,
           box-shadow none, border none

   So the two share one rule here rather than being written twice. 60/40 is
   `minmax(0,3fr) / minmax(0,2fr)`; live's 40px inter-column gutter comes
   from its two 20px column paddings, and is a real `gap` here.

   Live composition (fuelfoxfranchising.com's own generated CSS,
   `#section-3-2` on the home page): full-bleed background combining the
   `--ff-hero-wash` overlay with a photo -
   `linear-gradient(rgba(109,24,25,.77),rgba(109,24,25,.77)),
   url(.../Your-Fuel-Delivered-Truck-8.5x11-01.webp)` - confirmed by
   fetching the live site's own oxygen-cache-2.css rather than guessing
   from the filenames already vendored into sites/franchising/img/2023/02/.
   Two columns above 991px (copy ~60%, card ~40%, matching the live
   #div_block-5-2/#div_block-6-2 split), single column below with the
   card stacked beneath the headline.

   `.ff-hero` is `main > section` (the wrapper introduced for this task),
   so it also matches the shared container rule above
   (`main > * { max-width; margin-inline; padding-inline }` plus
   `main > section { padding-block: 56px }`) - it is in fact the one
   deliberate opt-out from that default. A single class always
   outranks a two-type-selector compound regardless of source order, so
   overriding those properties here is enough to beat that rule without
   fighting it - the same full-bleed-bar-with-centered-content padding
   math `.ff-header` already uses for the identical reason (a fixed
   max-width on the section itself would clip the photo instead of
   letting it run edge-to-edge). */

.ff-hero,
.ff-cta-band {
  /* `max-width`, `margin-inline` and `padding-inline` are THE BLEED RULE's,
     near the container above - these two are entries in its selector list. */
  padding-block: 64px;
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  /* `center`, not `start`. The band's height is set by the e-book card in
     column two, so with `start` the copy pinned to the top and left a large
     void beneath it - measured 64px above the headline against 590px below
     on a 942px band. Live centres this copy: its column is a flex column
     with `justify-content: center` (measured on #section-3-2, headline
     332px down an 849px band). Text stays left-aligned; this is vertical
     only.
     Applied on the shared rule so the home hero and the foot-of-page CTA
     band behave identically - they are the same composition and the e-book
     callout appears in both. The card itself takes `align-self: start`
     below, so only the copy centres. */
  align-items: center;
  gap: 40px;
  /* The photo comes from `--ff-ground-img`, the token the `.ff-ground-*`
     bands already use, so one vocabulary names every photo on the site.
     Task 6e needed it because the seven interior heroes each carry a
     different picture.

     A `var()` FALLBACK rather than a `--ff-ground-img:` declaration in this
     rule, and that is the load-bearing part. `.ff-hero` is (0,1,0) and so is
     `.ff-ground-img-truck`; had this rule set the token, which one won would
     have come down to which appeared later in the file - an invisible
     source-order dependency of exactly the kind this file keeps getting
     bitten by. With the default in the fallback slot, `.ff-hero` never
     declares the token at all: any element that sets it wins outright, and
     an element that doesn't (home's hero, every `.ff-cta-band`) gets the
     truck. Nothing between `html` and these sections declares
     `--ff-ground-img`, so there is no inherited value to shadow it either. */
  background-image:
    linear-gradient(var(--ff-hero-wash), var(--ff-hero-wash)),
    var(--ff-ground-img, url('/img/2023/02/Your-Fuel-Delivered-Truck-8.5x11-01.webp'));
  background-size: cover;
  background-position: center;
  color: #ffffff;
}

/* The kicker's site-wide rule (above) sets it orange for use on white
   ground; the live hero renders this same line in white (inherited from
   the section's own `color:#ffffff`, confirmed in the fetched CSS - no
   override targets it specifically), so it's reversed here too, matching
   the headline instead of fighting the dark wash for contrast. */
.ff-hero-copy .ff-kicker,
.ff-hero-copy h1,
.ff-cta-band-copy .ff-kicker,
.ff-cta-band-copy h2 {
  color: #ffffff;
}

/* The band's own copy column. Scoped to `-copy`, never to `.ff-cta-band h2`,
   because the e-book card in the other column carries an `<h2>` too and that
   one has to stay dark on white.

   `margin` is declared because shared/css's bare `h2` rule is
   `font:900 56px/58px "Open Sans"; text-transform:uppercase; color:#231f20;
   margin:0 auto 30px` - this file answers the first three at the heading
   scale near the top, but nothing answered the margin, so the vendor's 30px
   was the computed value. Live's is 10px under the headline and 16px under
   the kicker. `margin-inline: 0` goes with it: `auto` is inert on a
   full-width block but would centre the heading the moment anything caps its
   width, which is not what the left-aligned live block does. */
.ff-cta-band-copy h2 {
  margin: 0 0 10px;
}

/* shared/css `* > :last-child { margin-bottom: 0 }` already zeroes the
   bottom of this paragraph, since it is the column's last child. The top
   margin is the deliberate part - `main p { margin: 0 0 16px }` leaves the
   button crowded against the copy above it. */
.ff-cta-band-action {
  margin-block-start: 24px;
}

/* DIVERGENCE FROM LIVE, kept on purpose. The live block has no button at
   all: its form is the only call to action. This port's band carries a
   `Get Started` link to the 16-field application, which is a different
   conversion path from the e-book form, so it is styled in rather than
   deleted. Jamey may still choose to drop it.
   Its hover needs a band-local override: the site-wide
   `main .ff-cta:hover { background: var(--ff-dark-red) }` is #9b1b1e, and
   the band's ground is rgba(109,24,25,0.77) over a photo - the hover would
   sink the button into its own background. Reversed instead, which keeps a
   button-shaped thing under the cursor. */
.ff-cta-band-copy .ff-cta:hover,
.ff-cta-band-copy .ff-cta:focus-visible {
  background: #ffffff;
  color: var(--ff-dark-red);
}

.ff-cta-band-copy .ff-cta:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
}

/* The card frame only - background, radius, padding. The live card's
   computed style is `border-radius: 36px 0px` (top-left/bottom-right cut,
   top-right/bottom-left square) and `box-shadow: none` - a diagonal corner
   cut, not a rounded box, and no elevation at all. Neither value is in the
   three captured Oxygen stylesheets or their inline style blocks, so this
   is measured straight off the live page rather than sourced from CSS -
   a deliberate brand detail, not a generic card, so it's hand-written
   here rather than pulled from `--ff-radius` (which stays a uniform-corner
   token for the elements that actually want one, e.g. the header's CTA
   pills above). Resetting `color` back to the body's own value here
   (rather than leaving it inherited from `.ff-hero`'s white) is what
   keeps the ebook form's own headings and copy dark on the white card;
   form-control styling itself is Task 5's. */
/* Interior-page heroes -----------------------------------------------------
   Six pages (/about/, /opportunity/, /support/, /faqs/, /get-started/,
   /privacy-policy/) open on live with the same full-bleed
   photo band the home page uses - `--ff-hero-wash` over a per-page
   photograph, white type, content in the 1300px column. Measured at 1400px:

     page              live section     height  photo
     /about/           #section-3-10     830    Truck-2.webp
     /opportunity/     #section-3-12     840    53405414_...n.webp
     /support/         #section-3-14     840    101795750_...n.webp
     /faqs/            #section-3-16     622    hero-banner-fuel-fox.webp
     (/blog/ was #section-3-53, 830, header_safety-scaled.webp - the page
      was removed from the port 2026-09-02; row kept for the record)
     /privacy-policy/  #section-3-3      622    header_benefits-scaled.webp
     /get-started/     #section-5-65     195    hero-banner-fuel-fox.webp

   all `rgba(109,24,25,0.77)` over the photo with `color: rgb(255,255,255)`.
   The port rendered their opening `<h1>`, kicker and (on three pages) a
   loose `<img>` as bare `main` children on white. Task 6e wrapped each in a
   `<section class="ff-hero ff-hero-solo ...">`.

   A MODIFIER on `.ff-hero`, not a sibling class. `.ff-hero` already appears
   in four selector lists - THE BLEED RULE, the composition rule above, the
   `-copy` colour reversal, and the 991px collapse - and a sibling class
   would have had to be added to every one of them, with a silent, different
   breakage for each one somebody forgot (contained rectangle with white
   gutters; no wash; dark text on maroon; two columns on a phone). The
   modifier joins all four for free, which is right, because these bands ARE
   the hero: same wash, same bleed, same reversal, same collapse. The single
   thing that differs is the column count, and that is what the modifier
   says.

   DIVERGENCE FROM LIVE, recorded not hidden. Live's six tall interior heroes
   are not single-column: each carries the e-book/contact card in a second
   464px column, which is the whole reason they measure 830-840px rather than
   ~200. This port consolidated that form into one `.ff-cta-band` per page
   (Task 6c) instead of repeating it in the hero, so there is no second
   column to lay out and the band is content-height. Live's own
   /get-started/ hero is the single-column case and measures 195px tall with
   60px of block padding - close enough to this file's existing 64px that the
   band inherits it unchanged rather than inventing a min-height.

   Second divergence: live centres /get-started/'s hero text and left-aligns
   the other six. Left everywhere here - a centred headline over that page's
   left-aligned 16-field form reads as a mistake, and six-of-seven is the
   live house style. */
.ff-hero-solo {
  grid-template-columns: 1fr;
  /* Live's interior heroes measure 622-840px, but that height is the e-book
     card in their second column, not the band itself - and Task 6c
     consolidated that card into the one CTA band at the foot of every page,
     so ours have only a headline and a kicker to hold. At the shared 64px
     block padding they came out 192-354px, which is a strip rather than the
     "big full-width photo banner" this task was approved to build.
     Live's OWN headline-only band, /get-started/ #section-5-65, is 195px at
     60px padding - so live has no tall card-less precedent to copy, and this
     number is a judgement call rather than a measurement. 320px lands them
     between live's slim single-column case and its tall card-bearing ones:
     unmistakably a banner, without acres of empty photo under the headline.
     Content is centred vertically so the extra height reads as deliberate
     framing instead of bottom padding. Easy to retune - it is one value. */
  min-height: 320px;
  align-content: center;
}

@media (max-width: 767px) {
  .ff-hero-solo { min-height: 220px; }
}

/* Answers the vendored `h1 { margin: 0 auto 30px }` and `p { margin: 0 auto
   30px }` from shared/css/stylesheet.css, which this file's typography rules
   never named - the standing lesson of this plan, hit for the seventh time.
   The heading scale near the top of this file sets `h1`'s family, weight,
   size, colour, line-height and text-transform and stops there, so 30px was
   the computed bottom margin and `auto` the inline one.

   Written as own-margin-zero plus a 10px top margin on every child after the
   first, rather than as `.ff-hero-solo h1 { margin: 0 0 10px }`, because the
   bands hold three different child sets - `<h1>` alone (/privacy-policy/),
   `<h1>` + `.ff-kicker` (/about/, /opportunity/,
   /support/), `<h1>` + a plain `<p>` (/faqs/, /get-started/) - and this
   spaces all three from one pair of declarations while zeroing whatever the
   vendor set on any of them. 10px is live's measured gap under the headline
   (/about/: headline bottom 413, kicker top 423).

   (0,2,1) is deliberate: it has to beat `.ff-kicker`'s own
   `margin-block-end: 8px` (0,1,0) on the last child and shared/css's
   `* > :last-child { margin-bottom: 0 }` (0,1,0), which would otherwise
   split the two-child bands' spacing across two different rules. Home's
   `.ff-hero-copy` is untouched: it has no `.ff-hero-solo` ancestor, so its
   kicker keeps the 16px live measures above the home headline. */
.ff-hero-solo .ff-hero-copy > * {
  margin: 0;
}

.ff-hero-solo .ff-hero-copy > * + * {
  margin-block-start: 10px;
}

.ff-hero-form,
.ff-cta-band-card {
  /* `start` so the card keeps its own top edge while the copy beside it
     centres - see `align-items: center` on the composition rule above. */
  align-self: start;
  background: #ffffff;
  border-radius: 36px 0;
  padding: 32px;
  color: var(--ff-text);
}

@media (max-width: 991px) {
  .ff-hero,
  .ff-cta-band {
    grid-template-columns: 1fr;
  }
}

/* Forms -----------------------------------------------------------------
   `.ff-form` covers both forms: the 8-field e-book form (`.ff-hero-form`'s
   card on the home page, a plain `main > section` on six other pages) and
   the 16-field application form that is the whole of `/get-started/`.
   Neither ships client-side validation yet (that lands with the Lambda in
   a later plan), so this CSS and the browser's own behaviour are the
   entire experience before submission.

   Values below are measured, not invented: fuelfoxfranchising.com's own
   generated CSS styles the equivalent Contact Form 7 markup with
   `.genform`/`.gensubmit` classes -

     input.genform, textarea.genform, select.genform {
       padding: 8px 20px; width: 100%; max-width: 100%;
       border: 2px solid rgba(0,0,0,0.1); border-radius: 8px; }
     select.genform { padding: 14px 20px; color: #404040; }
     input.gensubmit { padding: 16px 24px; border: none; border-radius: 8px;
       background: #e74025; color: #fff; font-weight: 700;
       transition: 0.2s ease-in-out; }
     input.gensubmit:hover { background: #9b1b1e; }
     input[type=checkbox].genaccept { display: inline; margin-right: 5px; }

   The live border colour (rgba(0,0,0,0.1)) and radius (8px) are near-exact
   matches for tokens already on hand - --ff-hairline (rgba(0,0,0,0.11), the
   value Task 4 defined but left unconsumed) and --ff-radius - so both are
   reused here instead of hand-rolled. The live border is 2px; this task's
   brief calls for 1px, honoured as written since that reads as a
   deliberate simplification rather than a measurement to match exactly.

   `label` is one of the 84 elements shared/css's universal reset zeroes
   (background/border/outline/vertical-align/padding/margin) and gives it
   no display of its own. Every field in this markup is a single
   `<label>Text <input></label>` wrapper (no `for`/`id` pairing), so
   without explicit block layout here the whole form renders as one
   run-on inline paragraph.

   VENDORED COLLISION, found by reading the whole rule body rather than
   trusting the "input/select/textarea/button are a clean slate" scouting
   note - that note is true for the four element selectors, but shared/css
   also carries two CLASS selectors this task's own required names walk
   straight into: `.form-required { color: #dd0000 }` and a full
   `.submit-button` component (`font:900 14px/14px "Open Sans"; ...
   text-transform:uppercase; background:#e74124; border-radius:4px;
   padding:25px 50px 24px` plus `.submit-button:hover { background:#231f20 }`).
   Both are (0,1,0) specificity, same as the rules below, and franchising.css
   loads after shared/css/stylesheet.css, so every property THIS file also
   sets (color, background, border, border-radius, padding, font, cursor,
   transition) wins on source order alone - no extra specificity needed.
   `.form-required` needed no fix beyond the color line already below.
   `.submit-button` needed one: this file never declared `text-transform`,
   so the vendor's `uppercase` was the only declaration for that property
   and rendered "REQUEST INFO & DOWNLOAD" in caps until caught by a real
   browser check (the button's other properties all measured correctly -
   this one doesn't show up unless you look at rendered text). Fixed by
   adding `text-transform: none` to `.submit-button` below rather than a
   separate patch rule. */

.ff-form label {
  display: block;
  margin-block-end: 20px;
}

.ff-form input[type="text"]:not([name="company_website"]),
.ff-form input[type="email"],
.ff-form input[type="tel"],
.ff-form select,
.ff-form textarea {
  display: block;
  width: 100%;
  margin-block-start: 6px;
  border: 1px solid var(--ff-field-border);
  border-radius: var(--ff-radius);
  font-family: inherit;
  font-size: 15px;
  color: var(--ff-text);
  background: #ffffff;
}

.ff-form input[type="text"]:not([name="company_website"]),
.ff-form input[type="email"],
.ff-form input[type="tel"],
.ff-form textarea {
  padding: 8px 20px;
}

.ff-form select {
  padding: 14px 20px;
}

.ff-form textarea {
  resize: vertical;
}

/* CONTRAST FIX. `--ff-orange` (#e74025) on the white card is 4.071:1 -
   under AA's 4.5:1 for normal text, and the asterisk is now 11px inside a
   floated label, where it is the smallest text on the page. Pre-existing;
   the float-label task measured it and flagged it rather than fixing it.

   `--ff-dark-red` (#9b1b1e) measures 8.174:1 on white - it clears the bar
   with 1.8x to spare and is still unmistakably a red, which is the whole
   job of a required marker. The design system's `--color-brand-dark`
   (#9c182f) was the other candidate and was verified, not taken on its
   comment's word: 8.123:1, so the comment is right and understates it. It
   is NOT used here, because #9c182f is precisely the rebrand successor to
   this file's #9b1b1e and the colour-token migration is deliberately
   deferred - minting the new value in one declaration would leave the
   asterisk off-palette against every other red on the page until the rest
   catches up. Using the token means the migration carries this with it. */
.form-required {
  color: var(--ff-dark-red);
}

/* `.ff-consent` is the one label wrapping a checkbox instead of a text
   control. `.ff-form label` above still applies to it (it needs its own
   line of vertical spacing like every other field), but its checkbox and
   its own (multi-line) text need to sit side by side rather than the
   checkbox stacking full-width the way a text input would. Qualified as
   `.ff-form .ff-consent` (0,2,0) rather than bare `.ff-consent` (0,1,0) so
   it actually outranks `.ff-form label` (0,1,1) - the same specificity
   lesson the header override comment above already names: a single class
   loses to a class+type reset silently, regardless of source order. */
/* NOT flex, deliberately. This label's content is one sentence with inline
   markup in it - `... See our <a>Privacy Policy</a>. <span>*</span>` - and
   `display: flex` makes EVERY child its own flex item, including the anchor,
   the bare ". " text node after it and the asterisk span. The sentence
   fragmented into four columns: the link and its trailing period rendered
   yards apart from the words they belong to. Flex is for laying out boxes;
   this is prose that happens to contain a control.

   So the label stays a block and the checkbox hangs in a gutter made by
   `padding-inline-start`, which leaves the text in normal inline flow and
   lets the sentence wrap as a sentence. */
.ff-form .ff-consent {
  display: block;
  position: relative;
  padding-inline-start: 26px;
}

.ff-form .ff-consent input[type="checkbox"] {
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 4px;
  margin: 0;
}

.submit-button {
  display: inline-block;
  border: 0;
  border-radius: var(--ff-radius);
  padding: 16px 24px;
  background: var(--ff-orange);
  color: #ffffff;
  font-family: inherit;
  font-size: 15px;
  font-weight: 700;
  text-transform: none;
  cursor: pointer;
  transition: background-color 0.2s ease-in-out;
}

.submit-button:hover {
  background: var(--ff-dark-red);
}

/* Every focusable control in the form loses its outline to shared/css's
   universal `* { outline: none }` reset (see the override-block comment at
   the top of this file - that rule reaches every element on the page,
   form controls included, even though the separate 84-selector reset
   below it does not name input/select/textarea/button). With no
   client-side validation yet, this ring is the only feedback a keyboard
   user gets before submitting - restoring it is required, not decorative.
   The honeypot is excluded on purpose: it must stay exactly as shipped,
   and its `tabindex="-1"` already keeps it out of the tab order regardless
   of this rule. */
.ff-form input:not([name="company_website"]):focus-visible,
.ff-form select:focus-visible,
.ff-form textarea:focus-visible,
.ff-form a:focus-visible,
.submit-button:focus-visible {
  outline: 2px solid var(--ff-orange);
  outline-offset: 2px;
}

/* Floating labels, e-book form only --------------------------------------
   Review note 16. The card form measured 784px tall in a 420px column
   against live's 448px in a 400px one - the single biggest divergence on
   every page that carries the CTA band, and the reason Jamey said the form
   "doesn't feel quite right". Live buys its 448px by deleting the visible
   labels and using `placeholder` as the field name, which is a known
   accessibility failure: the name disappears the moment you type, so nobody
   can check their answers before submitting. Jamey chose the float-label
   pattern instead of matching live exactly.

   SCOPED TO `.ff-form-float`, WHICH ONLY THE E-BOOK FORM CARRIES. The
   16-field application on /get-started/ is also `.ff-form` and is NOT in
   this pass; every selector below names `.ff-form-float` so that form keeps
   the stacked labels it has. When it is converted, it converts by adding one
   class and rewriting its labels - no CSS here changes.

   HOW THE STATE IS READ: `:has(> input:placeholder-shown)`, not a sibling
   combinator, because the <span> is written BEFORE the control (reading
   order, and it is what the old markup did). `placeholder=" "` is a single
   space that is never painted - it exists purely to give
   `:placeholder-shown` something to match, so this form has no visible
   placeholder text and therefore no low-contrast placeholder. The visible
   resting text is the label span itself, at #5b5b5b on the card's #ffffff:
   6.79:1, which clears AA for normal text nearly 1.5x over. It keeps that
   colour when it floats to 11px, where the bar is the same 4.5:1.
   `--ff-orange` was considered for the focused label and rejected on the
   measurement this plan already had on file: #e74025 on white is 4.07:1, so
   an orange 11px label would fail. The `.form-required` asterisk inside the
   label inherited that same 4.07:1 and this note used to flag it as worth
   its own fix later; that fix is done - see `.form-required` above, now
   `--ff-dark-red` at 8.174:1.

   FALLBACK IF `:has()` IS MISSING: the label simply stays in its floated
   position and the field stays empty underneath it - i.e. a conventional
   small-label-above-field form. Nothing overlaps, nothing is hidden. That is
   why the floated state is the DEFAULT here and the resting state is the
   override, rather than the other way round.

   GEOMETRY. 48px control: 1px border + 21px top padding + a 20px line +
   5px bottom padding + 1px border. The floated label sits at 6px in that
   21px of top padding; the resting label at 14px, which centres a 20px line
   in the 48px box. The textarea rests at 22px instead, where its first typed
   line actually lands, because a 68px box with a vertically-centred hint
   reads as a filled field. 48px also happens to be over the ~44px a finger
   wants, so the <= 479px rule that used to bump these controls' padding no
   longer has to reach them - see the `:not(.ff-form-float)` it grew there.

   TWO-UP ROWS are where the rest of the height came from: first/last name
   and email/phone are four short fields that fit side by side even in a
   279px card on a 375px screen. The grid is declared with an explicit list
   of what spans both tracks rather than `.ff-form-float > *`, because a
   universal child selector would also name the honeypot - which must be
   excluded from every selector in this file. It is out of flow
   (`position: absolute`) so it is not a grid item either way; naming nothing
   universal means that is belt and braces rather than the only thing
   stopping it.

   THE BUTTON now stretches the full width of the card at every width
   instead of being a 228px inline-block at the left edge. That is a change
   on desktop, made deliberately: it is the only control in the column that
   did not line up with the others, and the <= 479px block had already
   decided the same thing for phones. */

.ff-form-float {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  align-items: start;
}

.ff-form-float .ff-field,
.ff-form-float .ff-consent,
.ff-form-float .submit-button { grid-column: 1 / -1; }

.ff-form-float .ff-field-half { grid-column: span 1; }

/* `.ff-form label { margin-block-end: 20px }` stacked the old layout; the
   grid's own `gap` owns that spacing now. (0,2,1) beats it. */
.ff-form-float label { margin-block-end: 0; }

.ff-form-float .ff-field {
  position: relative;
  display: block;
}

/* `:not([name="company_website"])` is copied off the base rule, and it is
   load-bearing twice over. It keeps the honeypot out of a selector this
   file writes - the standing requirement - and it is ALSO the only reason
   this rule reaches a text input at all: an attribute selector inside
   `:not()` counts, so the base `.ff-form input[type="text"]:not([name=...])`
   is (0,3,1), not the (0,2,1) its email and tel siblings measure. Without
   the `:not()` here, `padding` and `margin-block-start` lost to it on
   specificity and `type="text"` fields rendered 44px tall while `email` and
   `tel` rendered 48 - measured, four pixels of stagger down one column, in
   the first build of this rule. */
.ff-form-float .ff-field > input:not([name="company_website"]),
.ff-form-float .ff-field > select,
.ff-form-float .ff-field > textarea {
  margin-block-start: 0;
  padding: 21px 12px 5px;
  font-size: 15px;
  line-height: 20px;
}

.ff-form-float .ff-field-label {
  position: absolute;
  inset-inline-start: 13px;
  inset-block-start: 6px;
  max-width: calc(100% - 26px);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 11px;
  line-height: 13px;
  color: #5b5b5b;
  /* The span must not swallow the click that was aimed at the field
     underneath it - without this, clicking the resting label focuses the
     input but drops the caret at position 0 instead of where you clicked. */
  pointer-events: none;
  transition: font-size 0.12s ease-out, line-height 0.12s ease-out,
              inset-block-start 0.12s ease-out;
}

.ff-form-float .ff-field:has(> input:placeholder-shown):not(:focus-within) > .ff-field-label {
  font-size: 15px;
  line-height: 20px;
  inset-block-start: 14px;
}

.ff-form-float .ff-field:has(> textarea:placeholder-shown):not(:focus-within) > .ff-field-label {
  font-size: 15px;
  line-height: 20px;
  inset-block-start: 22px;
}

/* The floated label sits INSIDE the control, and the 21px of top padding above
   is what keeps text clear of it -- but padding does not clip a SCROLLING
   textarea. Once the content passes two rows, it scrolls up through that
   padding and collides with the label, which is what a real submission looked
   like: three lines of answer printed straight over "Tell us about your
   background".
   Two changes, because either alone is incomplete. The box grows with its
   content, so it does not scroll at any plausible answer length -- and the
   label gets an opaque band, so text passes BEHIND it rather than through it
   when someone writes an essay anyway.
   `field-sizing` is not universally supported; the `rows` attribute and this
   min-height are the floor when it is absent. */
.ff-form-float .ff-field > textarea {
  field-sizing: content;
  min-height: 72px;
  max-height: 240px;
}

.ff-form-float .ff-field:has(> textarea) > .ff-field-label {
  inset-inline-start: 1px;
  /* Both insets, so the band STRETCHES. An absolutely positioned span
     shrink-wraps its text -- with only `inset-inline-start` it covered 173px
     of a 421px control, and a long enough answer would scroll into view
     beside it. */
  inset-inline-end: 1px;
  inset-block-start: 1px;
  max-width: none;
  padding: 5px 12px 3px;
  /* Matches the control, not the card: the label has to hide text sliding
     underneath it, and the control is what is behind it. */
  background: #fff;
  border-start-start-radius: 6px;
  border-start-end-radius: 6px;
}

/* Resting state has to move with the padding above, or the placeholder-shown
   label sits too high inside the taller box. */
.ff-form-float .ff-field:has(> textarea:placeholder-shown):not(:focus-within) > .ff-field-label {
  inset-block-start: 16px;
}

/* The <select>. Its name is a 57-character question that wraps to two lines
   at 12px in a phone-width card, so it cannot live inside the box - it would
   paint through the control. It also has no `:placeholder-shown` state and
   always displays something, so there is nothing to rest on top of. Static
   label above the border, allowed to wrap. */
.ff-form-float .ff-field-stacked > .ff-field-label {
  position: static;
  display: block;
  max-width: none;
  overflow: visible;
  white-space: normal;
  font-size: 12px;
  line-height: 16px;
  margin-block-end: 4px;
}

.ff-form-float .ff-field-stacked > select { padding: 13px 12px; }

/* Content text ------------------------------------------------------------
   VENDORED COLLISION, same class as the `.submit-button` / `text-transform`
   leak Task 5 shipped and then had to fix: a bare selector wins every
   property nothing else declares. shared/css/stylesheet.css carries

     p     { font:400 16px/26px "Open Sans", sans-serif; color:#656262;
             margin:0 auto 30px }
     ul    { list-style:none; margin:0 auto 30px }
     ul li { font:400 16px/26px "Open Sans", sans-serif; color:#231f20;
             background:url("../img/bullet.png") top 6px left no-repeat;
             background-size:12px auto; padding:0 0 0 25px;
             margin:0 auto 20px }
     ol    { margin:0 auto 30px 20px }
     ol li { font:400 16px/26px "Open Sans", sans-serif; color:#231f20;
             margin:0 auto 20px }

   plus `@media (max-width:950px) { ul li, ol li, p { font:400 14px/24px
   "Open Sans" } }` in mediaqueries.css. Nothing in this file had ever
   declared anything for bare `p` or `li`, so those rules were uncontested:
   measured on the built /about/ page before this task, every content
   paragraph computed to `16px/26px "Open Sans", sans-serif` in `#656262`
   and every list item to the same in `#231f20`, with `bullet.png` painted
   behind it. Open Sans is no longer requested (Task 1 swapped the Google
   Fonts link for Carrois Gothic / Source Sans Pro / Lexend Deca), so that
   `font` shorthand was resolving to the generic `sans-serif` fallback -
   the body copy of all nine pages was rendering in a typeface the design
   never chose, at the wrong size, in the wrong grey. Acceptance criterion 1
   of this plan ("all nine pages render with Carrois Gothic body text") was
   not being met.

   `main p` / `main ul li` / `main ol li` are (0,0,2)/(0,0,3), so they beat
   the vendor on specificity rather than on source order - and `font:
   inherit` / `color: inherit` are used rather than restating the token
   values so a paragraph inside a reversed block (the hero, a future dark
   band) still takes its colour from its context instead of forcing
   `--ff-text` onto a dark ground.

   Scoped to `main` on purpose: the header's `.ff-header-phone` and the
   footer's `.ff-copyright` are also `<p>`, and Task 3 / Task 7 own those.

   The matching heading leak (shared/css `h1`-`h4` carrying
   `text-transform:uppercase; font:900 ...`) was fixed at the base
   typography rule near the top of this file, where the heading scale
   lives - see the comment there. */

main p {
  font: inherit;
  color: inherit;
  /* `margin-block`, not the `margin` shorthand. This rule is (0,0,2) and the
     container rule `main > *` is (0,0,1), so a shorthand here also sets
     `margin-inline: 0` and beats the container's `margin-inline: auto` - which
     stopped a top-level <p> from centring. On /get-started/, whose copy sits
     directly in <main>, that produced three different left edges on one page:
     the hero's text at 43, the loose paragraphs at 24, the form at 43.
     Only the block margins are this rule's business; it exists to answer the
     vendored `p { margin: 0 auto 30px }`, whose inline `auto` is exactly what
     a contained top-level paragraph wants. */
  margin-block: 0 16px;
}

/* Content lists -----------------------------------------------------------
   DECISION (the brief's "use the bullet images or set the markers
   explicitly, say which"): explicit native markers, not the bullet PNGs.

   `sites/franchising/img/` does contain bullet.png / bullet-orange.png /
   bulletblack.png / bulletlogo.png, but those are fuelfox.net's design
   system, carried across with the rest of the image tree - they are not
   franchising art. The live franchising site's real lists (the `<ol>` and
   `<ul>` in the WordPress-authored body of /privacy-policy/) render with
   plain UA markers: measured `list-style-type: disc` / `decimal`,
   `padding-inline-start: 40px`, `margin-left: 0`. Its designed "bullet"
   rows elsewhere (About, Support) are not lists at all - they are Oxygen
   flex rows with a 32px orange icon box, which no `::marker` can
   reproduce. So the choice is between franchising's own real markers and
   another site's bullet art; the markers win. `::marker { color }` picks
   up the orange those icon rows carry, which is as close as a marker gets.

   A PNG marker also can't scale: bullet.png is pinned at 12px with a fixed
   25px indent and a `top 6px` offset tuned to a 26px line-height that this
   site no longer uses.

   INDENT (the brief's step 2b - "currently an accident, make it a
   decision"): 40px, the value measured on the live site's own lists. Top-
   level `<ul>`/`<ol>` need it stated twice because the container rule
   (`main > * { padding-inline: 24px }`) writes the page gutter into the
   very same property - they are added rather than allowed to replace each
   other, so the marker sits in space the list owns and the 24px gutter
   still separates it from the viewport edge. Before this, the vendored
   `ol { margin:0 auto 30px 20px }` had been holding those markers on
   screen; the container's `margin-inline: auto` replaced that 20px and
   nothing took its place. */

main ul,
main ol {
  list-style-position: outside;
  padding-inline-start: 40px;
  margin-block: 0 24px;
}

main ul { list-style-type: disc; }
main ol { list-style-type: decimal; }

main > ul,
main > ol { padding-inline-start: calc(24px + 40px); }

main ul li,
main ol li {
  font: inherit;
  color: inherit;
  background-image: none;
  padding: 0;
  margin-block: 0 12px;
}

/* The live site's bullet rows are separated by their icon boxes' own
   padding, not by a margin - a text-only `<li>` has no such structure, so
   this restores an explicit rhythm rather than inheriting the vendor's
   20px (fuelfox.net's, tuned to its 26px line-height). */

main ul li::marker { color: var(--ff-orange); }

/* Icon list ---------------------------------------------------------------
   /support/'s seven support areas, with a Lucide glyph in an orange disc in
   place of the disc marker. Jamey picked the seven icons (review note 2,
   "ICON SET - DECIDED"); this is live's own house treatment carrying them.

   MEASURED OFF LIVE, not invented: 32px disc, 5px of padding around a 20px
   glyph, `background: #e74025`, white artwork, 15px of clear space to the
   text, rows separated by a 1px rgba(0,0,0,.11) hairline. `--ff-orange` and
   `--ff-hairline` already hold the last two values exactly.

   ABSOLUTE POSITIONING, NOT FLEX, and this is the same trap `.ff-consent`
   documents 200 lines up. Live's rows are one short label each, so live can
   afford `display: flex` on the row. Ours are not: every <li> here is a bold
   label, a colon, and two or three sentences of prose in the SAME text flow.
   `display: flex` makes each of <strong>, the ": Our dedicated team..." text
   node and any inline markup its own flex item, which shreds the sentence
   into columns - exactly what a previous pass shipped on the consent label.
   So the glyph is lifted out of flow into a gutter the <li> reserves with
   `padding-inline-start`, and the prose stays prose.

   VENDORED RULES THIS HAS TO ANSWER, read in full rather than assumed from
   the class prefix - the <li> here is still a plain `ul li` and inherits
   everything aimed at one:
     shared/css   ul li { background: url(../img/bullet.png) ...; padding: 0
                  0 0 25px; margin: 0 auto 20px; font/color }
     mediaqueries ul li { font: 400 14px/24px "Open Sans" } at <= 950px
     this file    main ul { list-style-type: disc; padding-inline-start: 40px }
                  main ul li { background-image: none; padding: 0;
                               margin-block: 0 12px }
                  main ul li::marker { color: var(--ff-orange) }
   `main ul li` is (0,0,3) and already zeroes the vendored bullet PNG and its
   25px indent for every list in `main`, so nothing below has to re-answer
   those. What IS re-answered is this file's own `padding: 0` / `margin-block`
   / `list-style-type`, from (0,1,0) and (0,1,1) selectors that outrank it.
   The `::marker` colour needs no answer: `list-style: none` removes the
   marker box entirely.

   STROKE WIDTH 2.25, the same value the header's phone glyph landed on and
   for the same reason: Lucide draws at 24 units with `stroke-width: 2`, so a
   20px glyph puts down 1.67px of ink. Against a solid orange disc, white ink
   that thin reads as a smudge rather than a symbol - live's equivalents are
   Font Awesome SOLID, filled shapes with no stroke at all, so a like-for-like
   2 is not the neutral choice here, it is the thin one. 2.25 renders 1.875px.
   Checked by looking at the seven glyphs rendered side by side at 2 / 2.25 /
   2.5 / 2.75, not by reading a computed value. 2 reads visibly lighter than
   the 700-weight label it sits against. The ceiling is set by the two glyphs
   with the tightest interior detail - `cpu` (an 8-unit rect concentric inside
   a 16-unit one) and `handshake` (four separate finger strokes): at 2.5 both
   start to crowd, at 2.75 `cpu`'s gap is nearly gone and the fingers merge.
   2.25 is the last value that is unambiguously solid AND still open.

   ALIGNMENT. The disc is 32px and the first line box is 24px (15px/1.6), so
   centring the disc on the first line puts it 4px above the text's own top -
   hence `calc(...) - 4px` rather than a bare 0. Top-aligned, not centred on
   the row: these rows are 3-5 lines tall and a vertically-centred disc would
   float in the middle of a paragraph with nothing to relate to.

   REUSABLE, and meant to be. /opportunity/ (7 rows) and /about/ (3 rows)
   carry the same disc with a repeated check mark; they are NOT in scope here,
   but adopting this is `class="ff-icon-list"` on their <ul> plus
   `{{ glyph("check") }}` in each <li> once `check` is added to lucide.njk.
   Nothing below mentions /support/. */

.ff-icon-list {
  list-style: none;
  padding-inline-start: 0;
}

.ff-icon-list > li {
  position: relative;
  padding: 12px 0 12px 47px;
  margin-block: 0;
  border-block-end: 1px solid var(--ff-hairline);
}

.ff-icon-list > li:first-child { padding-block-start: 0; }

/* The last row's hairline would be a rule under the list with nothing below
   it to separate - live has six separators for seven rows, not seven. */
.ff-icon-list > li:last-child {
  padding-block-end: 0;
  border-block-end: 0;
}

.ff-icon-list > li > .ff-glyph {
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: calc(12px - 4px);
  box-sizing: border-box;
  width: 32px;
  height: 32px;
  padding: 6px;
  border-radius: 50%;
  background: var(--ff-orange);
  color: #ffffff;
}

.ff-icon-list > li:first-child > .ff-glyph { inset-block-start: -4px; }

/* Copy-beside-photo split --------------------------------------------------
   /support/'s training section: the copy in one column, the truck photo
   beside it. The port had rendered that `<img>` as the section's last block
   child, so it landed UNDER the list at its own intrinsic width - a
   1000x1500 photograph sitting below the copy it belongs to.

   Live's arrangement, measured on fuelfoxfranchising.com/support/ at a
   1470px viewport: `#new_columns-16-14` is a flex row 1260px wide holding
   two `.ct-div-block` columns at `width: 50%` (630px each) with `padding:
   20px`, so the two content boxes are 590px with 40px between them. The
   photo's column, `#div_block-17-14`, carries `align-items: flex-start` and
   the image renders 590x885 - top edge level with the kicker, not centred
   against the copy. Reproduced here as an even two-track grid with a 40px
   gap: inside this file's 1252px content column that is 606px a side, the
   same 50/50 split with the same gutter.

   The WHOLE copy stack goes in the left column, kicker and headline
   included - that is live's split, not merely the list beside the photo. It
   is also what keeps the two columns near the same height: measured at a
   1400px viewport the copy column is 880px tall and the photo 909px, so the
   two end within 29px of each other. Starting the column at the `<ul>`
   instead would drop the photo's top edge a third of the way down the
   section and leave the headline stranded across the full 1252px.

   A WRAPPER `<div>`, not a class on the section alone. Grid auto-placement
   can seat one child in a second track, but it can only span that child
   across rows of the EXPLICIT grid, and the copy is five children deep with
   no fixed row count - so an image placed at row 1 / column 2 makes row 1
   909px tall and pushes the headline that far down its own section. Tried
   before it was rejected. `.ff-split-copy` is the same shape as
   `.ff-hero`/`.ff-hero-copy` above and the same shape as live's own
   `#div_block-19-14`. Bare `div` appears in shared/css only inside the
   84-selector reset (plus `div.thumbnail`, class-scoped), so the wrapper
   brings no inherited box of its own - checked, not assumed.

   THE LEFT EDGE IS UNCHANGED, which is the thing this rule could most
   easily have broken. The section keeps the container rule's `max-width` /
   `margin-inline` / `padding-inline` untouched; the wrapper is simply track
   1 and starts on the section's content edge. Verified by measuring every
   `h1`/`h2`/`h3`/`p`/`li` in `main` rather than reasoned about: at a 1400px
   viewport /support/ puts 13 of them on x=74 and the seven `<li>` on x=114
   (74 + the list's own 40px indent, which predates this rule), and /about/,
   /opportunity/ and the home page all share that same x=74. At 375px the
   shared edge is x=24 on all four.

   `align-items: start` for the reason live uses `flex-start`, and because
   the alternative is worse than untidy: a grid item's default
   `align-self: stretch` sets the image's used height to the row's, which on
   an intrinsically 2:3 photo with `height: auto` stretches it.
   `width: 100%` / `height: auto` answer the same question on the other
   axis. `main img` (near the top of this file) sets `width: auto`, under
   which a 1000px image in a 606px track is held there by `max-width: 100%`
   and looks correct by accident - it would stop filling the track the
   moment the track went wider than 1000px. Stating the fill makes it
   deliberate, and it is what live does at 590px.

   Source order decides which side is which, and that is now the class's
   whole API rather than a limitation. The home page has three of these and
   they do not all face the same way:

     section                      live width split   media side   source order
     "Are You Ready"              756 / 504          left         media, copy
     "Meeting market needs"       630 / 630          right        copy, media
     "What It Takes to Thrive"    630 / 630          left         media, copy

   Two of live's three are an even 50/50 and the third is 60/40; this file
   holds all three at 50/50 because the port's copy is longer than live's in
   the one section that differs (ours carries a three-item list live renders
   as icon rows), and a 40% column made it noticeably taller than its own
   photograph. Recorded as a choice, not a measurement.

   /about/ and /opportunity/ still do not use this class - they are not
   banded like this on live and they are not this task.

   COLLAPSE AT 991. That is live's own breakpoint for this exact block
   (`@media (max-width: 991px) { #new_columns-16-14 > .ct-div-block {
   width: 100% !important } }`) and the width at which the rest of this file
   already reflows - the nav, the hero and the CTA band. 767 was the other
   candidate offered and it is too late, measured rather than guessed: hold
   the split to 768 and the tracks are 340px, at which the seven list items
   run 5/5/6/6/7/7/5 lines and the copy column is 1382px against a 510px
   photo - 872px of empty column beside the list. At 991 the last
   two-column width is 452px a side, copy 1082 against a 678px photo.

   ONE DIVERGENCE, recorded rather than hidden. Live pairs that breakpoint
   with `flex-direction: column-reverse`, so on a phone the photo sits ABOVE
   the copy. Not copied: at 375px the image is 490px tall, a full screen of
   decorative photograph before the section's own headline. The stack keeps
   source order - copy, then photo - which is also what this page did before
   this change. It is one declaration to flip if the live order is wanted.

   NOT COPIED, flagged rather than silently added: live's image carries
   `border-radius: 60px 0px 0px` (top-left corner only). That is decoration,
   not proportion, and nothing else in this port rounds a content
   photograph. */

main > section.ff-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 40px;
  align-items: start;
}

main > section.ff-split > img {
  width: 100%;
  height: auto;
}

@media (max-width: 991px) {
  main > section.ff-split {
    grid-template-columns: 1fr;
  }
}

/* Live rounds one corner of each of these photographs, and it is a
   different corner each time - the rounded corner always points at the
   copy, so it reads as the picture leaning into the text rather than as a
   generic rounded card. `image-58-2` (copy on its left) is
   `border-radius: 0 60px 0 0`; `image-63-2` (copy on its right) is
   `60px 0 0 0`. Measured off the live computed style; the earlier review
   note recorded the first of these as a rounded TOP-LEFT corner, which is
   the mirror of what the page actually serves.

   Once the split collapses at 991px the copy is underneath, not beside, so
   the corner no longer points anywhere - both go square. */
main > section.ff-split > .ff-corner-tr { border-radius: 0 60px 0 0; }
main > section.ff-split > .ff-corner-tl { border-radius: 60px 0 0 0; }

@media (max-width: 991px) {
  main > section.ff-split > .ff-corner-tr,
  main > section.ff-split > .ff-corner-tl {
    border-radius: 0;
  }
}

/* A photo panel as a split column ------------------------------------------
   /about/'s "Fuel Delivery for the 21st Century" and /opportunity/'s
   "Trucking Companies... Need FuelFox" each carry one on live, and both
   were missing here - which is most of why our /about/ section measured
   394px against live's 691. With the panel it measures 536 at a 1500px
   viewport; the rest of live's height is its own looser copy setting, not
   anything this rule can supply.

   They are `<div>`s with a background image and no text on live, not
   `<img>`s, so they are the same shape as the home page's staggered pair
   and reuse `.ff-photo` rather than introducing a second mechanism. What
   they do NOT share is the pair's `aspect-ratio`: live sizes both of these
   with `width: 100%; height: 100%` inside a 50% flex column
   (`#div_block-81-10`, `#div_block-22-12`), i.e. the panel is as tall as
   the copy standing beside it. `align-self: stretch` is that, in grid -
   and it has to be declared, because `.ff-split` sets `align-items: start`
   for the sake of its `<img>` children and a background div left at
   `start` collapses to zero height.

   `min-height: 350px` is live's own, off `#div_block-22-12`, and it does
   exactly one job HERE: it floors the panel when the copy beside it is
   short. /opportunity/'s split holds only a kicker, a headline and two
   paragraphs, which is the case live wrote that floor for; /about/ holds
   the whole copy stack and never reaches it (482px at 992, 424 at 1500).

   It does NOT guard the collapsed layout - the media query below zeroes
   it, and the aspect-ratio is what keeps the panel visible there. That
   distinction matters, because collapsed is where the panel would
   otherwise VANISH: `align-self: stretch` in a one-column grid stretches
   to a row whose height is the panel's own content height, which for a
   background-image div with no text is zero. Live has that exact bug on
   /about/ - `height: 100%` of an indefinite parent with no floor under it
   - and its panel disappears on a phone. Not copied: `align-self: start`
   plus a ratio gives the box an intrinsic height of its own instead of
   borrowing one.

   The ratio is the pair's, for the pair's reason - a full-width panel held
   at a portrait crop is most of a phone screen of photograph - and
   `min-height: 0` goes with it so the 350px floor cannot square the crop
   up again at 375px, where 3/2 is 218. Between 768 and 991 that makes the
   panel the largest single element on the page (943x629 at 991) - looked
   at rendered, not just measured, and it holds: the crop is the whole line
   of trucks, and 3/2 is the one landscape ratio this file already uses. */
main > section.ff-split > .ff-photo {
  align-self: stretch;
  min-height: 350px;
}

@media (max-width: 991px) {
  main > section.ff-split > .ff-photo {
    align-self: start;
    min-height: 0;
    aspect-ratio: 3 / 2;
  }
}

/* A row that spans the whole split. Live's /opportunity/ section is a
   two-column row (kicker, headline and the intro paragraph beside the
   photo) followed by a full-width row holding the three "reasons"
   (`#new_columns-16-12` then `#div_block-29-12`, both inside
   `#section-15-12`) - so the three subheads are NOT copy that belongs
   beside the picture, and cramming them into the copy column would leave
   half a metre of empty column next to a 350px photo.

   A wrapper `<div>`, like `.ff-split-copy`, and for the same reason: bare
   `h3`/`p` grid items pick up shared/css's `margin: 0 auto` and centre
   themselves at fit-content instead of filling the track. Inside a block
   wrapper the same auto margins resolve to zero, which is what they
   already do everywhere else on the page. */
main > section.ff-split > .ff-split-full {
  grid-column: 1 / -1;
}

/* A paragraph whose only content is a button. `main p { margin-block: 0
   16px }` leaves it crowded under the copy above, the same complaint
   `.ff-cta-band-action` answers in the hero. Shared name, one value. */
main .ff-split-action {
  margin-block-start: 24px;
}

/* The staggered photo pair -------------------------------------------------
   Live's `#div_block-40-2`: a two-column grid, each cell 338x550 with
   `background-size: cover` and `background-position: 50% 50%`, and the
   SECOND cell 40px higher than the first (y=1720 against y=1760). That
   offset is the whole point - level them and the pair reads as a row of two
   pictures instead of one composition.

   Live gets the stagger by letting the first cell overflow the grid box
   downward, which leaves the section's own bottom padding doing the work of
   holding it in. Here the first cell takes a 40px top margin and the grid
   is allowed to grow to 550 + 40 instead, so the offset costs the layout
   nothing below it and the pair still ends flush with its own box.

   `aspect-ratio` rather than live's flat 550px height: our column is
   whatever half of the content column is at the current viewport, not a
   fixed 716px, so a fixed height would distort the crop as the cells
   narrow. 338/550 is live's own ratio, kept exactly.

   These are `<div>`s with background images and no text, exactly as live
   has them - which is precisely the shape the Task 6d content survey could
   not see, because it mapped our sections onto live's by heading text and
   an element whose only content is a photograph has no heading to match. */
.ff-photo-pair {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 40px;
  align-items: start;
}

/* The paint half of a photo panel, and nothing else. `aspect-ratio` used to
   live here and has moved down onto `.ff-photo-pair > .ff-photo`, where it
   belongs: 338/550 is the PAIR's proportion, and the split-column panels
   added for /about/ and /opportunity/ take their height from the copy
   beside them instead. `background-image` reads `--ff-ground-img`, the
   token every other background on the site already uses. */
.ff-photo {
  background-image: var(--ff-ground-img);
  background-repeat: no-repeat;
  background-position: 50% 50%;
  background-size: cover;
}

.ff-photo-pair > .ff-photo { aspect-ratio: 338 / 550; }

.ff-photo-pair > .ff-photo:first-child { margin-block-start: 40px; }

/* Below the split's own 991px collapse the pair sits above a full-width
   column of copy, where two tall thin panels plus a 40px stagger is most of
   a phone screen of photograph. The ratio goes landscape and the stagger
   goes away with it. */
@media (max-width: 991px) {
  .ff-photo-pair > .ff-photo { aspect-ratio: 3 / 2; }
  .ff-photo-pair { gap: 20px; }
  .ff-photo-pair > .ff-photo:first-child { margin-block-start: 0; }
}

/* Two columns survive to 767 and no further. Measured at 375: the content
   column is 327px, so a 20px gap leaves 144px panels rendering 144x96 -
   a pair of thumbnails, not photographs. One column at the same ratio gives
   327x218 each. The stack costs vertical space on the narrowest screens and
   is worth it; these are content pictures, not texture. */
@media (max-width: 767px) {
  .ff-photo-pair { grid-template-columns: 1fr; }
}

/* Franchise locations row --------------------------------------------------
   Live lays the three markets out as a row of icon boxes, each a 30px
   map-pin glyph beside an 18px/700 heading. The pin is a Linearicons
   `map-marker` pulled out of the page's own SVG sprite; there is no such
   asset in this tree and adding one file per glyph for a 20x20 outline is
   not worth a passthrough entry, so it is inlined as a data URI here.

   `background-image` with an inline start pad, NOT `display: flex` with a
   `::before`. A flex container turns its text into an anonymous flex item,
   and this file has already been bitten once by that on a prose label -
   these headings are short enough that it would probably have been fine,
   which is exactly the reasoning that produced the last one.

   `auto-fit`/`minmax` rather than three fixed columns: the market list is
   authored content that grows every time a franchise opens, and it should
   reflow rather than squeeze. */
/* Eleven markets now, not three. `auto-fit` at a 180px floor gave three
   very wide columns when there were three items; with eleven it wants a
   tighter track so the pins read as a set rather than a sparse row.
   A `<ul>` rather than eleven `<h3>`s - eleven sibling headings under one
   h2 is heading spam, and a list is what this actually is. */
main .ff-locations {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(155px, 1fr));
  gap: 14px 20px;
  padding-inline-start: 0;
  margin-block: 0;
}

/* Flex row with a Lucide pin, replacing a hand-rolled 20x20 background SVG.
   Three things were wrong with the old one and only the first was visible:
   it was a filled pin where every other icon on the site is a Lucide stroke
   glyph, so it read as a different vocabulary; `background-position: top 2px`
   pinned it to the line box rather than to the text, so it sat high and drifted
   as soon as a name wrapped; and being a background it could not inherit
   `currentColor`, so its white was hard-coded and would have to be edited by
   hand if this band ever changed ground.
   `align-items: center` and a gap do the alignment the 38px padding and 2px
   offset were approximating. `flex-shrink: 0` keeps the pin its own size when
   a two-word city squeezes the column. */
main .ff-locations li {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 17px;
  font-weight: 700;
  line-height: 1.35;
  margin: 0;
}

main .ff-loc-pin {
  flex: 0 0 auto;
}

/* Fact list with check badges ---------------------------------------------
   The start-up-cost figures. Live drops the bullet entirely and marks each
   line with a 32px orange disc carrying a white check, then pulls the
   dollar amount itself out in orange 17px/700 - three separate hierarchy
   devices on one two-item list, which is most of what "tells a little more
   story" means in the review note.

   The badge is a `::before`, not a `::marker`: `::marker` accepts almost no
   properties (no background, no size, no padding), so a coloured disc
   cannot be built there at all. `list-style: none` and an explicit indent
   replace the marker box the list would otherwise reserve.

   `margin-inline: 0` is not decoration. shared/css sets
   `ul { margin: 0 auto 18px }` at (0,0,1) and this file's `main ul, main ol`
   (0,0,2) answers only `margin-block` - so a NESTED list (one that is not a
   `main` child, and this one is not) still takes the vendor's
   `margin-inline: auto`. Inert on a full-width block, live the moment the
   list sits in a narrower column that lets it shrink to fit. Eighth sighting
   of the standing lesson in this file. */
main .ff-facts {
  list-style: none;
  padding-inline-start: 0;
  margin-inline: 0;
  margin-block: 0 24px;
}

main .ff-facts > li {
  position: relative;
  padding-inline-start: 47px;
  margin-block: 0 15px;
  font-size: 17px;
  font-weight: 700;
  line-height: 1.6;
}

main .ff-facts > li::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background-color: var(--ff-orange);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 28' fill='%23ffffff'%3E%3Cpath d='M26.109 8.844c0 0.391-0.156 0.781-0.438 1.062l-13.438 13.438c-0.281 0.281-0.672 0.438-1.062 0.438s-0.781-0.156-1.062-0.438l-7.781-7.781c-0.281-0.281-0.438-0.672-0.438-1.062s0.156-0.781 0.438-1.062l2.125-2.125c0.281-0.281 0.672-0.438 1.062-0.438s0.781 0.156 1.062 0.438l4.594 4.609 10.25-10.266c0.281-0.281 0.672-0.438 1.062-0.438s0.781 0.156 1.062 0.438l2.125 2.125c0.281 0.281 0.438 0.672 0.438 1.062z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 14px 14px;
}

main .ff-figure {
  color: var(--ff-orange);
  font-weight: 700;
}

/* The FDD caveat. Live de-emphasises it twice over - back down to body size
   and back to normal weight - so it reads as a footnote to the figure above
   rather than as a third fact. */
main .ff-fineprint {
  font-size: 15px;
  font-weight: 400;
}

/* Content CTA -------------------------------------------------------------
   `.ff-cta` is used in two places with two owners: the header pill
   (``, Task 3, outside `main`) and the in-content button
   here. Values measured off the live site's `.ct-link-button` on
   /thank-you/: background #e74025, colour #fff, padding 10px 16px,
   border-radius 8px, font 700 15px/24px Carrois Gothic, `text-transform:
   none`, no letter-spacing. `text-transform` is declared even though no
   vendor rule sets it for `a` - that is the Task 5 habit, not paranoia
   about a specific rule.

   shared/css has `a { font-weight:500; color:#e74124; text-decoration:none;
   transition:all 0.2s ease }` (0,0,1) and `a:hover { color:#231f20 }`
   (0,0,2); `main .ff-cta` is (0,1,1) and `main .ff-cta:hover` (0,2,1), so
   both are beaten on specificity, not source order. */

main .ff-cta {
  display: inline-block;
  background: var(--ff-orange);
  color: #ffffff;
  padding: 10px 16px;
  border-radius: var(--ff-radius);
  font-family: var(--ff-body);
  /* `font-size` was missing and the omission was invisible until a button
     landed inside `main > .ff-ground-orange`, whose paragraphs are raised to
     24px for contrast (see the note at the foot of this file). A button
     inherits from its paragraph, so the same class rendered 15px on eight
     pages and 24px in one band. Live's `.ct-link-button` is 15px/700
     everywhere; stating it makes that true here too, and it is a no-op
     outside that band. */
  font-size: 15px;
  font-weight: 700;
  text-decoration: none;
  text-transform: none;
  letter-spacing: normal;
  transition: background-color 0.2s ease-in-out;
}

/* Buttons that carry live's right-arrow glyph. The arrow is decorative next
   to a verb phrase that already says where the link goes, so the SVG is
   `aria-hidden` in the markup rather than given a label that makes a screen
   reader say "arrow right" after every call to action.

   `inline-flex` is what puts the glyph on the text's baseline row without a
   `vertical-align` fudge; the include emits no whitespace of its own, and
   whitespace-only runs between flex items are dropped anyway, so the `gap`
   is the only space between them. `currentColor` means the reversed pill
   below re-colours the arrow with the text and nothing has to name it
   twice. */
main .ff-cta-arrow {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 16px 32px;
}

main .ff-cta-arrow > .ff-arrow {
  flex: 0 0 auto;
  width: 21px;
  height: 21px;
  fill: currentColor;
}

/* The reversed pill. On the orange band live flips the button rather than
   dropping it to a text link: white ground, orange label. `main .ff-cta` is
   (0,1,1) and this is (0,2,1), so it wins on specificity rather than on
   source order. The hover cannot reuse the site-wide `--ff-dark-red` on
   white/orange terms either - it goes dark-red-on-white's inverse, which is
   the only pair on this band that keeps a button-shaped thing under the
   cursor and stays over 4.5:1.

   `main > .ff-reverse a:not(.ff-cta)` above deliberately excludes buttons,
   so this does not have to fight the band's own link reversal. */
main > .ff-ground-orange .ff-cta {
  background: #ffffff;
  color: var(--ff-orange);
}

main > .ff-ground-orange .ff-cta:hover,
main > .ff-ground-orange .ff-cta:focus-visible {
  background: var(--ff-dark-red);
  color: #ffffff;
}

main > .ff-ground-orange .ff-cta:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
}

main .ff-cta:hover,
main .ff-cta:focus-visible {
  background: var(--ff-dark-red);
  color: #ffffff;
}

/* shared/css's universal `* { outline: none }` reaches this link too. */
main .ff-cta:focus-visible {
  outline: 2px solid var(--ff-text);
  outline-offset: 2px;
}

/* E-book card contents ----------------------------------------------------
   `form-ebook.njk` is never a top-level section any more. Task 6c gave it
   exactly two homes, both white cards on a maroon wash: the home hero's
   `.ff-hero-form` and every page's `.ff-cta-band-card`. The frame (white,
   `border-radius: 36px 0`, 32px padding, no shadow) is on those two, above;
   what is left here is the two things inside the card that are not plain
   body copy.

   Both are measured off the live card at 1400px: the cover image renders
   120px wide and the heading 20px, stacked - the image is not floated
   beside the copy in this context, and the port's own source order
   (h2 / p / img / form) puts it a step lower than live's (img / h2 / p /
   form). That ordering difference predates this task and is left alone;
   moving the image would move it in the hero card too.

   Deleted with this task: `main > .ff-ebook` and its `@media (max-width:
   991px)` companion - a 720px bordered card with a two-column
   image-beside-heading grid, for a top-level placement that no page
   produces now. Nothing in the built HTML matches `main > .ff-ebook`
   (checked in the browser across all nine pages, not just grepped). */

.ff-ebook h2 {
  font-size: 20px;
  line-height: 1.3;
  margin: 0 0 10px;
}

.ff-ebook img {
  width: 120px;
  height: auto;
}

.ff-ebook form {
  margin-block-start: 20px;
}

/* FAQ accordion -----------------------------------------------------------
   `<details>`/`<summary>`, no script - the whole point of the port's FAQ
   markup. Measured off the live Oxygen accordion on /faqs/:

     row      background #f9f9fa, border-bottom 1px #eceef0, padding 8px,
              flex, space-between, cursor pointer
     label    600 18px/28.8px Lexend Deca, colour #e74025
     panel    padding 30px, body colour #404040 at 15px/24px
     column   760px wide, not the full 1300px container

   #f9f9fa and #eceef0 are within a few units of tokens that already exist
   (`--ff-ground` #f4f4f4, `--ff-hairline` rgba(0,0,0,0.11)); the tokens are
   used rather than two new near-duplicate literals.

   `summary`'s default `display: list-item` paints a disclosure triangle.
   `display: flex` removes it on its own in every current engine, but
   `list-style: none` and the `::-webkit-details-marker` rule are kept as
   belt and braces for older WebKit, which honours neither of the other
   two. The +/- is a `::after` rather than a glyph in the markup because
   this task may not touch the markup, and it is `content`-swapped on
   `[open]` so the affordance says which way the row will move.

   `summary` is focusable and shared/css's universal `* { outline: none }`
   strips its focus ring, so it is restored here - with `outline-offset`
   negative so the ring sits inside the row rather than overlapping the
   divider. */

main > .ff-faq {
  max-width: min(100%, 808px);
}

.ff-faq details {
  border-bottom: 1px solid var(--ff-hairline);
}

.ff-faq summary {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 16px;
  background: var(--ff-ground);
  color: var(--ff-orange);
  font-family: var(--ff-body);
  font-size: 18px;
  font-weight: 600;
  line-height: 1.6;
  text-transform: none;
  cursor: pointer;
  list-style: none;
}

.ff-faq summary::-webkit-details-marker { display: none; }

.ff-faq summary::after {
  content: "+";
  flex: 0 0 auto;
  font-size: 24px;
  line-height: 1.2;
  color: var(--ff-orange);
}

.ff-faq details[open] summary::after { content: "\2212"; }

.ff-faq summary:focus-visible {
  outline: 2px solid var(--ff-orange);
  outline-offset: -2px;
}

.ff-faq details > p {
  margin: 0;
  padding: 20px 16px;
  color: var(--ff-text);
}

/* Numbered benefits -------------------------------------------------------
   `<ol class="ff-benefits">` on /opportunity/. Live equivalent is
   `#section-120-12`, which is not a list at all - six Oxygen div cards in a
   `grid-template-columns: 200px x6` with a 20px gap, each card white with
   `padding: 60px 30px 30px`, holding a 60x60 circle (`border-radius: 100%`,
   `border: 5px solid #9b1b1e`, white fill) around a `700 40px/40px` numeral
   in #9b1b1e, above a centred `17px/27.2px` label in #e74025.

   Two things are reproduced with CSS instead of markup: the numeral (a
   `counter()` on `::before`, since the `<li>` marker cannot be a bordered
   circle) and the card.

   Task 6 left these cards on `--ff-ground` over the page's white, because
   the band they belong on was missing: the port's `<h2>` and `<ol>` were
   two sibling top-level children of `<main>` with no element wrapping
   them, and Task 6 could not add one. Task 6d banded them anyway, without
   a wrapper - both siblings carried `.ff-ground-red` and the seam between
   them was collapsed by the adjacency rule up in the grounds block - so
   the cards go back to live's white, which is what they need against dark
   red.

   THE PHOTO IS BACK, and the wrapper is what brought it. Live's
   `#section-120-12` is `background-color: rgb(155,27,30)` under
   `Your-Fuel-Delivered-Truck-8.5x11-01.webp` under a
   `rgba(155,27,30,0.73)` wash of that same colour, so the truck reads
   through at about 27%. That could not be done across two siblings: a
   `background-size: cover` photo sizes and centres itself against each
   box separately, and the 204px `<h2>` and the 338px `<ol>` would have
   shown crops a couple of hundred image-pixels apart - a hard seam
   straight across the band. The two are now inside one `<section>`, which
   carries `.ff-ground-red` AND `.ff-ground-wash` together: the first
   paints live's `background-color` and the second the wash-over-photo
   `background-image` on top of it, which is exactly the pair of
   declarations live's own rule holds. The `.73`/`.77` question is settled
   in the token block at the top of this file.

   `padding` and `margin` below are now load-bearing where they used to be
   dead. As a `main` child this `<ol>` was reached by THE BLEED RULE
   (0,1,1) and by `main > .ff-ground-red { margin-block: 0 }`, both of
   which out-rank this class, so neither declaration here ever applied.
   Nested, they are the ones that win - and `padding-inline: 0` has to be
   STATED rather than deleted, because dropping it hands the grid back to
   `main ol { padding-inline-start: 40px }` and puts the row of cards 40px
   right of the heading above it.

   TENTH SIGHTING of the standing lesson, and it was measured, not
   predicted: `margin-block: 0` was not enough. shared/css carries
   `ol { margin: 0 auto 30px 20px }`, and THE BLEED RULE's
   `margin-inline: 0` had been the only thing answering that 20px left
   margin. Nested, nothing did - the grid of cards landed at x=144 with the
   `<h2>` above it at x=124, a 20px stagger inside one band. `margin: 0`
   answers all four sides at once, which is the shape this leak keeps
   taking: a vendored shorthand is only ever beaten property by property.

   `auto-fit`/`minmax` rather than six fixed 200px columns because the list
   is authored content: six items today, and a seventh should reflow rather
   than overflow. At the 1300px container that produces six ~193px columns,
   within 4% of the live 200px. */

/* Faithful to live and, in Jamey's words, "absolutely hideous" - a row of
   narrow numbered columns squeezing six one-line benefits into 160px tracks.
   Replaced with the treatment he pointed at on live's own "Need More Reasons
   to Join FuelFox?" block: one benefit per row, a round check badge in the
   gutter, a hairline between rows, and room to breathe. Same `::before`
   badge mechanism as `.ff-facts` rather than a second one.

   The badge REVERSES here. `.ff-facts` puts a white check on an orange disc
   for white grounds; this list sits on the dark-red photo wash, where orange
   on #9b1b1e is far too close in value to read. So the disc goes white and
   the check goes dark red - the same shape, inverted, which keeps one badge
   idea on the site instead of two.

   Capped at 46em and centred: six full-sentence benefits across a 1252px
   container would run near 150 characters a line, well past comfortable
   reading. */
.ff-benefits {
  list-style: none;
  display: block;
  max-width: 46em;
  margin-inline: auto;
  counter-reset: ff-benefit;
  padding-inline: 0;
  /* `margin-block`, not the `margin` shorthand: the shorthand also resets
     `margin-inline` and silently cancelled the `auto` three lines above,
     leaving a capped list flush left instead of centred. The shorthand is
     here to answer the vendored `ol { margin: 0 auto 30px 20px }`, and only
     its block halves need answering. */
  margin-block: 0;
}

/* The numbered-card treatment that used to live here is GONE, not overridden.
   It described the design Jamey called "absolutely hideous" - white cards,
   orange centred text, a 60px counter disc - and the replacement further down
   this file is `main .ff-benefits > li`, which is more specific and so won
   every property it declares. The properties it did NOT declare kept leaking
   out of this rule: white background, orange text and centre alignment, on a
   dark-red band. Deleting is the fix; adding overrides would have left two
   descriptions of one component and the next person guessing which is live.
   (The long note that lived here about `overflow-wrap: anywhere` vs
   `break-word` went with it - it only mattered for the narrow card columns
   that no longer exist.) */


/* Next-steps grid ----------------------------------------------------------
   THE ARITHMETIC, which the review flagged and was right to: "a 3x2 grid"
   is six cells and the page has seven steps. Measured on live rather than
   guessed. `#div_block-188-2` is a THREE-column grid holding seven cells
   across three rows - the first cell carries `grid-column: span 3` and runs
   the full 1260px, because Step 1 is the only step with body copy and a
   button under its title. Steps 2-7 then fill two rows of three. So live is
   3x3 with the top row merged, and Jamey's "3x2" is an accurate description
   of the six short cards; the seventh step is the wide one at the top, not
   a leftover. Nothing is missing from either list - both have seven.

   A CSS grid, not a `<table>`. Live's markup is nested `<div>`s, the review
   asked for grid explicitly, and a real table would tell a screen reader
   there are rows and columns of related data when there are neither.

   An `<ol>`, though, where live has plain divs: seven numbered steps in a
   fixed order is the textbook case for an ordered list, and the port had
   them as seven bare `<h4>` siblings with no grouping at all. `list-style:
   none` costs the list role in Safari+VoiceOver, and the usual patch -
   `role="list"` - is a validation error here (html-validate:recommended
   runs `no-redundant-role`, and `list` is `<ol>`'s implicit role). The
   ordinal is carried in visible text on every card ("Step 1:"), which is
   read either way, so the trade lands on the side of shipping valid HTML.

   Cell padding, ground and gap are live's: 30px, #f4f4f4 (= --ff-ground),
   20px. No border and no radius - checked, live has neither.

   VENDORED SCOUT, by element rather than by prefix:
     ol       { margin: 0 auto 30px 20px }   (0,0,1) - the 20px left indent
                is live on any `<ol>` that is not a `main` child, which this
                one is not. `main ul, main ol` (0,0,2) answers only
                `margin-block`, so the indent survives it. Answered here.
     ol li    { font: 400 16px/26px "Open Sans"; color:#231f20; margin:0
                auto 20px }  - `main ul li, main ol li` (0,0,3) answers
                font, colour and margin-block; `margin-inline: auto` is the
                leak, and it bites the moment a cell is a shrink-to-fit box.
     h4       { font: 900 26px/26px "Open Sans"; text-transform:uppercase;
                color:#231f20; margin:0 auto 15px }  - the base heading rule
                near the top of this file answers everything except the
                MARGIN, so `0 auto 15px` was the computed value on every
                step title. Answered here.
     ul li    (mediaqueries.css, inside a max-width query) `font: 400
                14px/24px "Open Sans"` - beaten by `main ol li` (0,0,3).
     the 84-element reset `{ background:transparent; padding:0; margin:0 }`
                (0,0,1) - beaten by every class rule below.

   Two columns at 991 and one at 767, the file's existing breakpoints. The
   lead cell spans whatever the column count is, so it stays the wide one at
   every width rather than becoming an odd single card. */
main .ff-steps {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 20px;
  padding-inline-start: 0;
  margin-inline: 0;
  margin-block: 0;
}

main .ff-steps > li {
  grid-column: auto;
  background: var(--ff-ground);
  padding: 30px;
  margin: 0;
}

/* Step 1 spans all three columns because it is the only step with body copy
   and a button. Its content is centred to match: left-aligned in a
   full-width cell it stranded most of the row empty, which Jamey called
   goofy and was right about. Live centres every step card; we centre only
   this one, because steps 2-7 sit in narrow cells where centring short
   two-line titles reads worse than a shared left edge. */
main .ff-steps > li:first-child {
  grid-column: 1 / -1;
  text-align: center;
}

main .ff-steps > li:first-child .ff-split-action { display: flex; justify-content: center; }

/* Sentence case, regular weight, and only the nouns that carry meaning set
   in bold - Jamey's call, and a deliberate divergence from live, which sets
   every step title bold. Site-wide `h1-h4` are Montserrat uppercase 800, so
   this is an explicit exception: seven all-caps titles in a grid read as
   shouting, and with everything bold there is nothing left to emphasise.
   The `<strong>`s in the markup do the emphasising instead. */
main .ff-steps h4 strong { font-weight: 800; }

/* The "Step N:" label. Live splits it onto its own line above the title, in
   the body face at 17px/600 orange, while the title itself stays in the
   display face - so the two read as label and thing rather than as one long
   heading. Kept INSIDE the `<h4>` rather than lifted out to a sibling
   `<p>`, because lifting it would change the heading's text; as a block
   `<span>` the rendered string is byte-identical and only the line breaks
   move. The colon comes with it, which live does not have - it is the
   port's own copy and copy is not this task's to edit. */
/* Scoped to BOTH grids. The reasons cards reuse `.ff-step-n` for the same
   label-then-thing job, but this rule was written when only the steps grid
   existed - so inside a reasons card the label kept `display: inline` and ran
   on into the title as "Reason 1 Exceptional systems & technology". Reusing a
   class means reusing the rule that makes it work, and a class scoped to one
   ancestor is not actually reusable. */
main .ff-steps .ff-step-n,
main .ff-reasons .ff-step-n {
  display: block;
  font-family: var(--ff-body);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--ff-orange);
  margin-block-end: 8px;
}

/* Sentence case at a regular weight, with only the load-bearing nouns bold -
   Jamey's call, and a deliberate divergence from live, which sets every step
   title bold. Site-wide `h1-h4` are Montserrat uppercase 800, so this is an
   explicit exception: seven all-caps titles in a grid read as shouting, and
   when everything is bold nothing is emphasised. The `<strong>`s in the
   markup carry the emphasis instead.
   Sizes stay as the steps grid set them - 24px orange on the lead card,
   20px on the six that follow - rather than being restated here. */
main .ff-steps h4 {
  font-size: 20px;
  line-height: 1.35;
  margin: 0;
  font-weight: 500;
  text-transform: none;
}

/* The lead cell is the one step with somewhere to go, so live gives its
   title the section-heading treatment - 24px and orange against the other
   six at 20px grey. It is the only orange `h3`/`h4` on the live site, which
   is why the site-wide heading-colour rule near the top of this file stops
   at `h2` and this one is coloured by hand. */
main .ff-steps > li:first-child h4 {
  font-size: 24px;
  color: var(--ff-orange);
}

@media (max-width: 991px) {
  main .ff-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 767px) {
  main .ff-steps { grid-template-columns: 1fr; }
}

/* Footer -------------------------------------------------------------------
   MEASURED on fuelfoxfranchising.com (#section-8-9), not inherited: the live
   footer's ground is ORANGE, `rgb(232,65,37)`, with white text, h=300, a
   1300px inner wrap and three columns of 1260 at 340 / 580 / 340.

   This corrects two things that were written down confidently enough to read
   like findings. Task 7's own brief said "dark ground with reversed text",
   and the vendored-override comment at the top of this file guessed that
   shared/css's inherited `footer { background:#1e1a1b }` "happens to land
   close to what the live franchising footer wants". Both were assumptions.
   The live footer is nothing like #1e1a1b.

   `rgb(232,65,37)` is one step per channel off `--ff-orange` (#e74025 =
   rgb(231,64,37)) - about 0.3 dE, imperceptible, and almost certainly Oxygen
   serialising a rounded value. Reusing the token rather than minting a
   near-duplicate; if that one step ever turns out to be deliberate, this
   comment is where to start.

   VENDORED SCOUT. shared/css reaches this subtree through nine rules, and
   the standing lesson of this plan is that a rule you otherwise fully
   override still leaks every property you never declare - so each
   declaration is answered explicitly below rather than assumed dead:

     img            { width:100%; max-width:none; height:auto; display:block }
     footer         { background:#1e1a1b; width:100%; height:auto;
                      padding:30px 5% 20px }
     footer p       { font:400 14px/14px "Open Sans"; color:rgba(255,255,255,.25);
                      display:inline-block; margin:0 15px 0 0 }
     footer p a     { color:rgba(255,255,255,.25) !important }
     footer ul      { float:right; margin:0 }
     footer ul li   { background:none; padding:0; margin:0 auto 0 20px;
                      display:inline-block }
     a              { font-weight:500; text-decoration:none; color:#e74124;
                      transition:all .2s ease }
     ul             { list-style:none; margin:0 auto 30px }
     @media(<=950px) footer ul { float:none }  /  footer ul li { margin:0 10px }

   Three of those were live traps, not theory:

   1. `img { width:100% }` is the ONLY img rule that reaches the footer logo -
      this file's `main img` override is scoped to `main`, and the footer is
      outside it. Left alone, the 180x44 SVG stretches to its whole grid
      column. Answered by `.ff-footer > img` below.
   2. `footer ul { float:right }` survived the existing `.ff-header nav ul,
      .ff-footer nav ul` reset near the top of this file, because that reset
      answers background/position/width/height/top/right/text-align and never
      mentions `float`. A float inside a grid item is inert, but the
      `.ff-footer-social ul` is a flex row and would have been shoved right.
      Answered explicitly.
   3. `footer p a { ... !important }` locks any link inside a footer `<p>` to
      25%-opacity white. `.ff-copyright` carries no link today, so nothing is
      wrong now - but anyone who adds one will need `!important` to beat it,
      or a selector that is not `footer p a`. Flagged rather than pre-fought.

   `<address>` is in shared/css's 84-element reset, which zeroes
   background/border/outline/vertical-align/padding/margin and says nothing
   about `font-style` - so it keeps the UA's italic. The live contact block
   is not italic; `font-style: normal` below is deliberate, not boilerplate.

   CONTRAST: white on this orange is 4.03:1. That clears AA Large (3:1) and
   fails AA for normal text (4.5:1). It is exactly what the live site does,
   so matching it is faithful and the shortfall is inherited, not introduced -
   the same 4.07:1 call that `.ff-ground-orange` raises. Flagged for Jamey as
   one brand decision covering both, not patched here in isolation. */

.ff-footer {
  background: var(--ff-footer-ground);
  color: #ffffff;
  padding-block: 48px 32px;
  display: grid;
  grid-template-columns: 1fr 1.7fr 1fr;
  grid-template-areas:
    "logo nav     contact"
    "copy social  contact";
  column-gap: 40px;
  row-gap: 24px;
  align-items: start;
}

/* `width` + `height: auto`, same shape as `.ff-logo img` and for the same
   two reasons: it answers the vendored `img { width: 100% }` (the ONLY img
   rule that reaches here - this file's `main img` override is scoped to
   `main` and the footer is outside it), and naming one axis makes the
   letterbox bug unrepresentable.

   193px is picked to hold the OLD footer lockup's visible size, so swapping
   the art does not quietly resize the footer: the previous SVG rendered
   180 x 57.8, and horizontal-logo-on-red.svg at 193 wide puts its ink at
   178.9 x 57.6. Box is 193 x 68.1, ink ratio and baked padding as measured
   for the header. Well over the 120px minimum-width rule.

   The `-on-red` variant, not `-on-dark`: BRAND_GUIDELINES.md maps on-red to
   "Fox Red #e43d30, Crimson #9c182f" and confines on-dark to
   `--color-bg-base` #161412 "or darker. Nowhere else." This footer is
   --ff-orange #e74025 - the live site's red, three points off canonical Fox
   Red until the deferred colour migration - so on-red is the documented
   match and on-dark would break the placement rule. Only the wordmark
   differs between the two variants (white here); the droplet stays #231f20
   and clears the ground on its own. */
.ff-footer > img      { grid-area: logo; width: 193px; max-width: 100%; height: auto; }
.ff-footer-nav        { grid-area: nav; }
.ff-footer-social     { grid-area: social; }
.ff-footer-contact    { grid-area: contact; }
.ff-copyright         { grid-area: copy; }

/* Answers `footer ul { float:right }` and `ul { margin:0 auto 30px }` for
   both footer lists at once.

   `padding` is Task 8's, and it is a mediaqueries.css leak of exactly the
   kind the footer scout was told to look for and still missed: the rule is
   `@media (max-width:1350px) nav ul { ... padding: 20px 0 0 ... }`, and the
   footer holds two `<nav>`s. `.ff-header nav ul, .ff-footer nav ul` near the
   top of this file answers that rule's background/position/width/height/
   top/right/text-align and never mentions padding, and `.ff-nav ul` supplies
   `padding: 0` for the HEADER list only - so below 1350px both footer lists
   sat 20px lower than the logo and the address beside them. Measured on
   /about/: `.ff-footer-nav ul` computed `padding: 20px 0px 0px` at 1300 and
   `0px` at 1400. */
.ff-footer ul {
  float: none;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Two columns, not eight stacked rows. The live footer carries no nav at all
   - its middle column holds only the social row - so this list is the port's
   own addition, and single-filing it made the footer 408px against live's
   300 while the outer columns ran 58px and 162px. Two columns of four brings
   the block back in proportion with its neighbours. */
.ff-footer-nav ul {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px 24px;
}

/* Answers `footer ul li { margin:0 auto 0 20px; display:inline-block }` - the
   20px inline-start would indent every nav row and the social row.

   The `:last-child` variant is NOT redundant. shared/css also has
   `nav ul li:last-child { margin:0 0 0 10px }`, which is (0,1,3) - it
   out-ranks a bare `.ff-footer li` (0,1,1) regardless of source order, and
   silently indented the last item of BOTH lists by 10px. Measured, not
   guessed: last li computed `margin: 0px 0px 0px 10px` while the first
   computed `0px`.

   `font`/`color`/`background`/`padding` answer a THIRD rule on the same
   elements - `ul li { font:400 16px/26px "Open Sans"; color:#231f20;
   background:url(bullet.png) top 6px left no-repeat; padding:0 0 0 25px;
   margin:0 auto 20px }`, plus a 14px/24px restatement in mediaqueries.css.
   Task 6 answered that rule for content lists but scoped its fix to `main`,
   and the footer is outside `main`. Without these four the footer links
   render in Open Sans at the wrong size, in #231f20, indented 25px, with
   fuelfox.net's bullet art painted behind each one. This is also why
   `font: inherit` on the anchor below was not enough on its own: it
   inherits from the `li`, and the `li` was the thing that was wrong. */
.ff-footer li,
.ff-footer nav ul li,
.ff-footer nav ul li:last-child {
  margin: 0;
  display: block;
  font: inherit;
  color: inherit;
  background: none;
  padding: 0;
}

.ff-footer-social ul {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
}

.ff-footer-social li { display: inline-block; }

/* Answers the bare `a { color:#e74124 }`, which would otherwise put orange
   links on an orange ground - invisible. */
.ff-footer a {
  color: #ffffff;
  text-decoration: none;
}

/* The footer's two <nav>s also match shared/css's HEADER nav chain, which the
   footer scout missed by grepping `footer`-prefixed rules: the leaking rules
   are `nav`-prefixed.

     stylesheet.css   nav ul li a { font:700 14px/14px "Open Sans";
                                    letter-spacing:0.04em; color:#231f20;
                                    text-transform:uppercase;
                                    transition:all .2s ease;
                                    padding:0 0 40px 0 }
     mediaqueries.css @media(<=1350px) nav ul li a { font:700 25px/25px
                                    "Open Sans"; color:#fff }

   `.ff-footer a` above is (0,1,1) and out-ranks `nav ul li a` (0,0,4), so
   colour was already correct - and every property it did NOT name leaked
   anyway. Measured: footer links rendered Open Sans 700 14px uppercase with
   0.56px tracking and 40px of dead bottom padding above 1350px, and jumped
   to 25px below it, which is what made the footer read as a stack of
   headings. The 1350px breakpoint is wider than any this site uses, so a
   normal desktop window sits inside it.

   Fifth instance of this plan's standing failure: a rule you out-rank still
   leaks every property you never declare. Every declaration above is
   answered here, at (0,1,4). */
.ff-footer nav ul li a {
  font: inherit;
  letter-spacing: normal;
  text-transform: none;
  padding: 0;
  transition: none;
}

/* `color` is Task 8's. `.ff-footer a` above is (0,1,1) and sets white, but
   the HOVER state is decided by a different rule: shared/css's
   `nav ul li a:hover { color: #e74124 }` is (0,1,4), which out-ranks it, and
   the footer holds two `<nav>`s. Every nav link in the footer therefore went
   #e74124 on the #e74025 ground under the cursor - 1.0:1, invisible. This
   rule is (0,2,1) (`:hover` counts in the class column), so it wins outright.
   Confirmed by enumerating the matching `:hover` rules in the browser rather
   than by reading the cascade: a:hover(#231f20), nav ul li a:hover(#e74124),
   .ff-footer a:hover(text-decoration only) - nothing was holding the colour. */
.ff-footer a:hover,
.ff-footer a:focus-visible {
  color: #ffffff;
  text-decoration: underline;
}

.ff-footer a:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
}

.ff-footer-contact {
  font-style: normal;
  line-height: 1.8;
}

/* Answers `footer p { font:400 14px/14px "Open Sans"; color:rgba(255,255,255,.25);
   display:inline-block; margin:0 15px 0 0 }` - all four. The 25% white is the
   one that matters: on this orange it computes to roughly 1.3:1. */
.ff-copyright {
  font: inherit;
  font-size: 14px;
  color: #ffffff;
  display: block;
  margin: 0;
}

@media (max-width: 767px) {
  .ff-footer {
    grid-template-columns: 1fr;
    grid-template-areas:
      "logo"
      "nav"
      "contact"
      "social"
      "copy";
    row-gap: 28px;
  }

  .ff-footer-nav ul {
    grid-template-columns: 1fr;
  }
}

/* Readability on the orange ground -----------------------------------------
   White on `--ff-orange` is 4.07:1. WCAG AA wants 4.5:1 for normal text and
   3:1 for large text, where "large" means >=24px at normal weight or
   >=18.66px bold. So this ground clears AA only for large text, and 15px
   body copy on it does not pass.

   Jamey's call: keep the brand orange, raise the type. Darkening the ground
   was the other option and he rejected it - the orange reads as the canonical
   brand colour, which is the point of the band.

   24px/400 is not a round-up for effect; it is the exact threshold at which
   normal-weight text becomes "large" under the spec. The live site sets this
   same copy at 16px/400, so it has the identical shortfall - this is one of
   the few places the port is deliberately better than the original rather
   than faithful to it.

   Only `p` needs it. The headings in these bands are already 40px (h2) and
   26px (h3) from this file's own scale, both comfortably over the line. */
main > .ff-ground-orange p {
  font-size: 24px;
  line-height: 1.5;
}

/* The footer once carried 19px/700 here, purely so utility text could clear
   AA against the orange ground (4.07:1 permits only >=18.66px bold). Jamey
   moved the footer to the design system's dark ground, where white measures
   18.37:1 - so the constraint is gone and the type goes back to a normal
   footer size. Deleting a workaround once its cause is removed, rather than
   leaving heavy type nobody can explain later. */
.ff-footer,
.ff-footer-contact,
.ff-copyright,
.ff-footer nav ul li a {
  font-size: 15px;
  font-weight: 400;
}

/* Links stay a little heavier than the address they sit beside - the only
   weight distinction left now that nothing is bold for contrast reasons. */
.ff-footer nav ul li a {
  font-weight: 600;
}

.ff-footer-contact {
  line-height: 1.6;
}

/* Responsive pass ==========================================================
   Task 8. The live site's own breakpoints are 1300 / 991 / 767 / 479, and
   this file already meets three of them where the component that reflows
   lives: the nav collapses to the toggle at 991 (header block), the hero and
   the CTA band go single-column at 991 (hero block), the footer stacks and
   the interior heroes shorten at 767 (footer and hero blocks). Those stay
   where they are - a rule reads better next to the thing it modifies than in
   a pile at the bottom of the file. What is collected here is the work that
   has no other home: one vendored leak that is not scoped to any component,
   and the 479px block, which is a set of small decisions about phones rather
   than one component's reflow.

   Measured, not reasoned about. Every width below was probed in a real
   iframe sized to it - `resize_window` does not reflow, and a media query in
   Chrome resolves against the viewport INCLUDING the scrollbar, so an iframe
   set to 991px wide is really answering the 1006px question unless the
   scrollbar is taken out of it first. Overflow was checked by comparing the
   maximum right edge of every visible element against
   `documentElement.clientWidth`, because `html { overflow-x: hidden }` in
   shared/css clamps `scrollWidth` and makes it a useless signal; text
   overflow was checked separately, as `scrollWidth > clientWidth` on every
   element, because an unbreakable word paints outside a box whose own rect
   never moves. All ten pages at 1400 / 1300 / 991 / 767 / 479 / 375: clean.

   NOT DONE, deliberately: the heading scale is untouched. The brief's step
   for 479px says to reduce the headings at the low end of their `clamp()`,
   and Jamey has since settled that the scale stays. It also turns out not to
   be needed - at 375px `h1` is already at its 32px floor and `h2` at 26px,
   and the text-overflow sweep found no heading (or any other element)
   painting outside its box on any page. */

/* `@media (max-width:1050px) br { display: none }` in mediaqueries.css.
   fuelfox.net uses it to drop the decorative line breaks its marketing
   headlines carry; franchising's `<br>`s are structural. The footer's
   `<address>` is five of them - company, street, city/state/zip, email, two
   phone numbers - and below 1050px it collapsed into one run-on paragraph
   reading "FuelFox Franchise Co., LLC 2100 Southbridge Parkway Suite 529
   Birmingham, AL 35209 franchise@fuelfox.net 205-807-6787 205-964-9949".
   The same rule ran the home page's "$928 Billion / The Size of the U.S.
   Gasoline and Petroleum Wholesale Industry" together, and each of
   /privacy-policy/'s seven bold section headings into the paragraph under
   it. 60 `<br>`s across the ten pages, every one of them load-bearing.

   Declared bare and unconditionally rather than inside a matching media
   query: both selectors are (0,0,1) and franchising.css is the last
   stylesheet in `base.njk`, so source order settles it at every width, and
   a plain `br { display: inline }` cannot be undone by a vendored breakpoint
   nobody remembers. */
br { display: inline; }

/* `479.98px`, not `479px`, and the fraction is load-bearing. The live site's
   breakpoint is `max-width: 479px`, which is only ever exactly right when the
   viewport is a whole number of CSS pixels - and it often isn't. Any browser
   zoom that is not a multiple of the device pixel ratio produces a fractional
   viewport: measured in this harness at devicePixelRatio 2.2, a viewport
   nominally 479px wide reports `clientWidth` 479 while the media query
   resolves against ~479.09 and does NOT match, so the phone block silently
   skipped the one width the brief names. It fires at 478 and below. Half a
   pixel of slack closes that gap without reaching 480. */
@media (max-width: 479.98px) {
  /* The header at phone width is two rows however it is written - the logo
     is 200px, the toggle 79 and the phone pill 120, which is 399px of
     content plus gaps inside a 327px column at 375. What this block fixes is
     that the second row was ARBITRARY: `justify-content: space-between`
     distributes each flex line separately, and the empty `<nav>` (zero-width
     once the dropdown is absolutely positioned) counts as an item, so which
     line it landed on decided where the phone number went. Measured: pill at
     x=24 at a 479px viewport and x=232 at 375 - hard left on one phone and
     hard right on the next.

     `flex-basis: 100%` gives the pill its own line outright, so it is always
     under the logo; `flex-start` plus an auto inline-start margin on the
     toggle keeps the toggle hard right on row one without depending on what
     else shares the line. Row one is the 48px logo, row two the 40px pill:
     122px of sticky header at 12px padding and a 10px row gap, down from the
     136px this was. */
  .ff-header {
    justify-content: flex-start;
    gap: 10px 16px;
    padding-block: 12px;
  }

  .ff-nav-toggle {
    margin-inline-start: auto;
  }

  .ff-header-phone {
    flex-basis: 100%;
  }

  /* Text inputs measured 36px tall (8px of block padding around a 15px line)
     on both forms at every width - fine under a mouse, under the ~44px a
     finger wants. 12px takes them to 44 exactly. `select` is already 50px
     from its own 14px padding and `textarea` is 90px, so neither is touched.

     The two rules are split the same way the base form rules are split
     (shared appearance in one, per-control padding in the other), so this
     patches the padding rule's own selector list rather than inventing a
     third grouping that would then have to be kept in step with both.

     `:not(.ff-form-float)` added when the e-book form took floating labels:
     its controls are 48px at every width already, and this bump would land
     24px of padding on top of the 21px the floated label needs, breaking the
     geometry rather than helping a thumb. /get-started/'s 36px controls -
     the ones this rule was written for - still get it. */
  .ff-form:not(.ff-form-float) input[type="text"]:not([name="company_website"]),
  .ff-form:not(.ff-form-float) input[type="email"],
  .ff-form:not(.ff-form-float) input[type="tel"],
  .ff-form:not(.ff-form-float) textarea {
    padding-block: 12px;
  }

  /* The brief asks for a full-width submit on a phone. It was
     `inline-block`, 97px wide on /get-started/ ("Submit") and 228px in the
     e-book card, sitting alone at the left edge under a column of full-width
     fields - the one control in the form that did not line up with the rest.
     `display: block` is what lets `width: 100%` mean anything on it. */
  .submit-button {
    display: block;
    width: 100%;
  }

  /* 32px of card padding is a fifth of a 375px screen spent on white space
     twice over. 24px still reads as a card and gives the fields back 16px. */
  .ff-hero-form,
  .ff-cta-band-card {
    padding: 24px;
  }
}

/* Pages that open with a bare headline instead of a band ---------------------
   Eight of the ten pages now lead with a full-bleed band, which is meant to
   sit flush under the sticky header. `/thank-you/` and `/404.html` do not -
   they open with a top-level `<h1>` - so their headline collided with the
   header's lower edge with no breathing room at all.

   Selecting on `main > h1:first-child` rather than naming the two pages means
   any future page that opens with a bare headline gets the same treatment,
   and any page that gains a band stops getting it, automatically. Checked
   against the built output: those two pages are the only `main > h1`
   first-children; every other page's first child is a `section` carrying
   `.ff-hero` or a `.ff-ground-*` class. */
main > h1:first-child {
  margin-block-start: 48px;
}

/* Reasons cards (/opportunity/) ------------------------------------------
   Live sets these three as a 1x3 card row, not a bulleted list, and Jamey
   preferred it: grey card, small orange "Reason N" label, bold title, body
   copy under it, all centred. Same label-then-thing structure as the steps
   grid, so the two share `.ff-step-n` rather than growing a second label
   class for the identical job.
   Centred here where the steps grid is not: these are three equal cards of
   short copy reading as a set, and live centres them; the steps grid holds
   seven cells of uneven length where a shared left edge reads better. */
main .ff-reasons {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 20px;
  padding-inline-start: 0;
  margin-block: 0;
  margin-inline: 0;
}

main .ff-reasons > li {
  background: var(--ff-ground);
  padding: 30px;
  margin: 0;
  text-align: center;
}

main .ff-reasons h3 {
  font-family: var(--ff-display);
  font-size: 19px;
  font-weight: 700;
  line-height: 1.35;
  text-transform: none;
  color: var(--ff-text);
  margin: 0;
}

main .ff-reasons p {
  margin: 12px 0 0;
}

@media (max-width: 991px) {
  main .ff-reasons { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 767px) {
  main .ff-reasons { grid-template-columns: 1fr; }
}

main > .ff-ground-red .ff-benefits > li,
main > .ff-ground-wash .ff-benefits > li,
main .ff-benefits > li {
  position: relative;
  display: block;
  padding: 16px 0 16px 52px;
  margin: 0;
  font-size: 17px;
  line-height: 1.55;
  border-bottom: 1px solid rgba(255, 255, 255, 0.22);
}

main .ff-benefits > li:last-child { border-bottom: 0; }

main .ff-benefits > li::before {
  content: "";
  position: absolute;
  inset-block-start: 18px;
  inset-inline-start: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background-color: #ffffff;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 28' fill='%239b1b1e'%3E%3Cpath d='M26.109 8.844c0 0.391-0.156 0.781-0.438 1.062l-13.438 13.438c-0.281 0.281-0.672 0.438-1.062 0.438s-0.781-0.156-1.062-0.438l-7.781-7.781c-0.281-0.281-0.438-0.672-0.438-1.062s0.156-0.781 0.438-1.062l2.125-2.125c0.281-0.281 0.672-0.438 1.062-0.438s0.781 0.156 1.062 0.438l4.594 4.609 10.25-10.266c0.281-0.281 0.672-0.438 1.062-0.438s0.781 0.156 1.062 0.438l2.125 2.125c0.281 0.281 0.438 0.672 0.438 1.062z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 13px 13px;
}

@media (max-width: 767px) {
  main .ff-benefits > li { padding-left: 44px; font-size: 16px; }
  main .ff-benefits > li::before { width: 26px; height: 26px; inset-block-start: 17px; }
}

/* Phone-width header ------------------------------------------------------
   Below 992px the nav collapses to the toggle, and the phone pill was the
   only thing left competing for the row. It lost: measured at 430/390/375 it
   wrapped to a full-width second row (382px wide at 430) and took the sticky
   header to 136px - 18% of a 760px phone screen, 146px at 320.

   So the digits go and the glyph stays. The whole control is a `tel:` link;
   on a phone the number is decoration and tapping is the point. The link
   keeps its aria-label, so the number is still announced and still
   copyable from the context menu.

   44px is the tap-target floor, not a round number - the glyph itself is
   17px and would otherwise be a 17px target next to a 32px-tall Menu
   button. */
@media (max-width: 991px) {
  .ff-header-phone .ff-phone-number { display: none; }

  .ff-header-phone > a {
    width: 44px;
    height: 44px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  .ff-header-phone .ff-phone-icon { width: 20px; height: 20px; }

  /* Sit the call button next to Menu rather than at the far edge, so the two
     controls read as one group and the thumb finds both in the same place.
     `flex: 0 0 auto` is load-bearing: as a plain flex item the `<p>` took the
     whole remaining row - measured 327px wide at a 375px viewport - and
     pushed the Menu button clean off the screen to x=549. Shrink-to-fit is
     what a 44px icon button actually wants. */
  .ff-header { flex-wrap: nowrap; }
  .ff-nav-toggle { order: 3; flex: 0 0 auto; }
  .ff-header-phone { order: 2; margin-inline-start: auto; flex: 0 0 auto; }
  .ff-logo { flex: 0 1 auto; min-width: 0; }
}

/* The full desktop header - 227px logo + 528px nav + 157px phone pill plus
   two 24px gaps - needs about 1008px before padding. The nav collapses to the
   toggle at 991, which left a narrow band just above it where everything is
   still shown and no longer fits: measured at 992 the pill wrapped to a
   second row and the sticky header doubled to 158px. Trimming the gap and the
   logo through that band keeps it on one row rather than moving the collapse
   breakpoint, which would hand the hamburger to a wider set of laptops. */
@media (max-width: 1200px) {
  .ff-header { column-gap: 16px; }
  .ff-logo img { width: 200px; }
}

/* A 14px nav with 22px gaps needs about 1046px before padding, and the nav
   does not collapse to the toggle until 991 - so 992-1046 is a band where
   everything is shown and no longer fits. The room comes out of the logo and
   the item gap rather than the type, because the type size is the thing
   Jamey asked for. Measured: 165 + 527 + 176 + gaps/padding = 948 at 992. */
@media (max-width: 1080px) {
  .ff-logo img { width: 165px; }
  .ff-nav ul { gap: 8px 16px; }
}

/* The logo is 227px wide on desktop; at phone widths that plus a 44px call
   button plus the Menu button overruns a 320px screen. 150px keeps the
   wordmark legible and the row intact down to 320. */
@media (max-width: 479px) {
  .ff-logo img { width: 150px; }
}

/* Franchise application form (/get-started/) ------------------------------
   Sixteen fields in one undifferentiated column read as harvesting. Three
   named, numbered sections read as onboarding - and these genuinely are
   sequential stages of qualification, so the numbering encodes something
   true rather than decorating. A franchise process IS a tabbed document
   packet; the numeral sitting on a gutter rule is that packet.

   `<fieldset>`/`<legend>` rather than divs and headings: it is the element
   for grouping form controls, and a screen reader announces the legend with
   every field inside it, so "State" in section 2 is heard as "Where you are,
   State". shared/css's 84-element reset already zeroes fieldset and legend
   chrome, which for once helps.

   Borders only, no shadows - a document, not a card stack. The one accent is
   the submit button; --ff-dark-red carries the numerals and required marks
   at 8.17:1, which reads as a stamp rather than a warning. Distinct from
   `.ff-form`, which the shorter e-book form still uses. */

.ff-apply {
  max-width: 760px;
  /* 72px at the foot, because the next thing down the page is the footer's
     dark band and the submit row was landing flush against it. */
  margin-block: 32px 72px;
}

.ff-apply-step {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 20px;
  padding: 0 0 40px 56px;
  margin: 0;
  /* No gutter rule. The numeral in the margin already marks the section, and
     a vertical line down the whole form added structure the form did not
     need - Jamey's read, and he is right: it drew a boundary where there was
     no boundary to draw. The 56px indent stays, because that is what puts
     the numeral in the margin rather than in the text column. */
  border: 0;
}

.ff-apply-step:last-of-type { padding-block-end: 8px; }

.ff-apply-step > legend {
  grid-column: 1 / -1;
  float: left;
  width: 100%;
  padding: 0;
  margin: 0 0 4px;
  font-family: var(--ff-display);
  font-size: 20px;
  font-weight: 700;
  color: var(--ff-text);
}

/* The numeral hangs in the margin, which is what makes it read as a tab on a
   packet rather than a bullet beside a heading. `-56px` is the step's own
   padding, so the two stay locked together if either changes. */
.ff-apply-num {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  margin-inline-start: -56px;
  /* 8px, not 22. The numeral and its section name are one label; at 22 they
     read as two separate things that happened to line up. */
  margin-inline-end: 8px;
  border-radius: 50%;
  background: var(--ff-dark-red);
  color: #ffffff;
  font-size: 16px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  vertical-align: middle;
}

.ff-field { grid-column: span 4; display: flex; flex-direction: column; gap: 6px; }
.ff-field-half { grid-column: span 2; }
.ff-field-quarter { grid-column: span 1; }

.ff-apply label,
.ff-apply legend {
  font-size: 14px;
  font-weight: 600;
  line-height: 1.45;
  color: var(--ff-text);
}

.ff-apply input[type="text"]:not([name="company_website"]),
.ff-apply input[type="email"],
.ff-apply input[type="tel"],
.ff-apply select,
.ff-apply textarea {
  width: 100%;
  padding: 11px 14px;
  border: 1px solid var(--ff-field-border);
  border-radius: var(--ff-radius);
  background: #ffffff;
  font-family: inherit;
  font-size: 15px;
  color: var(--ff-text);
}

.ff-apply textarea { resize: vertical; }

/* Custom chevron, because the native one cannot be positioned. Left to the
   platform it sits at a fixed inset from the control's right edge that has
   nothing to do with our padding, so on the narrow State control it crowded
   the label text while on the full-width one it floated far from it - the
   two selects looked like different components. `appearance: none` plus a
   background chevron puts it 14px in, matching the 14px text padding on the
   other side, at every width.
   `padding-inline-end` reserves room so a long option name never runs under
   the chevron. Firefox needs the `text-indent`/`text-overflow` pair to stop
   drawing its own arrow in some versions. */
.ff-apply select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  padding-inline-end: 40px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23404040' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 14px 14px;
  text-indent: 0.01px;
  text-overflow: "";
}

.ff-apply select::-ms-expand { display: none; }

/* The empty option is real placeholder text now. It used to repeat the
   question verbatim, so the open dropdown's first row restated the label
   directly above it - and because it was selectable, "How did you hear about
   the FuelFox franchise program?" was a submittable answer to itself.
   `disabled` closes that too. */
.ff-apply select:invalid,
.ff-apply select option[value=""] { color: var(--ff-placeholder); }

.ff-apply input:focus-visible,
.ff-apply select:focus-visible,
.ff-apply textarea:focus-visible {
  outline: 2px solid var(--ff-orange);
  outline-offset: 2px;
}

/* Segmented group for the one question a rep reads first. Four short options
   is faster to answer as buttons than a dropdown, and unlike a select the
   choices are all visible without a click - which is the point when the
   question is qualifying. Real radios, visually hidden, so keyboard and
   screen-reader behaviour is the platform's and not reimplemented. */
.ff-segmented {
  grid-column: span 4;
  border: 0;
  padding: 0;
  margin: 4px 0 0;
}

.ff-segmented > legend { margin-block-end: 8px; }

.ff-segmented-options { display: flex; flex-wrap: wrap; gap: 8px; }

.ff-segmented input[type="radio"] {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.ff-segmented input[type="radio"] + label {
  padding: 10px 16px;
  border: 1px solid var(--ff-field-border);
  border-radius: var(--ff-radius);
  background: #ffffff;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}

.ff-segmented input[type="radio"]:hover + label { border-color: var(--ff-text); }

.ff-segmented input[type="radio"]:checked + label {
  background: var(--ff-dark-red);
  border-color: var(--ff-dark-red);
  color: #ffffff;
  font-weight: 600;
}

/* The ring goes on the LABEL, because the input it belongs to is clipped to
   1px and a ring there would be invisible. */
.ff-segmented input[type="radio"]:focus-visible + label {
  outline: 2px solid var(--ff-orange);
  outline-offset: 2px;
}

/* The button names the outcome, and what happens next sits beside it -
   someone handing over a home address for a six-figure decision wants to
   know what they just set in motion. */
/* Indented to match the fields. The submit row sits OUTSIDE the fieldsets,
   so it never inherited their 56px gutter and the button hung 56px to the
   left of every input above it - reading as a mistake rather than as an
   outdent. `align-items: start` rather than `center`: the note beside the
   button wraps to two lines, and centring made the button drift down against
   its second line. */
.ff-apply-submit {
  display: flex;
  flex-wrap: wrap;
  align-items: start;
  gap: 16px 24px;
  padding-block-start: 32px;
  padding-inline-start: 56px;
}

.ff-apply-next {
  flex: 1 1 22em;
  /* Optically aligned with the button's label rather than its box top - the
     button has 16px of its own padding, so a shared top edge puts the text
     16px above the words it sits beside. */
  margin: 4px 0 0;
  font-size: 14px;
  line-height: 1.55;
  color: var(--ff-placeholder);
}

@media (max-width: 767px) {
  .ff-apply-step,
  .ff-apply-submit { padding-inline-start: 44px; }
  .ff-apply-num { margin-inline-start: -44px; margin-inline-end: 12px; width: 30px; height: 30px; }
  .ff-field-half,
  .ff-field-quarter { grid-column: span 4; }
  .ff-segmented-options > label { flex: 1 1 100%; text-align: center; }
}

/* Check-badge list on a light ground --------------------------------------
   The same component as `.ff-benefits`, which sits on the dark-red band and
   so runs a white disc with a dark-red check. This is its light-ground twin:
   orange disc, white check. Live uses exactly this on /opportunity/'s "Need
   More Reasons to Join FuelFox?" block, which is the treatment Jamey pointed
   at when asking for the benefits list to be rebuilt - so the two blocks on
   this page now read as one idea instead of one badged list and one bulleted
   list a screen apart.

   Not merged into one rule with `.ff-benefits`: they share a structure but
   differ in disc colour, check colour and divider alpha, which is three of
   the five declarations that matter. A shared rule plus two overrides is
   more indirection than two short rules. */
main .ff-checklist {
  list-style: none;
  padding-inline-start: 0;
  margin-block: 0;
  margin-inline: 0;
  max-width: 46em;
}

main .ff-checklist > li {
  position: relative;
  display: block;
  padding: 15px 0 15px 48px;
  margin: 0;
  font-size: 16px;
  line-height: 1.55;
  background: none;
  border-bottom: 1px solid var(--ff-hairline);
}

main .ff-checklist > li:last-child { border-bottom: 0; }

main .ff-checklist > li::before {
  content: "";
  position: absolute;
  inset-block-start: 17px;
  inset-inline-start: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background-color: var(--ff-orange);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 28' fill='%23ffffff'%3E%3Cpath d='M26.109 8.844c0 0.391-0.156 0.781-0.438 1.062l-13.438 13.438c-0.281 0.281-0.672 0.438-1.062 0.438s-0.781-0.156-1.062-0.438l-7.781-7.781c-0.281-0.281-0.438-0.672-0.438-1.062s0.156-0.781 0.438-1.062l2.125-2.125c0.281-0.281 0.672-0.438 1.062-0.438s0.781 0.156 1.062 0.438l4.594 4.609 10.25-10.266c0.281-0.281 0.672-0.438 1.062-0.438s0.781 0.156 1.062 0.438l2.125 2.125c0.281 0.281 0.438 0.672 0.438 1.062z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 12px 12px;
}

@media (max-width: 767px) {
  main .ff-checklist > li { padding-left: 42px; }
  main .ff-checklist > li::before { width: 25px; height: 25px; background-size: 11px 11px; }
}

/* The form's own intro shares the form's column. `.ff-apply` is capped at
   760px and centred by the container's `margin-inline: auto`, so a
   full-width intro above it left the two with different left edges - which
   reads as a mistake rather than a choice. */
main > .ff-apply-intro {
  max-width: 760px;
  margin-inline: auto;
}

/* 4. Air between the banner and the copy under it. `main p` sets
   `margin-block: 0 16px`, so this paragraph had NO top margin and sat flush
   against the hero's lower edge. Every other page opens with a section that
   brings its own `padding-block`; this one opens with a bare paragraph. */
main > .ff-apply-intro {
  margin-block-start: 48px;
}

/* 1. Live centres /get-started/'s hero text and no other page's. The earlier
   pass left it aligned with the rest of the site deliberately and said so in
   the hero comment; Jamey wants live's treatment here, which also suits a
   page whose whole body is one centred form column. */
.ff-hero-centred .ff-hero-copy {
  text-align: center;
}

.ff-hero-centred .ff-hero-copy > * {
  margin-inline: auto;
}

/* Field hints and invalid state -------------------------------------------
   Native constraint validation only - `pattern`, `inputmode`, `maxlength`.
   Deliberately NOT a JS validator: CLAUDE.md records that the client
   validator and the contact Lambda land together in a later plan so the two
   halves of that hand-synced contract start in sync. That contract is about
   security (length caps, link rejection, CRLF guards, unicode
   normalisation); phone and ZIP SHAPE is not part of it, so constraining
   them here creates no sync burden and no half-built validator.

   `inputmode` is the part a visitor will actually feel: it brings up the
   numeric keypad on a phone instead of the full keyboard. */
/* Error text, shown ONLY when the field is actually wrong.
   An always-visible hint under every field was the first attempt and Jamey
   rejected it: the format belongs IN the box, which the input mask and the
   `(___) ___-____` placeholder now do. This is the other half - feedback when
   the value does not match - and it stays silent until it has something to
   say. CSS-only; the browser owns the state.
   (`.ff-hint` used to live here. Its paragraphs are gone from the markup, so
   the rule went with them rather than sitting orphaned - a dead rule whose
   properties leak into whatever matches it next is exactly how the benefits
   list ended up rendering red-on-white.) */
/* Submission failure, built by form-validate.js and revealed only when the
   request did not complete at all. The Lambda answers 200 even when monday is
   down (it emails the lead instead), so this is a network-level failure and
   the phone number is the whole point of it. */
/* /thank-you/. Inherits the shared 56px block padding from
   `main > section`; the extra below is because the download button used to
   sit 16px above the full-bleed CTA band, which read as the band crowding
   it rather than as the end of a section. */
.ff-thanks { padding-block-end: 88px; }

.ff-thanks-action { margin-block-start: 24px; }

.ff-submit-error {
  grid-column: 1 / -1;
  margin: 12px 0 0;
  padding: 12px 14px;
  border-radius: 6px;
  background: var(--ff-dark-red);
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.45;
}

.ff-submit-error a { color: #fff; text-decoration: underline; }

.ff-error {
  display: none;
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.45;
  color: var(--ff-dark-red);
}

/* input, textarea AND select. `input` alone was enough while only email,
   phone & ZIP carried a message line; form-validate.js can now flag any
   capped or link-checked field, and `capital` is a <textarea> -- so with
   `input` alone its message got a slot and still never displayed. */
.ff-apply :is(input, textarea, select):user-invalid ~ .ff-error { display: block; }

/* The message alone is easy to miss beside a control that still looks
   normal, so mark the control too. 2px, because a 1px colour change at this
   contrast reads as a rendering artefact rather than a state. */
.ff-apply :is(input, textarea, select):user-invalid {
  border-width: 2px;
  border-color: var(--ff-dark-red);
}

/* `:user-invalid`, NOT `:invalid`. A required empty field is invalid from
   first paint, so `:invalid` would paint six red boxes on a form nobody has
   touched yet - hostile, and it trains people to ignore the colour.
   `:user-invalid` waits until the field has actually been interacted with
   and left. Browsers without it fall back to the native tooltip on submit,
   which is a graceful floor rather than a broken state. */
/* The e-book form's floating labels leave no room for a message line, so
   there the border is the whole signal. */
.ff-form input:user-invalid {
  border-color: var(--ff-dark-red);
}

/* Submission is now blocked by the browser: both forms dropped `novalidate`,
   which had been switching native enforcement off entirely - so `required`
   and `pattern` were decorative and an empty form would have posted.

   `novalidate` was there because the plan expected a JS validator to own
   validation. That validator ships with the Lambda in a later plan; until it
   does, leaving enforcement off meant no enforcement at all. The browser's
   own is strictly better than none and costs nothing to hand over later -
   the JS validator will suppress the native bubbles itself when it lands.

   `:invalid` on the SUBMIT is safe in a way it is not on inputs: a form is
   invalid from first paint, but styling the button rather than six fields
   reads as "not ready yet" instead of "you have made six mistakes". */
.ff-apply:invalid .submit-button {
  opacity: 0.55;
  cursor: not-allowed;
}

/* Not `disabled`. A disabled button cannot be focused or clicked, so a
   keyboard user gets no way to ask what is missing and no error at all -
   they just find a dead control. Left clickable, so submitting an incomplete
   form does what it should: the browser moves focus to the first offending
   field and says why. */

/* Body emphasis at 600. This landed at 500 first, following the letter of the
   design system's weight rule ("500 for UI labels, table headers, and
   emphasis. Reserve 600+ and all display/headers for Montserrat") - and Jamey
   read 400-against-500 as too close to register, which it is: 100 units on a
   variable axis is a difference you can measure and barely see.

   700, arrived at by measuring INK rather than width. Width is a poor proxy
   on this axis - 400 to 600 moves a 553px string by 4.9px, under 1%, which
   made 600 look like no change at all. Rendering the same string to a canvas
   and counting dark pixels tells the real story: 500 is +12% ink over 400,
   600 is +21%, 700 is +30%. 700 is the first step that reads as emphasis
   across a paragraph rather than as a slightly darker word.

   Taking 700 as a considered deviation rather than an oversight. That rule's
   concern is DISPLAY usage - it is protecting headings from being set in the
   body face - and inline emphasis inside a paragraph is not a heading. All
   headings on this site remain Montserrat, so nothing the rule guards is
   affected.

   Whatever the number, it is a REAL weight now. The problem it replaced was
   Carrois Gothic shipping one weight, so every `<strong>` asked for 700 and
   got the browser smearing the regular outline. That is what read as fuzzy,
   and no weight value would have fixed it. */
main strong,
main b,
.ff-apply strong {
  font-weight: 700;
}
