/* Coffee Digital theme — the palette override.
   ────────────────────────────────────────────────────────────────────────────
   Loaded as a static <link> at the END of <head>, on every route.

   Static, and in the head, on purpose. The override used to arrive as a <style>
   injected by a module at the end of <body>, which meant one guaranteed frame of
   TRIONN's near-black before the cream landed. A stylesheet in the head is part
   of the first paint.

   Everything here is colour. No layout property is touched — no width, spacing,
   font-size, position or display. The site's fluid scale (`html{font-size:
   calc(1000vw / var(--size))}`) and its `--spacing` ladder are deliberately left
   alone; they are the two knobs that would re-lay-out the entire site.

   Specificity strategy: this file is loaded after the Tailwind build, so equal-
   specificity rules win on source order alone and need no !important. The
   exceptions are marked where they occur, and each says what it is beating.

   The inversion
   ─────────────
   TRIONN's design is dark-on-dark: a near-black ground (#040508) with light type
   (#d8d8d8) and panels a shade off the ground. Coffee Digital's locked palette is
   the opposite — a warm paper ground with dark ink type. So this is not a recolour,
   it is an inversion, and the rule applied throughout is:

       every DARK surface  ->  a light surface (cream / paper)
       every LIGHT surface ->  a warm light chip (peach)
       every text colour   ->  ink

   Sending light surfaces to peach rather than to ink is a deliberate, conservative
   choice. A true polar inversion (light card -> dark card) would preserve the
   original contrast drama, but it makes the correct text colour on each panel
   depend on which panel it sits in — and `text-dark-font` alone appears 402 times
   across contexts that cannot be told apart from the stylesheet. Sending
   everything light and all type to ink is guaranteed readable everywhere. Contrast
   is then added back deliberately, per section, after looking at it. */

/* ══ Typefaces ═════════════════════════════════════════════════════════════
   TRIONN's original four faces are kept as-is: neueHaas (body), familjen
   (display), ppEditorial (numerals), martianMono (buttons).

   An earlier pass replaced all four with Instrument Serif + Geist. That is
   reverted — the serif changed the character of every headline on the site,
   which is a design change, and the brief is content-only. The original
   @font-face blocks in _next/static/chunks/0-x39_77jza10.css and their
   measured `Fallback` metric-override companions are therefore left
   untouched, so line boxes stay exactly where TRIONN's build put them.

   Still open (tracked in docs/LEGAL.md, not fixed here): Neue Haas Display
   (Monotype) and PP Editorial New (Pangram Pangram) are commercial licences
   that arrived with the scrape and were never bought. Familjen Grotesk and
   Martian Mono are SIL OFL and are fine. Licensing the two commercial faces
   — or swapping only those two for metric-matched OFL equivalents — is a
   pre-launch decision, deliberately not made unilaterally here. */

/* ── Tokens ────────────────────────────────────────────────────────────────
   Unlayered, so this block beats the Tailwind `@layer theme` :root regardless
   of source order, and beats the two later unlayered :root blocks on order. */
:root {
  /* The locked palette. coffee-background.iife.js's injectPalette() also writes
     --paper/--cream/--peach/--orange/--ink at runtime from the WebGL scene
     config, so the page ground and the rendered ground can never drift. These
     are the same values, declared statically so first paint doesn't wait for
     the bundle — and so the page is still themed if it fails to load. */
  --paper: #fffaf3;
  --cream: #fff2db;
  --peach: #ffe5bf;
  --orange: #ff6a00;
  --ink: #3f2210;
  /* --heat is NOT written by injectPalette() (it isn't a scene colour), so this
     is its only declaration. 3.86:1 on paper — AA-large only. Never body text:
     display type 30px and up, fills, rules and indicators. */
  --heat: #f62440;

  /* Pure white, and deliberately NOT --paper (#fffaf3). The client asked for
     #fff by name for the awards band (Changes.xlsx row 7). Tokenised rather
     than inlined so the one place this steps outside the warm palette is
     declared here, beside the palette, instead of buried in a rule. The
     matching transition stripes carry the same value as an inline style in the
     chunk — see content/chunk-strings.json. */
  --awards-ground: #fff;

  /* Derived. Hairlines and muted type as ink at low alpha, so they sit on any
     surface in the palette without needing a per-surface value. */
  --line: color-mix(in srgb, var(--ink) 16%, transparent);
  --line-strong: color-mix(in srgb, var(--ink) 28%, transparent);
  --ink-muted: color-mix(in srgb, var(--ink) 62%, transparent);
  --ink-faint: color-mix(in srgb, var(--ink) 38%, transparent);

  /* ── Tailwind's own theme tokens, repointed ──
     These feed ~74 generated utilities plus their color-mix alpha variants, so
     each line here fixes every consumer at once. --color-light-font is the
     single highest-leverage value in the file: it is TRIONN's body-text colour
     and it reaches every `text-light-font` on the site. */
  --color-light-font: var(--ink);
  --color-dark-font: var(--ink);
  --color-black: var(--ink);
  --color-grey-light: var(--ink-muted);
  --color-grey-line: var(--line);
  --color-cream: var(--cream);
  --color-cream-line: var(--line);
  /* --color-white stays a light value: it is used for surfaces (bg-white, 132x)
     as well as type. The type case is handled by overriding .text-white below,
     because one token cannot be both a readable ink and a light ground. */
  --color-white: var(--paper);

  /* The unlayered component blocks further down the Tailwind build. */
  --bg: var(--cream);
  --white: var(--paper);
  --ink-soft: color-mix(in srgb, var(--ink) 55%, transparent);
  --glass-line: var(--line);

  /* The .ts-* "terminal scan" component. */
  --ts-color-bg: var(--cream);
  --ts-color-hint: var(--ink-muted);
  --ts-color-name: var(--ink);
  --ts-color-role: var(--ink-faint);
  --ts-color-scan-id: var(--ink-muted);
  --ts-color-detected: var(--ink);
}

