/* FBCing — fbcing.com
   Phase 1: tokens and shell. Section 1 custom properties, self-hosted
   IBM Plex Sans, metric-matched fallback. */

/* ---------- FONTS (Section 2) ---------- */

@font-face {
  font-family: "IBM Plex Sans";
  src: url("/fonts/IBMPlexSans-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "IBM Plex Sans";
  src: url("/fonts/IBMPlexSans-SemiBold.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

/* Metric-matched fallback. size-adjust measured against IBM Plex Sans by
   rendered advance width over an English-frequency sample; the three local
   families share Helvetica/Arial advance metrics, so one value covers all. */

@font-face {
  font-family: "IBM Plex Sans Fallback";
  src: local("Arial"), local("Liberation Sans"), local("Helvetica");
  font-weight: 400;
  font-style: normal;
  size-adjust: 100.586%;
}

@font-face {
  font-family: "IBM Plex Sans Fallback";
  src: local("Arial Bold"), local("Liberation Sans Bold"), local("Helvetica Bold");
  font-weight: 600;
  font-style: normal;
  size-adjust: 96.718%;
}

/* ---------- TOKENS (Section 1) ---------- */

:root {
  /* 1.1 Colour */
  --ink:    #18212C;
  --paper:  #F7F5F0;
  --ledger: #497959;
  --rule:   #D8D3C8;
  --slate:  #5A6673;
  --mist:   #B8BFC7;

  /* 7 Button hover fill, ~10% darker than --ledger.
     Fill on Ink measures 2.734:1 in this state, and no 3:1 floor governs
     it. Per W3C Understanding 1.4.11, where a component carries more than
     one visual cue only the cue relied upon must meet 3:1, and this button
     is identified by its label, not by its boundary. No criterion requires
     a hover state to exist, so nothing sets a floor for its indication.
     The binding requirement strengthens on hover: the label rises from
     4.630:1 to 5.452:1. */
  --ledger-hover: #426D50;

  /* 1.2 Spacing scale */
  --s-1:   8px;
  --s-2:  16px;
  --s-3:  24px;
  --s-4:  32px;
  --s-5:  40px;
  --s-6:  56px;
  --s-7:  72px;
  --s-8:  96px;
  --s-9: 128px;

  /* 1.3 Semantic spacing */
  --gap-ink-pad:        var(--s-9);
  --gap-section:        var(--s-8);
  --gap-subhead-button: var(--s-5);
  --gap-rule-content:   var(--s-4);
  --gap-paragraph:      var(--s-3);
  --gap-row-pad:        var(--s-3);
  --gap-heading-rule:   var(--s-2);
  --gap-label-desc:     var(--s-1);

  /* 1.4 Gutter — >= 1024px */
  --gutter: 40px;

  /* 2.3 Optical compensation for the h1 left glyph edge.
     MEASURED FINDING, NOT AN OMISSION. Glyphs were rasterised at 10x and
     the first inked pixel column read. The headline's "T" at 64px weight
     600 inks 1.66px from the spine; the first body paragraph's "F" at
     18px weight 400 inks 1.67px. They align to 0.01px, so the correct
     compensation is zero. "T" has an unusually small left sidebearing,
     0.026 em against 0.093 em for "F".
     Body text has no single ink edge to align to: paragraphs open on
     F G W T O S E A N Y I, inking between 0.23px and 1.67px at 18px, a
     1.44px spread. The headline sits inside that range.
     THIS REOPENS IF THE HEADLINE'S FIRST LETTER CHANGES.
     h2 takes no compensation for the same reason: the six headings open
     on three distinct letters (W x4, H, A) spanning 1.95px at 32px, and
     one value cannot serve them all. */
  --optical-headline: 0px;

  /* 1.5 Measure */
  --measure-body:     min(800px,  100vw - (2 * var(--gutter)));
  --measure-headline: min(1040px, 100vw - (2 * var(--gutter)));
}

/* 1.4 Gutter — 640px to 1023px */
@media (max-width: 1023.98px) {
  :root { --gutter: 32px; }
}

/* 1.4 Gutter — < 640px, and the 1.3 reassignments */
@media (max-width: 639.98px) {
  :root {
    --gutter: 24px;

    --gap-ink-pad:        var(--s-7);
    --gap-section:        var(--s-6);
    --gap-subhead-button: var(--s-4);
    --gap-rule-content:   var(--s-3);
  }
}

/* ---------- SHELL ---------- */

*,
*::before,
*::after {
  box-sizing: border-box;
  border-radius: 0;
}

html {
  background-color: var(--paper);
  /* Insurance against Android font boosting silently deviating from 2.1. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  background-color: var(--paper);
  color: var(--ink);
  font-family: "IBM Plex Sans", "IBM Plex Sans Fallback", sans-serif;
  font-weight: 400;
}

/* 2.4 Selection */
::selection {
  background: var(--rule);
  color: var(--ink);
}

.section--ink ::selection {
  background: var(--slate);
  color: var(--paper);
}

/* ============================================================
   PHASE 3 — LAYOUT AND RHYTHM
   ============================================================ */

/* ---------- SPINE (Section 3) ----------
   No page max-width exists. The shell is viewport-width, inset by
   --gutter. The measures cap the text, not the container. */

.shell {
  /* Centred shell, 1120px = 1040px headline cap + 2 x 40px gutters (Section 1.5).
     The smallest centred width that preserves the full governing headline
     measure. Below 1120px this never binds, so the composition at 1024px and
     under is unchanged. Verified by M14. */
  max-width: 1120px;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* Every text element is capped at the body measure and starts on the
   spine. The headline is the single exception, extending right only. */
.subhead,
h2,
p,
.rows,
.rows li {
  max-width: var(--measure-body);
}

.headline {
  max-width: var(--measure-headline);
}

/* ---------- SECTION RHYTHM (Section 3) ---------- */

.section {
  padding-block: var(--gap-section);
}

.section--ink {
  padding-block: var(--gap-ink-pad);
  background-color: var(--ink);
  color: var(--paper);
}

/* ---------- HERO PHOTOGRAPH (Sections 8.1, 8.2) ----------
   An Ink copy field that resolves into the conference room toward the right.
   The background is full-bleed; the hero's .shell is untouched, so the spine
   cannot move. Two declarations: the first is the universal JPEG fallback for
   engines without image-set(), the second overrides it with AVIF where the
   format is supported. No preload — measured no LCP benefit at 21.8 KB.
   Do not re-encode the AVIF with sips: its output omits the miaf brand and
   fails to decode in Chrome entirely. See Section 8.2. */

#hero {
  background-repeat: no-repeat, no-repeat;
  background-position: 0% 50%, 0% 50%;
  background-size: 100% 100%, cover;
  background-image: linear-gradient(90deg, rgba(24, 33, 44, .97) 0%, rgba(24, 33, 44, .95) 55%, rgba(24, 33, 44, .10) 85%, rgba(24, 33, 44, .04) 100%),
                    url("/hero-fallback.jpg?v=1");
  background-image: linear-gradient(90deg, rgba(24, 33, 44, .97) 0%, rgba(24, 33, 44, .95) 55%, rgba(24, 33, 44, .10) 85%, rgba(24, 33, 44, .04) 100%),
                    image-set(url("/hero.avif?v=1") type("image/avif"),
                              url("/hero-fallback.jpg?v=1") type("image/jpeg"));
}

/* 1024-1279: at cover the windows sit under the headline and contrast fails
   (0.4963 measured at 1024 against a 0.1642 ceiling). A 120% height zoom
   moves them off-frame. Verified 1024, 1100, 1152, 1200. */

@media (min-width: 1024px) and (max-width: 1279.98px) {
  #hero {
    background-size: 100% 100%, auto 120%;
  }
}

/* ---------- HERO MARK ----------
   The ruled wordmark, inlined in index.html as the first child of the hero
   shell. Selected by position so the SVG file passes through byte-identical
   (no class added). Width fixed at 160px, never larger; 136px below 768px.
   It inherits the shell's left padding and adds no offset of its own. */

#hero .shell > svg {
  display: block;
  width: 160px;
  height: auto;
  margin-bottom: 24px;
}

@media (max-width: 767.98px) {
  #hero .shell > svg {
    width: 136px;
  }
}

/* ---------- TYPE (Section 2.1) ---------- */

.headline {
  font-size: 64px;
  font-weight: 600;
  line-height: 1.0;
  letter-spacing: -0.025em;
  margin: 0;
  margin-inline-start: var(--optical-headline);
  text-wrap: balance;
  hyphens: none;
}

.subhead {
  font-size: 24px;
  font-weight: 400;
  line-height: 1.35;
  letter-spacing: -0.01em;
  margin: 0;
  margin-block-start: var(--gap-rule-content);
  color: var(--mist);
  text-wrap: pretty;
}

h2 {
  font-size: 32px;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.015em;
  margin: 0;
  margin-block-end: var(--gap-rule-content);
  color: var(--ink);
  text-wrap: balance;
  hyphens: none;
}

p {
  font-size: 18px;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: 0;
  margin: 0;
  text-wrap: pretty;
}

p + p {
  margin-block-start: var(--gap-paragraph);
}

/* ---------- PORTRAIT (Sections 4 and 4.1) ----------
   Sits between the section rule and the first paragraph. The space above is
   the h2's own margin-block-end (--gap-rule-content); nothing is added for it.
   height:auto with the intrinsic 480x480 attributes retained keeps CLS at 0
   while the CSS width governs the display size. */

.portrait {
  display: block;
  width: 240px;
  height: auto;
  margin-block-end: var(--gap-paragraph);
}

/* ---------- SECTION RULE (Section 6) ----------
   A pseudo-element, so no unspecified element enters the markup. */

h2::after {
  content: "";
  display: block;
  width: 48px;
  height: 3px;
  background-color: var(--ledger);
  margin-block-start: var(--gap-heading-rule);
}

/* ---------- LIST CONSTRUCTION (Section 5) ----------
   Unboxed rows. --gap-row-pad sits above and below each divider; the
   first row has no divider above it and the last none below, so neither
   carries padding on that edge. */

.rows {
  list-style: none;
  margin: 0;
  padding: 0;
}

.rows li {
  padding-block: var(--gap-row-pad);
  border-block-end: 1px solid var(--rule);
}

.rows li:first-child {
  padding-block-start: 0;
}

.rows li:last-child {
  padding-block-end: 0;
  border-block-end: 0;
}

.row__label {
  display: block;
  font-size: 20px;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.005em;
  color: var(--ink);
  text-wrap: balance;
}

.row__desc {
  margin-block-start: var(--gap-label-desc);
}

/* Section 6 renders label and description in Slate. */
.section--slate .row__label,
.section--slate .row__desc {
  color: var(--slate);
}

/* ---------- SECTION 4 INTRO (Section 4) ---------- */

.intro {
  margin-block-end: var(--gap-paragraph);
}

/* ---------- SPECIMEN (Sections 4, 4.2, 4.3) ----------
   The reading of a P&L, not a P&L: line items and the question asked of each.
   No figures anywhere; D21 enforces that. Two columns at and above 640, a
   single stack below it, on the existing breakpoint. Width is derived from
   content so the rules run the length of the reading rather than the measure,
   which would read as a statement with its figures deleted. The measure cap is
   explicit because dl is not in the Section 3 cap selector list — without it
   the specimen renders at 130% of measure and D19 fails. */

.spec {
  display: grid;
  grid-template-columns: max-content 1fr;
  width: max-content;
  max-width: var(--measure-body);
  margin-block: var(--gap-rule-content);
  font-size: 18px;
  line-height: 1.5;
}

.spec dt,
.spec dd {
  margin: 0;
  padding-block: var(--gap-row-pad);
  border-block-end: 1px solid var(--rule);
}

/* The column gap sits inside dt so the row rule stays continuous across both
   columns. A grid column-gap breaks it into two segments and the specimen
   reads as a table that broke. */
.spec dt {
  padding-inline-end: var(--gap-subhead-button);
  font-weight: 600;
  color: var(--ink);
}

.spec dd {
  color: var(--slate);
}

.spec dt:first-of-type,
.spec dd:first-of-type {
  padding-block-start: 0;
}

.spec dt:last-of-type,
.spec dd:last-of-type {
  padding-block-end: 0;
  border-block-end: 0;
}

/* Below 640 the pair stacks and the second alignment edge disappears, so the
   page returns to a single spine. V2's internal-structure allowance applies
   only at and above 640. */

@media (max-width: 639.98px) {
  .spec {
    display: block;
    width: auto;
    font-size: 17px;
  }

  .spec dt,
  .spec dd {
    display: inline;
    padding-block: 0;
    padding-inline-end: 0;
    border-block-end: 0;
  }

  .spec dd::after {
    content: "";
    display: block;
    height: 1px;
    background: var(--rule);
    margin-block: var(--gap-label-desc);
  }

  .spec dd { margin-inline-start: var(--gap-label-desc); }
}

/* ---------- BUTTON PLACEMENT (Section 3) ----------
   Spine placement only. Fill, padding, radius, hover and focus are
   Phase 4. */

.button {
  display: inline-block;
  margin-block-start: var(--gap-subhead-button);
}

/* ---------- FOOTER (Section 4) ---------- */

.footer {
  padding-block: var(--gap-section);
}

.footer p {
  font-size: 15px;
  font-weight: 400;
  line-height: 1.45;
  letter-spacing: 0.01em;
  color: var(--slate);
}

.footer p + p {
  margin-block-start: 0;
}

.footer__mail {
  display: inline-block;
  padding-block: 2px;
  color: inherit;
}

/* ---------- TYPE BELOW 640px (Section 2.1) ---------- */

@media (max-width: 639.98px) {
  .headline    { font-size: 36px; letter-spacing: -0.02em; }
  .subhead     { font-size: 20px; }
  h2           { font-size: 26px; letter-spacing: -0.01em; }
  .row__label  { font-size: 18px; }
  p            { font-size: 17px; }
  .footer p    { font-size: 15px; }
}

/* Portrait below 640px (Section 4). Spacing reduces to buy density; presence
   does not, so this reduces least: -16.7% against -19% to -44% elsewhere. */

@media (max-width: 639.98px) {
  .portrait { width: 200px; }
}

/* ============================================================
   PHASE 4 — BUTTON
   ============================================================ */

/* Section 7. Both buttons sit on Ink and are identical in every respect. */

.button {
  display: inline-block;
  margin-block-start: var(--gap-subhead-button);
  padding: 16px 32px;
  min-height: 48px;
  background-color: var(--ledger);
  color: var(--paper);
  font-size: 18px;
  font-weight: 600;
  line-height: 1.0;
  letter-spacing: 0.005em;
  text-decoration: none;
  border-radius: 4px;
  transition: background-color 120ms ease;
}

.button:hover {
  background-color: var(--ledger-hover);
}

/* ---------- HERO ACTIONS ----------
   Book a call (filled) and the free diagnostic (outlined) in one row, 16px
   apart, stacking at phone width. The outlined button is the same shape,
   size, padding and type as .button, so the two read as one family with
   one primary. Border in Ledger on the Ink hero measures 3.2:1, above the
   3:1 floor for a control boundary; the label is Paper (14.6:1), because
   Ledger text on Ink would be 3.2:1 and fail AA for 18px text. Hover fills
   Ledger with Paper text, the filled button's own resting pairing. */

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--s-2);
  margin-block-start: var(--gap-subhead-button);
}

