/* Hallmark · tokens · Oster Construction
 * Palette is the client's own (business card: construction orange + deep navy).
 * Theme axes — paper band: light · display style: display-condensed · accent hue: warm
 *
 * Accent discipline: orange appears on the primary CTA, the call bar, and the
 * wordmark mark. Nowhere else. Reference sites (Barrett Made, housebrand, Weald)
 * are near-monochrome and let photography carry the colour.
 */

/* ---- Fonts, self-hosted --------------------------------------------
   These were loaded from fonts.googleapis.com until 2026-08-10, when the
   headings turned up THIN AND WRONG in Safari: Anton never arrived, the
   stack fell through to Arial Narrow, and the erode filter then thinned
   that fallback into a hairline. Every content blocker on the market
   blocks Google Fonts, so the site's display voice was one browser
   extension away from disappearing — and it did.

   Self-hosting removes the third-party request entirely. Files are the
   exact woff2 Google serves, latin subset only (this site is English),
   and they come off the same origin as the page, so they cannot be
   blocked without blocking the site. 41 KB for both, and one fewer DNS +
   TLS handshake in front of the largest text on the page.

   To refresh: fetch the css2 url with a browser User-Agent, take the
   `latin` @font-face src from each, and replace the files here. */
@font-face {
  font-family: "Anton";
  font-style: normal;
  font-weight: 400;                     /* Anton ships one weight. That is all there is. */
  font-display: swap;
  src: url("/fonts/anton-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+2000-206F, U+2122, U+2212, U+FEFF, U+FFFD;
}
/* One variable file covers 400–700; Google serves the same url for all four
   weights the site asks for, so there is one request, not four. */
@font-face {
  font-family: "Libre Franklin";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/fonts/libre-franklin-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+2000-206F, U+2122, U+2212, U+FEFF, U+FFFD;
}

:root {
  /* ---- Colour ------------------------------------------------------- */
  --color-navy:        oklch(28.9% 0.062 263.9);  /* #1B2A4A */
  --color-navy-deep:   oklch(24.6% 0.053 261.8);  /* #12203A */
  /* The bright relative of the deep blue. The accent has to survive on BOTH
     grounds and the deep blue only works on one: on paper it is a strong mark,
     but on the navy panels and the footer it is invisible against its own
     background, and as a hover colour on navy body text it is no change at
     all. Every one of those cases points at this instead. */
  --color-blue-lit:    oklch(62% 0.152 253);      /* #2E7BD6-ish */
  --color-paper:       oklch(97.2% 0.005 84.6);   /* warm off-white */
  --color-steel:       oklch(52.5% 0.045 261.4);  /* #5C6B85 */
  --color-mist:        oklch(77.4% 0.039 262.1);  /* #A8B6CF */

  /* Semantic aliases — components reference these, never the raw names. */
  --color-ink:         var(--color-navy);
  --color-ink-2:       var(--color-steel);
  --color-ink-light:   oklch(99.4% 0.003 85);
  --color-ink-light-2: var(--color-mist);
  /* Orange is gone from the site (2026-08-09). The accent is now the deep blue
     off the contact sheet's button. Consequence worth knowing: the accent no
     longer contrasts with the ink, so it can no longer do its old job of
     making small type "pop" on paper — it works as a FILL (buttons, tags, crop
     marks, ticks) and as a lighter tint on dark grounds. Anywhere the old
     orange was carrying legibility rather than emphasis now uses --accent-hot. */
  --color-accent:      var(--color-navy-deep);
  --color-accent-hot:  var(--color-blue-lit);
  --color-accent-ink:  var(--color-ink-light);  /* on accent — 13:1 */
  --color-focus:       var(--color-blue-lit);   /* must read on paper AND navy */

  --color-rule:        oklch(28.9% 0.062 263.9 / 0.16);
  --color-rule-light:  oklch(99% 0 0 / 0.20);
  --color-scrim:       oklch(18% 0.04 262 / 0.52);
  /* inactive type sitting ON photography: mist bottoms out at ~3.03:1 over the
     brightest frames, which is no margin at all. Translucent white composites
     with whatever is behind it and holds ~5:1. */
  /* Inactive trade names, over a photograph. Was 0.72 while the type carried a
     text-shadow; that shadow is gone (it bloomed under the erode filter), and
     at 0.72 the dimmed names measured 3.91:1 over the kitchen and 4.15:1 over
     the flooring shot — under WCAG AA. 0.85 clears AA on all nine backgrounds
     without darkening the scrim, which is what the shadow was protecting. */
  --color-on-photo:    oklch(99% 0 0 / 0.85);

  /* ---- Type --------------------------------------------------------- */
  /* Libre Franklin throughout, set 2026-08-09 — the THIRD pairing this day
     and the first that was chosen against the right question. Two earlier
     sets (Barlow Condensed + Newsreader, then League Spartan + Instrument
     Sans) were both rejected as "trying too hard", and the diagnosis that
     stuck is that the FACE was never the problem: the site set every heading
     uppercase, near-black, tightly tracked and as large as the column would
     allow, which shouts whatever is loaded into it.

     So this change is a treatment change as much as a type change. Headings
     are sentence case at weight 500 with looser tracking and smaller display
     sizes. Small-caps LABELS are untouched — they are a separate device and
     they are what carries the drawing-sheet language.

     Libre Franklin is an American gothic out of civic signage and newspaper
     mastheads: quiet, but not the neutral nothing that Inter would have been.
     One family does display, body and UI.

     (superseded) League Spartan + Instrument Sans, set 2026-08-09. This replaced
     Barlow Condensed + Newsreader and it is not a like-for-like swap:
     - League Spartan is GEOMETRIC AND WIDE where Barlow Condensed was narrow,
       so the same headline takes roughly a third more horizontal room. Display
       sizes below were cut to pay for that, not because the type is smaller.
     - The body face is now a sans, not a serif, so the page has one voice
       instead of two. Newsreader was doing the "letter from the builder" job;
       Instrument Sans does it plainer.
     Fallbacks are chosen for shape, not popularity: Futura/Century Gothic are
     the geometric caps nearest League Spartan, and the system UI stack is the
     nearest neutral grotesque to Instrument Sans. */
  /* The display voice. Anton, because the hero headline artwork Marcus supplied
     is heavy condensed caps and this is the nearest thing that can be set as
     live text — so headings echo the hero instead of ignoring it. One weight
     only (400): thickness is controlled by the erode filters in index.html,
     not by font-weight, which Anton cannot honour. Never use it below ~24px
     and never for body copy — no bold, no italic, nothing to build with.

     Fallback order matters more here than anywhere else on the site. It used
     to read Anton → Arial Narrow → Impact, and Arial Narrow is installed on
     every Mac — so the one time Anton failed to arrive, the headline was set
     in a REGULAR-weight condensed face and then eroded on top of that. It came
     out as a hairline next to the hero artwork, which is the exact opposite of
     the job. The fallbacks are now heavy first: Haettenschweiler (Windows) and
     Impact (everywhere) are the two faces nearest Anton's weight, and Arial
     Narrow is gone. If this list is ever reordered, keep the rule: nothing
     lighter than the artwork may stand in for it. */
  --font-lettering: "Anton", "Haettenschweiler", Impact, "Arial Narrow", sans-serif;
  --font-display: "Libre Franklin", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-body:    "Libre Franklin", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-ui:      "Libre Franklin", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

  /* 11px labels and 13px body copy read as unfinished, and Joe's customers are
     homeowners who skew older. Supporting copy is 15px, labels 12px. */
  --text-xs:      0.75rem;
  --text-sm:      0.9375rem;
  --text-base:    1.0625rem;
  --text-md:      1.1875rem;
  --text-lg:      clamp(1.375rem, 1.1rem + 1.1vw, 1.875rem);
  --text-xl:      clamp(1.75rem, 1.3rem + 2vw, 2.75rem);
  --text-2xl:     clamp(1.75rem, 1.2rem + 2.4vw, 2.75rem);
  --text-statement: clamp(1.75rem, 1.15rem + 2.7vw, 3.25rem);
  --text-display: clamp(2.5rem, 1.1rem + 5.4vw, 5rem);

  --track-display: -0.012em;
  --track-label:   0.16em;

  /* ---- Space (4pt) --------------------------------------------------- */
  --space-3xs: 0.25rem;  --space-2xs: 0.5rem;   --space-xs: 0.75rem;
  --space-sm:  1rem;     --space-md:  1.5rem;   --space-lg: 2rem;
  --space-xl:  3rem;     --space-2xl: 4.5rem;   --space-3xl: 5rem;
  /* was clamp(5rem, 11vw, 11rem) -> 158px at 1440, and it is the padding on
     most sections. That single token was the biggest driver of page length. */
  --space-4xl: clamp(3rem, 6vw, 6.5rem);

  --gutter: clamp(1.25rem, 5vw, 4rem);
  --measure: 58ch;

  /* ---- Shape ---------------------------------------------------------
     Radius is 0 across the board. Rounded, bordered, shadowed boxes are the
     single most reliable "generated" tell; the reference sites have none. */
  --radius: 0;
  --rule-hair: 1px;
  --photo-gap: clamp(2px, 0.4vw, 6px);
  /* How far Joe's photo overhangs the grain band at BOTH ends — up into the
     film, down into the trades panel. The band is deliberately shorter than
     the picture: the wood is a stripe behind him, not a field around him.
     Shared three ways — the film and the trades panel each reserve the same
     amount so their type never lands under the overhang. */
  /* Stepped down twice from 6vw: -1/20, then -1/10 of the photo's height. The
     band's own height is fixed by the text column, so every reduction has to
     come out of the two overhangs — which is why the token falls much faster
     than the photo does. At 1440 this leaves ~30px of Joe past each edge of
     the grain; below about 1.5vw the overlap stops reading as deliberate. */
  --meet-break: clamp(0.9rem, 1.74vw, 1.74rem);

  /* ---- Motion -------------------------------------------------------- */
  --ease-out:    cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in:     cubic-bezier(0.7, 0, 0.84, 0);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  /* Overshoots its end value by a hair and settles back. Only for the MEET
     panel flip: a hinge that stops dead at flat reads as a slide, and the
     small pass-through-and-return is the whole reason the flip looks like a
     physical panel. Do not put this on opacity — the overshoot clips at 1 and
     the fade just stalls. */
  --ease-flip:   cubic-bezier(0.22, 0.9, 0.24, 1.04);
  --dur-short:   180ms;
  --dur-mid:     320ms;
  --dur-long:    700ms;

  /* The trades-panel crossfade. It is a token because THREE things have to
     agree: this, the `setInterval` in script.js, and the `fillbar` keyframe
     duration in styles.css. Keep it comfortably under the 2s dwell — the
     photograph needs a settled moment that is longer than its own arrival. */
  --pick-fade:   800ms;

  /* ---- Icon ---------------------------------------------------------- */
  --phone: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6.6 10.8a15.1 15.1 0 0 0 6.6 6.6l2.2-2.2a1 1 0 0 1 1-.24 11.4 11.4 0 0 0 3.6.58 1 1 0 0 1 1 1V20a1 1 0 0 1-1 1A17 17 0 0 1 3 4a1 1 0 0 1 1-1h3.5a1 1 0 0 1 1 1 11.4 11.4 0 0 0 .58 3.6 1 1 0 0 1-.25 1z'/%3E%3C/svg%3E");
}