/* ── The ground ────────────────────────────────────────────────────────────
   html/body/#hero-section carry background-color as literals (not through a
   token), so the token block above cannot reach them.

   !important here is beating coffee-page-ground.js, which sets the same three
   selectors with !important at runtime. That script stays for now because it
   also calls injectPalette(); this rule exists so the cream is present in the
   FIRST paint rather than after a module executes. Once the ground is proven
   stable from CSS alone, the script's style block can go and these three
   !importants with it.

   --cream, not --paper, matches the existing runtime choice: the WebGL room's
   fog and its backdrop bottom are both --cream precisely so the horizon never
   seams, and the page ground has to be that same value or the seam reappears
   wherever the canvas doesn't reach. */
/* The fallback ground goes on html, and body is transparent, so the fixed room
   layer (a child of html at z-index:-1) is visible through it. Painting body
   would bury the room: a negative z-index sits above the root background
   canvas but below in-flow content, and an opaque body background is in-flow
   content as far as that ordering is concerned. */
html {
  background-color: var(--cream);
}

/* !important is beating an INLINE write, not another stylesheet.

   The work route's hero init (_next/static/chunks/0mr3veaodr-fk.js) opens with
   `document.body.style.backgroundColor = "#040508"` — it was built for a black
   stage. An inline declaration outranks any non-important rule, so this rule
   was silently losing on /work only, and the page rendered on the
   original's near-black: dark-brown headings barely legible on it, project thumbnails
   floating over black, the wireframe mark invisible. That is Changes.xlsx row
   16, "This thing is cracking and the animation is not completing" — the
   animation completes, it was running against the wrong ground.

   Fixed by stylesheet rather than by chunk-editing that literal, for the same
   reason the services plate and the footer fog are: a rule applies to whatever
   node exists at the time and cannot be raced, whereas the init can re-run.

   transparent, not a colour: the fixed room layer is a child of html at
   z-index -1, and an opaque body background is in-flow content as far as that
   ordering goes, so painting body would bury the room. */
body {
  background-color: transparent !important;
}

/* ── Letting the room through ──────────────────────────────────────────────
   coffee-room.js mounts the WebGL room as a FIXED, body-level layer at
   z-index 0, so it spans the viewport for the whole scroll rather than one
   screenful inside the hero. Page content sits above it (main is "relative
   z-1"), which means any full-width section that paints an opaque background
   hides the room completely — which is exactly what "no background blend"
   looked like: atmosphere in the hero, flat colour everywhere after it.

   These are the page-level stages. They go transparent so the one room behind
   them is the ground for all of them. Cards, panels, chips and the menu
   overlay deliberately KEEP their surfaces — they are objects sitting in the
   room and need their own value to read as such. That is why this list is by
   id/section rather than by colour: the same bg-white that paints a
   full-height stage also paints a 380px project card. */
#hero-section,
#work-section,
#site-footer,
#testimonials,
#trionn-services,
.bg-\[\#C3C3C3\] {
  background-color: transparent;
}

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

/* ── Arbitrary-value utilities ─────────────────────────────────────────────
   Tailwind compiled these to real rules (`.bg-\[\#040508\]{background-color:
   #040508}`). Redeclaring the identical selector later in source order wins at
   equal specificity — no !important needed. 21 distinct colours; grouped by the
   role they played in the original dark design rather than by hex value. */