.hero-actions .button {
  margin-block-start: 0;
}

.button--outline {
  background-color: transparent;
  color: var(--paper);
  box-shadow: inset 0 0 0 2px var(--ledger);
}

.button--outline:hover {
  background-color: var(--ledger);
  color: var(--paper);
}

/* The supporting line belongs to the outlined button: 8px beneath it,
   the site's label-to-description gap, and left-aligned to its edge. */

.hero-alt {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--gap-label-desc);
  max-width: var(--measure-body);
}

.hero-alt__desc {
  margin: 0;
  font-size: 15px;
  line-height: 1.45;
  color: var(--mist);
}

.button:focus-visible {
  outline: 2px solid var(--paper);
  outline-offset: 2px;
}

/* D16. The only transition on the page is the button fill. */
@media (prefers-reduced-motion: reduce) {
  .button {
    transition: none;
  }
}

/* ============================================================
   PHASE 5 — PRINT (Section 13.2)
   ============================================================ */

@media print {
  @page {
    margin: 20mm;
  }

  html,
  body {
    background-color: var(--paper);
    color: var(--ink);
  }

  /* Ink section backgrounds are removed; their text reverts to Ink. */
  .section--ink {
    background-color: transparent;
    color: var(--ink);
  }

  .subhead {
    color: var(--ink);
  }

  /* The button label stays Paper. Forcing it to Ink would put --ink on the
     --ledger fill at 3.220:1, under the 4.5:1 floor for normal text; Paper
     on Ledger holds 4.630:1. */
  .button {
    color: var(--paper);
  }

  /* The booking URL prints as visible text after the label. Taken from the
     href itself, so it cannot drift from the live link. */
  .button::after {
    content: " " attr(href);
  }
}