/* Page-ground family: the near-blacks everything else sat on. */
.bg-\[\#040508\],
.bg-\[\#0C0C0C\],
.bg-\[\#000000\],
.bg-\[\#000\],
.bg-black {
  background-color: var(--cream);
}

/* Panels that sat a shade off the ground — now lift a shade off it instead. */
.bg-\[\#111214\],
.bg-\[\#131415\],
.bg-\[\#171717\],
.bg-\[\#2F3135\],
.bg-\[\#2F323B\] {
  background-color: var(--paper);
}

/* Surfaces that read as high-contrast light against the dark ground.
   These go to PAPER, not peach. `bg-white` alone is used on full-height
   sections (one is 1275px tall on /about), and a whole viewport of peach is
   both far too warm and a straight violation of the palette's one-accent-per-
   viewport rule. Paper against the cream ground is a quiet half-step — which
   is the correct reading of a surface whose job was "a shade off the ground". */
.bg-\[\#D8D8D8\],
.bg-\[\#D2D2D2\],
.bg-\[\#D9D9D9\],
.bg-\[\#C3C3C3\],
.bg-\[\#F7F7F7\],
.bg-\[\#E1E4E7\],
.bg-white {
  background-color: var(--paper);
}

/* ── Project detail pages ──────────────────────────────────────────────────
   The case-study body rendered by brand/js/coffee-project-page.js at
   /work/<slug>/. Those pages reuse work/index.html as their shell, so the
   header, footer, room and fonts are the site's own; only this block is new.

   Sized off the site's own scale rather than fixed pixels, and coloured only
   through palette tokens, so a change to either follows here automatically. */
.cd-project {
  position: relative;
  z-index: 2;
  padding: 12rem 0 8rem;
}

.cd-project__inner {
  width: min(72ch, calc(100% - 3rem));
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.cd-project__back,
.cd-project__more {
  font-size: .82rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-muted);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
  padding-bottom: .3rem;
  transition: color .2s, border-color .2s;
}

.cd-project__back:hover,
.cd-project__more:hover {
  color: var(--ink);
  border-bottom-color: var(--line-strong);
}

.cd-project__more { margin-top: 4rem; }

.cd-project__meta {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin: 3rem 0 1.5rem;
}

.cd-project__year,
.cd-project__tag {
  font-size: .72rem;
  letter-spacing: .07em;
  text-transform: uppercase;
  padding: .35rem .7rem;
  border: 1px solid var(--line);
  color: var(--ink-muted);
}

.cd-project__year {
  background: var(--peach);
  color: var(--ink);
  border-color: transparent;
}

.cd-project__title {
  font-size: clamp(2.4rem, 6vw, 4.6rem);
  line-height: 1.04;
  letter-spacing: -.03em;
  margin: 0;
  color: var(--ink);
  text-wrap: balance;
}

.cd-project__lead {
  font-size: clamp(1.05rem, 1.7vw, 1.4rem);
  line-height: 1.5;
  color: var(--ink-muted);
  margin: 1.5rem 0 0;
  max-width: 54ch;
  text-wrap: pretty;
}

.cd-project__block { margin-top: 3.5rem; }

.cd-project__list {
  margin: 0;
  padding: 0;
  list-style: none;
  max-width: 66ch;
  display: flex;
  flex-direction: column;
  gap: .7rem;
}

.cd-project__list li {
  font-size: 1.02rem;
  line-height: 1.55;
  color: var(--ink);
  padding-left: 1.1rem;
  position: relative;
  text-wrap: pretty;
}

/* A rule rather than a bullet: the site sets lists this way elsewhere, and a dot at this
   type size reads as noise next to the hairlines the rest of the page is built from. */
.cd-project__list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .72em;
  width: .5rem;
  height: 1px;
  background: var(--line-strong);
}

.cd-project__h2 {
  font-size: .78rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 .9rem;
}

.cd-project__body {
  font-size: 1.02rem;
  line-height: 1.65;
  color: var(--ink);
  margin: 0;
  max-width: 66ch;
  text-wrap: pretty;
}

@media (max-width: 640px) {
  .cd-project { padding: 8rem 0 5rem; }
  .cd-project__block { margin-top: 2.5rem; }
}

/* ── The hero scroll indicator ─────────────────────────────────────────────
   Replaces the "hold to blast" label at the bottom of the hero (Changes.xlsx
   row 3). CSS rather than Lottie: lottie-web would be a new runtime dependency
   on a repo with no bundler and no build step, to draw a rounded rectangle and
   a dot that travels down it.

   Sized in em so it tracks the .title scale it sits beside rather than fixing
   a pixel height the site's fluid scale would fight. Colour comes from --ink
   at the same 55% the surrounding hint type uses. */
.cd-scroll-mouse {
  width: 1.55em;
  height: 2.6em;
  border: 1px solid color-mix(in srgb, var(--ink) 55%, transparent);
  border-radius: 1em;
  position: relative;
  flex: none;
}

.cd-scroll-mouse::after {
  content: "";
  position: absolute;
  left: 50%;
  top: .42em;
  width: .22em;
  height: .55em;
  margin-left: -.11em;
  border-radius: .11em;
  background: color-mix(in srgb, var(--ink) 75%, transparent);
  animation: cd-scroll-wheel 1.9s cubic-bezier(.45,.05,.3,1) infinite;
}

@keyframes cd-scroll-wheel {
  0%      { transform: translateY(0);      opacity: 0; }
  18%     { opacity: 1; }
  70%     { transform: translateY(.85em);  opacity: 1; }
  100%    { transform: translateY(1.05em); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .cd-scroll-mouse::after { animation: none; }
}

/* ── The awards band ───────────────────────────────────────────────────────
   #keyfacts-section shipped as
   `bg-[linear-gradient(0deg,#FFFFFF_0%,#D2D2D2_100%)]` — a background-IMAGE,
   so the background-color rule above does not reach it, and the section still
   read grey at its lower edge where the stripes wipe in.

   Overridden by ID rather than by editing that class: the class name IS the
   compiled selector and the Tailwind build here is frozen, so renaming it
   would leave the markup matching no rule at all (CLAUDE.md gotcha 3).

   `background-image: none` is load-bearing — without it the gradient keeps
   painting over the colour and nothing visibly changes. */
#keyfacts-section {
  background-image: none;
  background-color: var(--awards-ground);
}

/* ── About: text spacing ───────────────────────────────────────────────────
   Changes.xlsx row 8: "Give more text spacing".

   The request is open to three readings — leading, tracking, or block padding.
   Leading is the one with an objective problem behind it: the section headline
   shipped at line-height 81px on an 85.5px font size, a ratio of 0.95. Leading
   tighter than the type size means the descenders of one line sit in the
   x-height of the next; at 1440 the "g" of "strategy" runs into "to bring"
   below it. 1.06 opens that without turning a display setting into body
   setting.

   Tracking is deliberately NOT touched. It is -0.06em here, which is a
   deliberate display-type choice rather than a defect, and changing it would
   re-line-break every heading.

   Scoped to .home-about so the /about route's own headings keep their setting;
   this is the home section the client marked up. The site's fluid scale and
   --spacing ladder stay untouched — they re-lay-out the whole site.

   Safe to grow: .home-about is min-h-dvh with justify-center, not a fixed
   height, so the extra leading expands the section rather than overflowing it. */
.home-about h2 {
  line-height: 1.06;
}

.home-about p {
  line-height: 1.55;
}

/* Peach is kept for genuinely small, deliberately-warm elements: pills, chips
   and hover states. Scoped by shape (rounded-full) rather than by colour, so it
   cannot land on a section. */
.bg-white.rounded-full,
.bg-\[\#D9D9D9\].rounded-full {
  background-color: var(--peach);
}

/* Type. Every one of these was light-on-dark or dark-on-light-chip; all become
   ink on a light ground. */
.text-\[\#D8D8D8\],
.text-\[\#e8e8e8\],
.text-\[\#111214\],
.text-\[\#000000\],
.text-white,
.text-black {
  color: var(--ink);
}

.text-\[\#777\] {
  color: var(--ink-muted);
}

/* Borders. */
.border-\[\#2F323B\],
.border-\[\#111214\],
.border-\[\#24262E\],
.border-white {
  border-color: var(--line);
}

/* The warm accent family. TRIONN ran a red/orange accent; Coffee Digital's
   equivalents are --orange (the colour that blooms) and --heat (the one hot
   colour). Text keeps to --orange: --heat is AA-large only and these classes
   are used at body sizes. */
.text-\[\#d9432b\],
.text-\[\#ff6b50\] {
  color: var(--orange);
}

.border-\[\#d9432b\],
.border-\[\#ff4b2f\] {
  border-color: var(--orange);
}

/* ── Alpha variants of the arbitrary classes ───────────────────────────────
   Tailwind compiles `.bg-[#040508]/40` into its own rule with the colour baked
   in as a literal (#04050866), so the un-suffixed override above does not reach
   it. Seven rules, enumerated by tools/scan-alpha.mjs.

   The SEMANTIC alpha classes (.bg-black/10, .text-light-font/50) need nothing
   here: those compile to color-mix(in oklab, var(--color-black) 10%,
   transparent), so repointing the token already fixes every alpha step.

   Translucent dark washes stay translucent DARK — they are scrims and
   shadows, and their job is to knock back what is behind them. Inverting a
   scrim to a light wash would erase the effect rather than re-theme it. */
.bg-\[\#000\]\/60,
.bg-\[\#000000\]\/40,
.bg-\[\#040508\]\/40 {
  background-color: color-mix(in srgb, var(--ink) 45%, transparent);
}

.bg-\[\#D8D8D8\]\/20,
.bg-\[\#D9D9D9\]\/10,
.bg-\[\#E1E4E7\]\/50 {
  background-color: color-mix(in srgb, var(--ink) 8%, transparent);
}

.border-\[\#111214\]\/15 {
  border-color: var(--line);
}

/* ── Muted type: alpha steps that no longer clear AA ───────────────────────
   The semantic alpha classes compile to
   `color-mix(in oklab, var(--color-light-font) 50%, transparent)`, so
   repointing the token fixed their hue but NOT their contrast. The original
   ran #d8d8d8 at 50% over a near-black ground, which was comfortably readable;
   ink at 50% over cream measures 2.99:1 against a 4.5 requirement.

   The design intent is "quieter than body text", not "50% alpha" — so these
   are re-expressed as the lowest opacity that still clears AA on cream. Ink at
   72% measures ~4.6:1. Found by the contrast audit, which flagged 7 nodes
   (the E./P. contact labels and the footer copyright) on one rule.

   Listed explicitly rather than via an attribute selector because these are
   the exact eight text-alpha classes the build emits — enumerated by
   tools/scan-token-alpha.mjs. */
.text-light-font\/40,
.text-light-font\/50,
.text-light-font\/60,
.text-dark-font\/50,
.text-dark-font\/60,
.text-black\/60,
.text-white\/60 {
  color: color-mix(in srgb, var(--ink) 72%, transparent);
}

/* `text-cream` was a LIGHT type colour on a dark ground. --color-cream is now
   the page ground itself, so cream-on-cream renders at a 1.0 contrast ratio —
   literally invisible. Caught by the audit on the /about testimonial role
   line ("Founder & CEO"). Type never wants the ground colour. */
.text-cream,
.text-cream\/60 {
  color: color-mix(in srgb, var(--ink) 72%, transparent);
}

/* ── Inline custom properties React re-asserts ─────────────────────────────
   The button component writes `--button_wrapper-color` inline on .button_wrapper
   on every render, and the ~20 declarations that consume it (the underline
   fills, the arrow path, the per-character text colour) inherit from there.

   Matched on the PROPERTY NAME only, not on its value. Different button
   instances ship different values (#D8D8D8, #434343, #272727), React writes
   them with a space after the colon, and any of that can change — a
   value-matching selector silently stops applying and the button goes back to
   near-invisible. Every button on a cream ground wants ink regardless, so the
   value is irrelevant to the fix.

   !important is required and is the documented exception: an inline custom
   property is only beatable by !important on a matching selector. */
.button_wrapper[style*="--button_wrapper-color"] {
  --button_wrapper-color: var(--ink) !important;
}

/* ── Genuinely dark chips ──────────────────────────────────────────────────
   `bg-dark-font` follows --color-dark-font, which is now ink — so these become
   real dark elements on the cream ground. That is wanted: an all-light page
   has no anchor, and these are small (pills, badges, the sound toggle). They
   need their type flipped to paper, since the page default is ink and ink on
   ink is invisible. Caught by the contrast scan, which found a 1.0 ratio. */
.bg-dark-font,
.bg-current {
  color: var(--paper);
}

.bg-dark-font a,
.bg-dark-font span,
.bg-dark-font p,
.bg-dark-font svg {
  color: var(--paper);
}

/* ── SVG presentation attributes ───────────────────────────────────────────
   A CSS `stroke`/`fill` beats a presentation attribute (stroke="#d8d8d8") with
   no !important, because attributes have no specificity. It needs !important
   only against an inline style attribute, which is what the second block does. */
svg [stroke="#D8D8D8"],
svg [stroke="#d8d8d8"],
svg [stroke="#434343"] {
  stroke: var(--line-strong);
}

svg [fill="#D8D8D8"],
svg [fill="#d8d8d8"] {
  fill: var(--ink);
}

svg [style*="stroke:#D8D8D8"],
svg [style*="stroke:#d8d8d8"],
svg [style*="stroke:#272727"] {
  stroke: var(--line-strong) !important;
}

/* ── mix-blend-difference subtrees ─────────────────────────────────────────
   The blend is switched OFF and these subtrees are given ink directly.

   An earlier pass kept the blend and restored TRIONN's #d8d8d8 source, on the
   arithmetic that difference composites |backdrop - source| and
   |(255,242,219) - (216,216,216)| = (39,26,3) — a near-black warm brown, i.e.
   the intended reading. That arithmetic is right and the result was still
   wrong, because it assumes the cream is IN the blend's backdrop. It is not.

   The room is now a fixed layer on documentElement at z-index:-1, and the
   blended subtrees live inside main (z-1). A blend can only see backdrop
   within its own stacking context, so with #hero-section and body both
   transparent there is nothing behind these groups to difference against —
   the source colour passes through essentially raw. #d8d8d8 over a light
   room reads as pale grey type, which is exactly the washed-out headline and
   header the browser showed.

   Painting cream back onto the group's ancestor would restore the operand,
   but that ancestor is #hero-section, and making it opaque again is precisely
   what hides the room. Given a ground that is now reliably light everywhere,
   the adaptation the blend bought is no longer worth its cost: ink on cream
   is legible without it, and without a hidden dependency on which ancestor
   happens to be painted. */
.mix-blend-difference {
  mix-blend-mode: normal;
}

.mix-blend-difference,
.mix-blend-difference * {
  color: #0c0c0c;
}

/* The responsive variant is a SEPARATE class, and the rules above do not
   reach it. Tailwind emits `.md\:mix-blend-difference` inside a min-width
   media query; `.mix-blend-difference` does not match that name, so the
   cookie-consent banner — the one element on the site using the md: form —
   kept its blend and stayed washed out on desktop while every other blended
   subtree was fixed.

   The banner's own colour is `text-light-font`, which this file already
   points at `--ink`. Under difference against the cream ground that computes
   to |cream - ink| — a pale grey, i.e. dark type rendering LIGHT. Same
   inversion trap as the block above, reached through a class the block above
   could not see.

   No !important: media queries add no specificity, and this file loads after
   the Tailwind build, so source order alone wins. */
.md\:mix-blend-difference {
  mix-blend-mode: normal;
}

.md\:mix-blend-difference,
.md\:mix-blend-difference * {
  color: #0c0c0c;
}
/* ── Services orbit stage ──────────────────────────────────────────────────
   `.services-orbit-scope` sets background:#0a0a0a as a literal on the class —
   a value that appears in no utility and no token, so nothing above reaches
   it. It is a full-viewport section, so it left /services as the one route
   still on TRIONN's black. */
.services-orbit-scope {
  background: var(--cream);
}

/* ══ Loading screen ════════════════════════════════════════════════════════
   The preloader ships its own token block in 0wf1gwg29cqjw.css and its own
   near-black stage. It is the first thing anyone sees, so it cannot stay on
   TRIONN's palette or keep TRIONN's mark.

   Re-grounded to the site's own colours. The token names stay (they are
   referenced throughout that stylesheet); only their values change. */
:root {
  --pl-black: var(--ink);
  --pl-white: var(--paper);
  --pl-panel-gray: var(--cream);
  --pl-line-gray: var(--line-strong);
}

/* The belts and the logo box both take --pl-panel-gray, so the whole stage
   becomes cream. The box border was a hardcoded near-black. */
.pl-logo-black-box {
  border-color: var(--line-strong) !important;
}

/* The mark.

   `.pl-t-path` is three SVGs holding TRIONN's "T" as hardcoded path data,
   revealed one at a time by clip-path wipes. They are hidden rather than
   re-pathed: the geometry lives as three ~520-character path constants inside
   the minified chunk, in a viewBox (-79.5 -31.4 662 640) that Coffee Digital's
   two-shape mark does not map onto without a real coordinate transform — and
   svgpathtools, which the repo's other brand scripts use for exactly that, is
   not installed here.

   Showing no mark would be the safe half of the fix; this does the other half
   by painting the actual Coffee Digital mark into the same box as a background
   image. images/favicon.svg is already a true vector of it. The wipe animation
   still runs on the (now invisible) paths, so nothing in the chunk's timeline
   breaks — the box simply carries the mark for the whole sequence instead of
   drawing it on stroke by stroke.

   Left as a known limitation: the draw-on reveal is lost. Restoring it means
   generating fitted path data for that viewBox — see docs/FLAGS-AND-BUGS.md. */
.pl-t-path {
  opacity: 0 !important;
}

.pl-overlay-logo-wrap::after {
  content: "";
  position: absolute;
  /* PROVISIONAL sizing and artwork, pending the client's promised SVG
     (Changes.xlsx row 2: "Logo to be made smaller and thin (will provide the
     new SVG)"). Both halves of that are reachable with what is already in the
     repo, so this shows the direction rather than waiting:

       smaller — inset 18% -> 27%, about a third off the mark's width. Done.

     thin — NOT done, and not doable with what is here. The obvious move was
     brand/marks/Thin.svg, but measuring ink coverage at a fixed size says
     otherwise: favicon.svg 22.1%, Thin.svg 31.6%, Thick.svg 35.5%. The
     favicon build is already the thinnest of the three; Thin and Thick are
     named relative to each other, not to it. Swapping would have made the
     mark heavier, which is the opposite of the request. Genuinely needs the
     client's file — a thinner weight cannot be synthesised from a filled
     path without redrawing it.

     Sizing is a percentage inset rather than a fixed size so it stays right
     whatever the replacement artwork's aspect turns out to be. */
  inset: 27%;
  /* The mark is masked, not painted, so its colour comes from the palette rather
     than from the file. images/favicon.svg is fill="#000" and is ALSO the real
     favicon in all five HTML heads — recolouring the file would brown the browser
     tab icon too. Masking recolours only this instance and leaves the file alone.
     --ink (#3f2210) is the palette's brown; the client asked for brown, not black. */
  background-color: var(--ink);
  -webkit-mask-image: url("/images/favicon.svg");
  mask-image: url("/images/favicon.svg");
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
  z-index: 501; /* above .pl-t-path (500), below the page label (600) */
  pointer-events: none;

  /* The mark waits for its box.

     Changes.xlsx row 2 says the logo loads before the box around it, and does
     not match the original's order. It did, and the reason is structural
     rather than a timing tweak gone wrong. The box that scales in is
     .pl-logo-black-box; this mark is an ::after on the WRAPPER around it, because
     the original's three .pl-t-path SVGs could not be re-fitted to their viewBox
     (FLAG-6). The wrapper carries no transform, so it was at full size and fully
     opaque from the first frame while the box was still at scale(0).

     So the mark waits, and 02m2onmcd.l.g.js sets data-mark-in on the wrapper at
     the original's own gate — the moment the box passes 40% scale, the same
     instant the three T-paths used to begin.

     But the hiding is gated on data-mark-ARMED, set by that same chunk when the
     loader sequence starts, and that indirection is load-bearing. This file is
     served `no-store`; the chunk is served `public, max-age=31536000,
     immutable`. Hide here and reveal there, and any visitor holding a cached
     chunk gets new CSS with old JS — mark hidden, nothing to unhide it, logo
     gone. This is a static export with FIXED chunk filenames, so that state
     lasts up to a year after deploy, not until the next reload.

     Arming from the same file that disarms keeps the two in step. An old chunk
     sets neither attribute, so the mark just behaves as it did before — visible
     early, which is the original complaint, not a blank box. Degrade to the bug
     you were fixing, never to a worse one. */
  transform-origin: 52% 52%; /* the original's, not 50% */
}

.pl-overlay-logo-wrap[data-mark-armed]::after {
  opacity: 0;
  transform: rotateY(-60deg) rotateZ(28deg) scale(0.15);
}

/* The entry mirrors the first T-path's: 700ms, opacity ramping ~3x faster than
   the transform so the mark reads before it has finished settling, scale 0.15 ->
   1 out of a rotateY/rotateZ tilt. Those are the original's numbers, taken from
   the i(A.current,0,-60,28) call in the same chunk. */
@keyframes cd-pl-mark-in {
  from { opacity: 0; transform: rotateY(-60deg) rotateZ(28deg) scale(0.15); }
  40%  { opacity: 1; }
  to   { opacity: 1; transform: rotateY(0deg) rotateZ(0deg) scale(1); }
}

.pl-overlay-logo-wrap[data-mark-armed][data-mark-in]::after {
  animation: cd-pl-mark-in 700ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
}

@media (prefers-reduced-motion: reduce) {
  /* No tilt-and-swell, but the mark must still appear with the box rather than
     before it, so the gate stays and only the movement goes. */
  .pl-overlay-logo-wrap[data-mark-armed]::after { transform: none; }
  .pl-overlay-logo-wrap[data-mark-armed][data-mark-in]::after { animation: none; opacity: 1; }
}

/* ── Selection ─────────────────────────────────────────────────────────────
   The one place --heat earns its keep at small sizes: a selection fill is a
   block of colour, not type, so the AA-large limit doesn't apply. */
::selection {
  background-color: var(--peach);
  color: var(--ink);
}

/* ── Header wordmark ───────────────────────────────────────────────────────
   images/logo.svg is not a vector — it is a 1508x368 white PNG wrapped in an
   <svg>. So `fill: currentColor` cannot reach it and it rendered white on the
   cream ground, i.e. invisible. Sampled: its opaque pixels average
   rgb(250,249,245).

   `brightness(0)` maps every colour channel to zero while leaving alpha
   untouched, so the mark becomes solid black and its antialiased edges stay
   clean. Preferred over `invert(1)` because it does not depend on the source
   being exactly white — a slightly warm white inverts to a slightly blue
   black, which is visible against this palette.

   This is a workaround, not the fix. The real fix is regenerating logo.svg as
   a true vector from brand/marks/Thick.svg (via _tools/make-brand.py, which
   already exists and was simply fed a raster), so the mark can take
   currentColor and follow the theme instead of being forced. Tracked in
   docs/FLAGS-AND-BUGS.md. */
.site-header img[src$="logo.svg"],
#nav img[src$="logo.svg"] {
  filter: brightness(0);
}

/* ── Sound toggle ──────────────────────────────────────────────────────────
   Hidden, because there is no longer anything for it to turn on. All of the
   site's audio was TRIONN's — five UI effects and eight recorded voiceover
   lines — and has been removed from the tree; see brand/js/coffee-silence.js.
   A control that visibly does nothing is worse than no control.

   Hidden here rather than removed from the chunk that renders it: the button
   is wired into the header's layout and its own state machine, and deleting
   it from minified JSX to gain nothing visible is not a trade worth making.
   `display: none` also takes it out of the tab order and the accessibility
   tree, which a visibility/opacity hide would not.

   If Coffee Digital records its own set later, deleting this rule brings the
   control back intact. */
#sound-toggle {
  display: none;
}

/* ── Stacked discipline type ───────────────────────────────────────────────
   `.mrquee-text` is the big stacked word list ("Marketing / Creative /
   Technology") on the home services stage, and the same class carries the
   scrolling marquee elsewhere. Two things were wrong with it on cream.

   Colour: it inherits #d8d8d8 — TRIONN's light-on-dark grey — so on the cream
   ground it was very nearly invisible. This is the same class of bug as the
   blended subtrees above, just reached by inheritance rather than by a blend.

   Leading: the compiled rule sets font-size 120px against line-height 80.64px
   (0.672em). That is deliberate on TRIONN's part — the words are meant to
   pack into a dense slab — but it is tuned for Familjen Grotesk's cap height
   at that size, and any ascender/descender pair in adjacent words collides.
   With four long words stacked it read as one illegible mass. 0.86em keeps the
   slab tight and deliberately overlapping-looking while pulling the collisions
   apart; it is a smaller change than it sounds, roughly 23px per line. */
.mrquee-text,
.mrquee-text * {
  color: var(--ink);
}

.mrquee-text {
  line-height: 0.86em;
}

/* ── Header "let's talk" ───────────────────────────────────────────────────
   Transparent, to match the MENU control sitting next to it.

   The button ships as `bg-white border border-white rounded-full`, so the
   peach pill rule above was filling it while MENU — same padding, same
   radius, same border, but no bg utility — stayed clear. Side by side that
   read as two different components rather than one pair.

   Scoped to the header rather than by dropping the peach rule, because the
   only other element it paints is the decorative disc behind the "1.5K+"
   key fact, which is genuinely a filled shape and should keep its fill.

   The hover is neutralised for the same reason: `hover:bg-white/80` would
   reintroduce the fill on pointer-over, while MENU only shifts its border.
   Both now behave identically — border-only, transparent throughout. */
header .bg-white.rounded-full,
#nav .bg-white.rounded-full,
header .bg-white.rounded-full:hover,
#nav .bg-white.rounded-full:hover {
  background-color: transparent;
}

/* ── Scroll-scrubbed character reveal ──────────────────────────────────────
   The home/about statement ("Coffee Digital is a full-stack partner…") is
   split per character and scrubbed by a GSAP ScrollTrigger that tweens each
   char's colour as it comes up the viewport.

   In the original build this ran faint -> bright: the heading carried
   `text-[rgba(216,216,216,0.1)]` as the dim start and the tween landed on
   solid #d8d8d8, which is a light-on-dark reveal. Both ends inverted badly
   on cream — and worse, that arbitrary class compiles to NO rule at all
   (Tailwind never emitted it), so the start state was not dim, it was
   whatever the chars inherited: full-strength ink. The reveal therefore ran
   backwards, starting readable and fading to near-invisible pale grey as
   you scrolled. That is what "text reveal from black to white" was.

   Fixed at both ends: the tween target is now ink black (in
   0_-x7hddx_1l6.js), and the start state is defined here as a muted ink so
   there is something to reveal FROM.

   Selected by attribute substring rather than by the escaped class name —
   `text-\[rgba\(216\,216\,216\,0\.1\)\]` is a lot of escaping to get exactly
   right for a selector that has to survive future edits. */
h2[class*="rgba(216,216,216"],
h2[class*="rgba(216,216,216"] .chars {
  color: rgba(63, 34, 16, 0.22);
}

/* ── Header: no bar treatment ────────────────────────────────────────
   The header carried a 62% cream frosted bar with a 10px backdrop-blur and a
   mask that faded it out at its lower edge. Removed on client instruction
   ("Remove the top fade which have given to the header", Changes.xlsx row 4).

   What that block was doing, so it is not re-added by reflex: the original
   header was transparent and relied on mix-blend-difference to stay readable
   over whatever scrolled beneath it. Switching that blend off (see the
   mix-blend section above, which fixed the washed-out type) removed the
   mechanism, and the frosted bar was what replaced it.

   KNOWN CONSEQUENCE of removing it: hero elements scrolling up past the bar
   collide with the nav text — "LET'S TALK" and "MENU" render directly over the
   est-box and the hero sub-line. The client asked for the bar gone rather than
   re-solved, so this is accepted, not overlooked. If it needs fixing later,
   restoring the blend is NOT the route: the room is now a fixed layer outside
   the header's stacking context, so there is no backdrop to difference
   against. Hide the nav items over the hero, or bring the bar in on scroll. */

/* The painted mark leaves when the original's mark would have.

   The preloader hides its own logo by display:none-ing the three .pl-t-path
   SVGs, then fades the border ring, then expands the box to fill the screen
   and fades the whole curtain out. Our replacement mark is a ::after on the
   WRAPPER (the paths could not be re-fitted to that viewBox — see FLAG-6), and
   the wrapper is not hidden at that point because the box expand still needs
   it. So the mark survived the expand and the fade, and sat over the revealed
   hero as a black shape until the overlay was finally removed.

   02m2onmcd.l.g.js now sets data-mark-hidden on the wrapper in the same
   statement that hides the three paths, so the two disappear together. The
   200ms fade matches the border ring's, so it reads as part of the same
   beat rather than a pop. */
/* [data-mark-armed] is carried here for SPECIFICITY, not logic — the attribute is
   always present by this point. The entry rule above is
   `[data-mark-armed][data-mark-in]` (0,3,1); this rule was `[data-mark-hidden]`
   alone (0,2,1) and therefore lost, so the entry animation's `forwards` fill kept
   asserting opacity 1 and the mark never left. It sat over the revealed hero for
   2159ms — measured t=7232 (flag set) to t=9391 (the whole overlay finally went
   display:none) — which is the "logo stays in the hero for 2-3 sec" report.

   Matching the entry rule's specificity and coming later in the file means this
   wins on source order. Adding an attribute to a rule can silently demote the
   rule that is supposed to undo it. */
.pl-overlay-logo-wrap[data-mark-armed][data-mark-hidden]::after {
  /* Cancelling the entry animation is required, not tidiness: it is `forwards`,
     so its filled opacity:1 would outrank this rule for as long as it stayed
     applied and the mark would never fade. Dropping the animation reverts the
     transform to the rule's base — scale(0.15) — so the settled transform is
     restated here, or the mark would shrink away instead of fading out. */
  animation: none;
  opacity: 0;
  transform: rotateY(0deg) rotateZ(0deg) scale(1);
  /* The 200ms fade lives HERE, on the state being transitioned TO, not on the
     base rule. On the base rule it also animated the ARMING: the mark's default
     is opaque (so a stale chunk can never hide it), so the frame the loader
     armed itself the mark faded 1 -> 0 over 200ms with the box still at scale 0
     — a visible flash of exactly the logo-before-box the fix is about, measured
     at t=2634..3007. Arming is now instant and only the exit is animated. */
  transition: opacity 200ms linear;
}
