/* =========================================================
   Martin Hübner — AI Inventory & Data-Flow Governance
   "Governed clarity" design system
   (Positioning repositioned 2026-08-20; the design system itself is unchanged.)
   ========================================================= */

/* ---------------------------------------------------------
   Fonts — SELF-HOSTED ON PURPOSE. Do not move these to a CDN.

   These three families used to load from Google's font CDN (googleapis.com /
   gstatic.com), which sent every visitor's IP to Google in the US on every page
   load, with no consent and no prior notice. LG München I (Az. 3 O 17493/20,
   20 Jan 2022) held that dynamic Google Fonts embedding cannot be justified
   under Art. 6(1)(f) DSGVO precisely BECAUSE the files can be hosted locally —
   so the transfer is not "necessary" and no disclosure or consent banner cures
   it. Hosting them here removes the transfer instead of documenting it. This
   site now makes zero third-party requests; re-adding a font CDN would
   silently undo that.

   Those two hostnames are deliberately never written in their full
   "fonts.<host>" form anywhere that reaches the built output: grepping dist/
   for them is the regression check for this whole change, and it is only
   useful while it returns nothing. See public/fonts/README.txt.

   Each family is ONE variable woff2 per subset (wght axis), which is what
   Google serves too — so the weight range below is the font's real fvar
   range, and every weight the tokens ask for is genuinely present rather
   than synthesised. Files came from Google's own CDN, so rendering is
   byte-identical to what the site shipped before. Licences (SIL OFL 1.1,
   which requires them to travel with the fonts) sit next to the files in
   public/fonts/.

   unicode-range splits latin from latin-ext verbatim as Google had it:
   `latin` alone covers German (ä ö ü ß are U+00DF/U+00E4/U+00F6/U+00FC),
   so latin-ext is fetched only if a page actually uses it. Note ⌜ ⌟ (the
   .eyebrow brackets) and → fall outside both ranges — they render from a
   system fallback, exactly as they did before this change.
   --------------------------------------------------------- */

/* Sora — var(--f-display). Site uses 400/600/700. */
@font-face{
  font-family:"Sora";
  font-style:normal;
  font-weight:400 800;
  font-display:swap;
  src:url("/fonts/sora-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:"Sora";
  font-style:normal;
  font-weight:400 800;
  font-display:swap;
  src:url("/fonts/sora-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;
}

/* Inter — var(--f-body). Site uses 400/500/600. */
@font-face{
  font-family:"Inter";
  font-style:normal;
  font-weight:100 900;
  font-display:swap;
  src:url("/fonts/inter-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:"Inter";
  font-style:normal;
  font-weight:100 900;
  font-display:swap;
  src:url("/fonts/inter-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;
}

/* JetBrains Mono — var(--f-mono). Site uses 400/500. */
@font-face{
  font-family:"JetBrains Mono";
  font-style:normal;
  font-weight:400 800;
  font-display:swap;
  src:url("/fonts/jetbrains-mono-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:"JetBrains Mono";
  font-style:normal;
  font-weight:400 800;
  font-display:swap;
  src:url("/fonts/jetbrains-mono-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{
  --ink:#0e1220;        /* deep cool near-black */
  --ink-2:#39414f;      /* secondary text on light */
  --ink-3:#6b7280;      /* muted text on light */
  --paper:#f5f6f8;      /* cool light background */
  --surface:#ffffff;    /* cards */
  --line:#e4e7ec;       /* hairlines */
  --line-2:#d5d9e0;
  --accent:#3a56f0;     /* electric cobalt — the one accent */
  --accent-press:#2c43ce;
  --accent-tint:#eaeefe;
  /* on dark */
  --on-dark:#eef1f6;
  --on-dark-mute:#9aa3b6;
  --dark-line:#242a3a;
  --dark-surface:#161b2b;

  /* Bands, drawn rules and washes — added with the masthead landing redesign. Nothing
     above is redefined and there is still exactly ONE accent hue.
     Two tokens from the approved mockup are deliberately NOT here: --paper-2 belonged to
     the portrait plate (no image asset exists, so that plate does not ship) and
     --shadow-card belonged to the light contact variant, which lost the pick. Adding
     either would be an unused token. */
  --band-dark:#0e1220;   /* today's --ink, decoupled from the TEXT colour: .footer and the
                            inset contact card are bands, not type. Same value on purpose —
                            .page-head keeps var(--ink) — so the two can diverge later
                            without a site-wide sweep. */
  --rule:#c9ced8;        /* a DRAWN hairline, one step stronger than --line-2 (#d5d9e0).
                            Carries the masthead: the section rules and the rung list's
                            outer rules. --line stays for card borders and the internal
                            dividers between plates. */
  --accent-wash:#dbe1fd; /* one step stronger than --accent-tint (#eaeefe, 1.07:1 on paper
                            and therefore unusable as a fill). 1.30:1 on --surface, so
                            DECORATIVE ONLY: it is the resting rule under the h1's accent
                            line and nothing reads on top of it. --accent on --accent-wash
                            is 4.30:1 and FAILS AA for small text, so small accent text on
                            a wash uses --accent-press (7.50:1) — see .mast-rung .tag b.
                            All three figures RE-COMPUTED in the QA pass (WCAG 2.x relative
                            luminance); the note previously read 1.28 / 4.43 / 5.94. Every
                            error was in the safe direction and both decisions stand. */
  --shadow-lift:0 30px 60px -40px rgba(14,18,32,.45); /* used ONCE: the inset dark contact
                            card. This direction otherwise uses no shadow at all. */

  --maxw:1120px;
  --gap:clamp(1rem,3vw,2rem);
  --r:14px;            /* card radius */
  --r-sm:9px;

  --f-display:"Sora",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
  --f-body:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
  --f-mono:"JetBrains Mono",ui-monospace,Menlo,Consolas,monospace;
}

*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}

body{
  font-family:var(--f-body);
  color:var(--ink);
  background:var(--paper);
  line-height:1.6;
  font-size:16px;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}

img{max-width:100%;display:block}
a{color:inherit;text-decoration:none}

.container{max-width:var(--maxw);margin:0 auto;padding:0 clamp(1.1rem,4vw,2rem)}

/* ---------- type ---------- */
h1,h2,h3{font-family:var(--f-display);font-weight:700;line-height:1.08;letter-spacing:-.02em;color:var(--ink)}
h2{font-size:clamp(1.7rem,3.6vw,2.6rem)}
h3{font-size:1.16rem;letter-spacing:-.01em}
p{color:var(--ink-2)}

.eyebrow{
  font-family:var(--f-mono);
  font-size:.74rem;
  letter-spacing:.16em;
  text-transform:uppercase;
  color:var(--accent);
  display:inline-flex;
  align-items:center;
  gap:.5em;
  font-weight:500;
}
.eyebrow::before{content:"⌜";font-size:1.05em;opacity:.9}
.eyebrow::after{content:"⌟";font-size:1.05em;opacity:.9}

/* Section kicker — the LANDING PAGE's quieter alternative to .eyebrow. .eyebrow's ⌜ ⌟
   brackets and full-accent colour are too loud for the masthead direction, and .eyebrow
   itself is untouched: /services, /resources, /blog, /privacy, /impressum and both 404s
   still use it, including its on-dark override under .page-head below. */
.sec-k{
  display:inline-flex;align-items:baseline;gap:.5em;
  font-family:var(--f-mono);font-size:.74rem;letter-spacing:.16em;text-transform:uppercase;
  color:var(--ink-3);font-weight:500;
}
.sec-k i{font-style:normal;color:var(--accent)}

/* A drawn structural hairline: not a border on a box, a line the page is built on. It
   animates itself on reveal — see the masthead block in "reveal" below. */
.rule{height:1px;background:var(--rule);border:0}

.section{padding:clamp(3.6rem,8vw,6.5rem) 0}
.section-head{max-width:44rem;margin-bottom:clamp(2rem,4vw,3rem)}
.section-head h2{margin:.7rem 0 .6rem}
.section-head p{font-size:1.04rem}

/* ---------- buttons ---------- */
.btn{
  display:inline-flex;align-items:center;gap:.55em;
  font-family:var(--f-body);font-weight:600;font-size:.95rem;
  padding:.8em 1.4em;border-radius:999px;border:1px solid transparent;
  cursor:pointer;transition:transform .16s ease,background .16s ease,border-color .16s ease,color .16s ease;
  white-space:nowrap;
}
.btn:active{transform:translateY(1px)}
.btn-primary{background:var(--accent);color:#fff}
.btn-primary:hover{background:var(--accent-press)}
.btn-on-dark{background:transparent;color:var(--on-dark);border-color:var(--dark-line)}
.btn-on-dark:hover{border-color:var(--on-dark);background:rgba(255,255,255,.04)}
/* the secondary button on LIGHT — the landing masthead's box is --surface. .btn-on-dark
   cannot be reused there (its colour is --on-dark, i.e. near-white) and must not be
   redefined: both 404 pages use it inside a dark .page-head. */
.btn-line{background:transparent;color:var(--ink);border-color:var(--line-2)}
.btn-line:hover{border-color:var(--ink);background:rgba(14,18,32,.03)}
.btn .arw{transition:transform .16s ease}
.btn:hover .arw{transform:translateX(3px)}

:focus-visible{outline:2.5px solid var(--accent);outline-offset:3px;border-radius:4px}

/* ---------- nav ---------- */
/* the tint is rgba(255,255,255,.86) — a --surface tint, NOT the --paper tint it used to be
   (rgba(245,246,248,.82)). The landing page's first box is --surface, and against white the
   paper tint read as a faint grey band with a visible seam at scroll-top. This class is on
   all 22 pages: the interior pages put a dark .page-head under the nav, where the change is
   a marginally whiter, marginally more opaque bar — nav text is --ink-2 on near-white
   either way, so their contrast only improves. The border-bottom hairline is what separates
   the bar from a white page and is load-bearing now, not decorative. */
.site-nav{position:sticky;top:0;z-index:50;background:rgba(255,255,255,.86);backdrop-filter:saturate(160%) blur(12px);border-bottom:1px solid var(--line)}
.nav-inner{display:flex;align-items:center;justify-content:space-between;height:66px}
/* NO `.brand .mk` RULE — the brand mark ("/ai-gov", later "/ai-inventory") was removed from
   Nav.astro with the masthead redesign, and the footer .brand never had one, so the rule had
   exactly one consumer and is gone with it. Both nav measurement notes below were re-measured
   for the markless row rather than left stale. The `gap:.5em` is harmless with one child. */
.brand{font-family:var(--f-display);font-weight:700;font-size:1.05rem;letter-spacing:-.02em;display:flex;align-items:center;gap:.5em}
.nav-links{display:flex;align-items:center;gap:2rem}
.nav-links a{font-size:.93rem;color:var(--ink-2);font-weight:500;transition:color .15s}
.nav-links a:hover{color:var(--ink)}
.nav-cta{display:flex;align-items:center;gap:1.1rem}
.nav-bar-lang{display:flex;align-items:center}
.nav-toggle{display:none;background:none;border:0;cursor:pointer;padding:.4rem;color:var(--ink)}
/* mobile-menu-only items: revealed inside the open burger menu (see responsive) */
.nav-menu-cta,.nav-menu-lang{display:none}

/* ---------- on-dark eyebrow override ---------- */
/* --accent on --ink is only 3.35:1 and fails AA; #9db0ff is 8.96:1. This used to read
   `.hero .eyebrow,.page-head .eyebrow`. The .hero half went with the dark landing hero (the
   landing page uses .sec-k now), and THE .page-head HALF MUST SURVIVE: /services,
   /resources, /blog, /privacy, /impressum and both 404 pages put .eyebrow on that dark
   band, and removing this drops their kicker to 3.35:1. */
.page-head .eyebrow{color:#9db0ff}
.page-head .eyebrow::before,.page-head .eyebrow::after{color:#9db0ff}
/* .hero-actions OUTLIVES .hero: both 404 pages use it inside .page-head, and the landing
   masthead reuses it via .mast-actions (its ≤680px full-width wrap rule is exactly right
   there). Do not fold it into either consumer. */
.hero-actions{display:flex;flex-wrap:wrap;gap:.8rem;margin-top:2rem}

/* ---------- landing S1 — VALUE: the masthead ---------- */
/* Replaces the dark `.hero` block, whose only consumer was Landing.astro. Everything else
   from that block is gone: .hero, .hero::before, .hero-inner, .hero-copy, .hero h1,
   .hero h1 .hl, .hero .lede, .hero .lede em, .hero-points. The 2.1rem h1 floor note that
   used to sit here RETIRED WITH `.hero h1` rather than being invalidated — it was tuned to
   a headline string that no longer exists. .mast-h1 carries its own measurement below. */
.mast{background:var(--surface);position:relative;overflow:hidden}
/* the governed-grid texture, rebuilt for light out of --line and masked into the top-right
   corner. Texture, not decoration — it should be almost deniable; at full opacity it read
   as a pattern rather than a surface at 900px. */
.mast::before{
  content:"";position:absolute;inset:0;pointer-events:none;
  background-image:linear-gradient(var(--line) 1px,transparent 1px),linear-gradient(90deg,var(--line) 1px,transparent 1px);
  background-size:46px 46px;opacity:.6;
  mask-image:radial-gradient(ellipse 48% 54% at 96% 2%,#000 0%,transparent 74%);
  -webkit-mask-image:radial-gradient(ellipse 48% 54% at 96% 2%,#000 0%,transparent 74%);
}
/* padding-block, not the padding shorthand: this block is also a .container, and a
   shorthand with 0 sides would wipe its inline gutter — invisible on desktop, edge-to-edge
   text on mobile (see commit cb3df89). Same for .thesis-inner and .contact-inner below. */
.mast-inner{position:relative;padding-block:clamp(1.4rem,3vw,2.2rem) clamp(2.8rem,6vw,4.4rem)}

/* The headline treatment is THREE CSS DEVICES AND NO JS — see the comment at the top of
   Landing.astro for how each one degrades. (a) and (b) live in the reveal block; (c) is
   the .hl rule here. */
.mast-h1{
  font-size:clamp(2.45rem,6.1vw,4.5rem);
  line-height:1.03;letter-spacing:-.032em;
  margin:clamp(2.4rem,6.5vw,4.8rem) 0 clamp(1.4rem,3vw,2.2rem);
  max-width:22ch;
  overflow-wrap:break-word;   /* only fires if a single word truly cannot fit */
}
.mast-h1 .ln{display:block}
/* --accent doing load-bearing work: 5.57:1 on --surface.
   TWO STACKED BACKGROUND LAYERS, not a ::before/::after bar on an inline-block. The DE
   accent line wraps at 360px, and an absolutely-positioned bar then draws ONE rule across
   the whole box instead of one under each line fragment; box-decoration-break:clone gives
   every fragment its own rule. PRESERVE THIS TECHNIQUE — the alternative was measured and
   is broken in DE. Layer 2 (--accent-wash) is the RESTING rule and is decorative only:
   1.28:1 on --surface, and nothing reads on top of it. */
.mast-h1 .hl{
  color:var(--accent);
  background-image:linear-gradient(var(--accent),var(--accent)),linear-gradient(var(--accent-wash),var(--accent-wash));
  background-repeat:no-repeat,no-repeat;
  background-size:0 .055em,100% .055em;   /* layer 1 sweeps in on hover, layer 2 rests */
  background-position:0 calc(100% - .04em),0 calc(100% - .04em);
  -webkit-box-decoration-break:clone;box-decoration-break:clone;
  transition:background-size .55s cubic-bezier(.16,.84,.24,1);
}
.mast-h1:hover .hl{background-size:100% .055em,100% .055em}
/* the sweep needs its OWN reduced-motion escape: it is not built on .reveal, so the reset
   in the reveal block below never reaches it. It is also hover-only, so it NEVER fires on a
   phone — the resting rule is the design and the fill is a bonus. */
@media (prefers-reduced-motion:reduce){.mast-h1 .hl{transition:none}}
/* 360px REGRESSION MEASUREMENT — and it is TIED TO THESE EXACT STRINGS. The h1 is
   "Know what AI you're running." / "And what data feeds it." and "Wissen, welche KI bei
   Ihnen läuft." / "Und welche Daten hineingehen.". Any rewrite by content-writer
   invalidates every figure below and has to be re-measured at 360 in EN *and* DE.
   Chrome 152.0.7977.65 headless, real self-hosted webfonts (document.fonts.ready awaited
   before every read — fallback metrics run ~16px narrower per line and will flatter the
   result; every figure below was taken with document.fonts.status == "loaded"):
     computed font-size 39.2px, i.e. the 2.45rem FLOOR is what binds. Measured crossover,
     unchanged by the rewrite and identical in both locales: the floor still binds at a 642px
     viewport and 6.1vw first wins at 643px (39.223px), so "below 643px" is the range — that
     figure is a function of the clamp alone and survives a copy rewrite.
     line-height 40.376px; .container leaves 324.8px inside its 1.1rem gutter floor at 360,
     284.8px at 320.
     360px EN: h1 rect 324.81x161.5 (content box 162) → 4 lines (.ln[0] 2 + .ln[1] 2).
     360px DE: h1 rect 324.81x242.25 (content box 242) → 6 lines (.ln[0] 3 + .ln[1] 3).
     320px EN: h1 rect 284.81x161.5 → 4 lines (2 + 2).   320px DE: 284.81x242.25 → 6 (3 + 3).
     NO overflow at either width in either locale: documentElement.scrollWidth ==
     clientWidth == the viewport, and the largest right edge of any element on the page is
     the html element itself at exactly the viewport width — nothing sits past it.
     Note the DE h1 is now two lines taller than it was (242 vs the old 202) and the EN split
     changed shape (2+2, not 3+1) at the same 162px total. Both are inside the column.
   AND THE REASON THE .hl TECHNIQUE ABOVE IS NOT NEGOTIABLE — the binding case is DE, and
   with these strings EN fragments at every phone width too. Measured .hl
   getClientRects().length by viewport, with the h1's total line count in brackets:
     EN  320:2[4] 340:2[4] 360:2[4] 375:2[4] 390:2[4] 414:2[4] 430:2[4] 480:1[3] 560:1[3] 680:1[2]
     DE  320:3[6] 340:3[6] 360:3[6] 375:3[5] 390:2[4] 414:2[4] 430:2[4] 480:2[4] 560:2[4] 680:2[4]
   scrollWidth == clientWidth at all ten widths in both locales. DE is the harder case at
   both ends — 3 fragments from 320 through 375, and still 2 at 680, where EN is down to a
   single unwrapped line. box-decoration-break:clone draws a rule under each fragment; a
   single absolutely positioned bar draws one rule across the whole box instead. Verified
   visually at EN 360, EN 320, DE 360 and DE 320 that every fragment carries its own
   --accent-wash resting rule. Do NOT conclude from the EN column that the technique is
   unnecessary — EN stops fragmenting at 480 and above, so a check on anything wider than a
   phone, in English, sees one clean fragment and looks like proof the clone is dead code.
   It is not: DE fragments at every width measured, up to and including 680.
   METHOD, if you re-do it: Chrome on macOS refuses a window narrower than ~400px, so a
   --window-size=360 screenshot CROPS rather than reflows and will lie to you. This pass used
   Emulation.setDeviceMetricsOverride over CDP in headless against `npm run preview` (a
   same-origin 360px iframe inside a wider page is the other way to get a real 360px layout
   viewport, and agreed exactly on the previous pass). Line counts were themselves
   cross-checked two ways at every width — Range.getClientRects() distinct tops over each
   .ln's contents vs that .ln's content-box height / line-height; both agreed exactly, in
   both locales, at all ten widths.
   SUPERSEDED, kept as history because each figure was real at the time. Under the previous
   strings "Sharp context. Strong governance." / "AI you can prove." and "Präziser Kontext.
   Starke Governance." / "KI, die Sie belegen können." (Chrome 151.0.7922.175 headless):
   360px EN 325x162 → 4 lines (3+1), DE 325x202 → 5 lines (3+2); font-size, line-height and
   the 642/643 clamp crossover were the same then. Fragment counts were
     EN 320:2 340:2 360:1 375:1 390:1 414:1 430:1 480:1 560:1 680:1
     DE 320:3 340:3 360:2 375:2 390:2 414:2 430:2 480:2 560:1 680:1
   and that note's headline claim — "the binding case is now DE ALONE at 360px" — has
   FLIPPED: the current EN accent line wraps at 360 again. Read the old EN column as a
   warning about how fast this table moves under a copy change, not as a baseline. */

/* the practice name + the framework, in the same box */
.mast-fw{padding-top:clamp(1.6rem,3.5vw,2.4rem)}
.mast-fw-brand{font-family:var(--f-display);font-weight:700;letter-spacing:-.022em;font-size:clamp(1.45rem,2.5vw,2rem);line-height:1.1;margin:.65rem 0 .5rem}
.mast-fw-lead{max-width:38rem;font-size:1rem;color:var(--ink-2)}
.mast-rungs{margin-top:clamp(1.4rem,3vw,2.1rem);border-top:1px solid var(--rule)}
/* NO :hover ON .mast-rung, DELIBERATELY. These rows are a non-interactive <div> list — the
   ladder restated as information — and they sit at exactly the point a visitor would try to
   click through to /services. They previously carried `background:var(--paper)` on hover plus
   an --accent colour change on .step, which is this site's own link-hover language (cf.
   .plate:hover, which IS an <a>, and .nav-links a:hover). Pointing that language at a div
   promises navigation the row cannot deliver, so both hover rules were removed in the QA
   pass, along with the two `transition` declarations that existed only to smooth them.
   The route to /services is the .btn-line CTA directly below this list; if these rows should
   navigate, make each one an <a> rather than restoring the hover. */
.mast-rung{
  display:grid;grid-template-columns:minmax(0,13.5rem) minmax(0,1fr);
  gap:.3rem 1.8rem;padding:1.05rem 0;border-bottom:1px solid var(--line);
}
.mast-rung .step{font-family:var(--f-mono);font-size:.78rem;letter-spacing:.12em;text-transform:uppercase;color:var(--ink)}
.mast-rung .out{color:var(--ink-2);font-size:1rem;line-height:1.5}
.mast-rung .tag{display:block;margin-top:.4rem;font-family:var(--f-mono);font-size:.72rem;letter-spacing:.06em;color:var(--ink-3)}
/* --accent-press (7.50:1 on --surface), NOT --accent: these are the tier titles at 11.5px,
   and small accent text is exactly where the tint/wash contrast findings bite. Note
   `.mast-rung .step` is .78rem (12.48px) and stays OUT of the 680px mono floor list;
   `.tag` is .72rem and belongs in it. */
.mast-rung .tag b{color:var(--accent-press);font-weight:500}
.mast-rung .flagtag{margin-top:.55rem}
/* .mast-actions only adds the top margin — the layout comes from .hero-actions above. */
.mast-actions{margin-top:clamp(2rem,4.5vw,3rem)}

/* ---------- landing S1b — thesis strip ---------- */
/* Restored from 018fb8f; the masthead redesign changed ONE declaration and left every
   measured rule below untouched. padding-block on .thesis-inner, not the padding shorthand
   — same reason as .mast-inner above: the element is also a .container, and a shorthand
   with 0 sides wipes its inline gutter (see commit cb3df89). The `auto 1fr` columns
   collapse to one at 900px, where .side flips its accent rule from left border to top
   border.
   THE CHANGED DECLARATION: `border-bottom:1px solid var(--line)` is gone. The strip stays
   on --surface, continuous with the masthead box above it, and the beat is now marked by
   the drawn <hr class="rule"> at the top of the section instead. The bottom hairline had
   nothing left to separate — .profile below is --paper, so the ground itself changes. */
.thesis{background:var(--surface)}
/* columns are `minmax(0,1fr) minmax(0,24rem)`, NOT the restored `auto 1fr`. That original
   was tuned for a one-line .big ("Most advisors hand you a policy they can't implement. I
   do both."): `auto` is shrink-to-fit, so a .big that now runs to three lines claims the
   row and the 1fr column collapses — browser-measured at 1280px, .big took 882px and .side
   was squeezed to a 126px ribbon, ~10 chars per line. This ratio hands .side the 24rem it
   already declares as its max-width (measured at 1280px, both locales: .side 384px, .big
   624px; at 900px and below the row is one column and .side is capped by that same 24rem
   at 384px, browser-measured) and keeps the
   minmax(0,…) floors so a long German compound cannot push either column past the row. */
.thesis-inner{padding-block:clamp(2.4rem,5vw,3.6rem);display:grid;grid-template-columns:minmax(0,1fr) minmax(0,24rem);gap:clamp(1.4rem,4vw,3rem);align-items:center}
.thesis .big{font-family:var(--f-display);font-weight:700;font-size:clamp(1.35rem,2.6vw,1.9rem);line-height:1.25;letter-spacing:-.02em;color:var(--ink)}
/* the emphasis carrier for thesis.bigEm — font-style is reset because the accent, not
   italics, is the emphasis here */
.thesis .big em{font-style:normal;color:var(--accent)}
.thesis .side{font-size:.98rem;color:var(--ink-2);border-left:2px solid var(--accent);padding-left:1.2rem;max-width:24rem}

/* ---------- landing S2 — WHO AM I (#about) ---------- */
/* ONE COLUMN, deliberately. The approved design puts a portrait plate in a left column
   (`.who-grid` + `.portrait` in the mockup); there is no image asset in the repo, and a grey
   plate captioned "portrait placeholder" is a visible defect on a live consulting site. So
   the grid, the plate and the --paper-2 token it needed are NOT shipped, and this beat is a
   single column until a photograph exists. The max-widths sit on the PARTS rather than on a
   wrapper, so adding the column later is markup plus one grid rule, with nothing to undo. */
.profile{background:var(--paper)}
.profile-body{padding-top:1.1rem}
.profile-name{font-family:var(--f-display);font-weight:700;letter-spacing:-.025em;font-size:clamp(1.6rem,2.6vw,2.1rem);line-height:1.1;margin:.9rem 0 .3rem}
/* .profile-role and .profile-lede are SCOPED UNDER .profile-body on purpose: the generic
   `.profile-body p` rule below is (0,1,1) and would otherwise beat a bare single-class rule
   and flatten both of them to body size. */
.profile-body .profile-role{font-family:var(--f-mono);font-size:.76rem;letter-spacing:.1em;text-transform:uppercase;color:var(--accent);margin:0}
/* 1.45rem ceiling and 42ch, NOT the mockup's 1.7rem/26ch: the shipped string is the real
   repositioned lede (the one that used to be hero.lede), roughly twice the length of the
   mockup's placeholder, and at 1.7rem/26ch it ran to nine lines and stopped reading as a
   lede at all. Re-check this pair if content-writer shortens the string. */
.profile-body .profile-lede{
  font-family:var(--f-display);font-weight:600;letter-spacing:-.015em;
  font-size:clamp(1.14rem,1.9vw,1.45rem);line-height:1.34;color:var(--ink);
  margin:clamp(1.4rem,3vw,2rem) 0 1.2rem;max-width:42ch;
}
.profile-body .profile-lede em{font-style:normal;color:var(--accent)}
.profile-body p{font-size:1.02rem;margin-bottom:1rem;max-width:38rem}
.profile-body p:last-of-type{margin-bottom:0}
/* the facts rail. max-width matters here BECAUSE there is no portrait column: without it
   the two-column fact rows stretch the full 1120px and the dd sits a screen away from its
   dt. It is a <dl> with <div class="fact"> wrappers, which HTML5 allows. */
.facts{margin-top:clamp(1.8rem,3.5vw,2.6rem);border-top:1px solid var(--line);max-width:38rem}
.facts .fact{display:grid;grid-template-columns:clamp(7rem,26%,11.5rem) 1fr;gap:.25rem 1.4rem;padding:.8rem 0;border-bottom:1px solid var(--line)}
.facts dt{font-family:var(--f-mono);font-size:.72rem;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);padding-top:.2rem}
.facts dd{color:var(--ink);font-size:.96rem;line-height:1.5}

/* ---------- landing S3 — WHERE TO GO NEXT: the three doors (#explore) ---------- */
/* COMPACT IS THE ONLY TREATMENT. The mockup carried a tall/compact toggle; the tall plates
   (ghost numerals + a description paragraph) lost the pick, so there is no `.plates.compact`
   modifier to switch on — the compact values ARE .plate, and there is no `.plate p` rule
   because no paragraph is rendered. Box 1 explains the framework now, so this beat is
   navigation, not a second pitch.
   The landing page no longer uses .paths/.post-card; .posts stays as /blog's own grid.
   Touch target: the whole .plate IS the <a>, and below 900px each one is a full-width row
   well over 44px tall, so .more needs no min-height of its own. */
.doors{background:var(--surface)}
.doors .section-head{padding-top:1.1rem}
.plates{display:grid;grid-template-columns:repeat(3,1fr);border-top:1px solid var(--rule)}
.plate{
  display:flex;flex-direction:column;
  padding:clamp(1.2rem,2.2vw,1.7rem) clamp(1.2rem,2.2vw,2rem);
  border-bottom:1px solid var(--rule);transition:background .2s ease;
}
/* internal dividers are --line (lighter) so the outer --rule reads as the structure */
.plate + .plate{border-left:1px solid var(--line)}
/* flush the outer edges to the container so the type aligns with the drawn rules */
.plate:first-child{padding-left:0}
.plate:last-child{padding-right:0}
.plate:hover{background:var(--paper)}
.plate-k{font-family:var(--f-mono);font-size:.72rem;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3)}
.plate h3{font-size:1.1rem;line-height:1.22;letter-spacing:-.018em;margin:.55rem 0 .1rem}
.plate .more{margin-top:.7rem;font-size:.88rem;font-weight:600;color:var(--accent);display:inline-flex;gap:.45em;align-items:center}
/* .arw only gets its transition from `.btn .arw`, so a non-button consumer declares it —
   same as .post-card does for the blog grid. */
.plate .more .arw{transition:transform .16s ease}
.plate:hover .more .arw{transform:translateX(3px)}

/* ---------- services ladder ---------- */
.ladder{display:flex;flex-direction:column;gap:1rem}
.rung{
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r);
  padding:clamp(1.3rem,3vw,1.9rem);display:grid;grid-template-columns:auto 1fr auto;gap:1.2rem 1.6rem;align-items:start;
  transition:border-color .18s ease,transform .18s ease,box-shadow .18s ease;
}
.rung:hover{border-color:var(--line-2);transform:translateY(-2px);box-shadow:0 18px 40px -28px rgba(14,18,32,.35)}
.rung .step{font-family:var(--f-mono);font-size:.72rem;letter-spacing:.12em;color:var(--ink-3);text-transform:uppercase;padding-top:.35rem}
.rung h3{margin-bottom:.4rem}
.rung .what{font-size:.98rem;color:var(--ink-2)}
/* overflow-wrap:anywhere is defensive, not cosmetic: at 320px DE this block has 84px of headroom
   (longest token "Shadow-AI-Discovery" 157px vs 241px available, measured 2026-08-20), so it changes
   nothing today. It engages only if a future German compound exceeds the content box — the exact
   regression shape recorded twice in the nav notes below. Precedent: `.article :not(pre) > code`.
   Do NOT add .rung .incl to the 680px mono-floor list; at .86rem it is already above 12px and that
   list would push it down. */
.rung .incl{font-size:.86rem;color:var(--ink-3);margin-top:.55rem;font-family:var(--f-mono);line-height:1.7;overflow-wrap:anywhere}
.rung .alt{justify-self:end;align-self:center}
.rung.flag{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent) inset,0 20px 45px -30px rgba(58,86,240,.5)}
.rung.flag .step{color:var(--accent)}
.flagtag{display:inline-flex;align-items:center;gap:.4em;font-family:var(--f-mono);font-size:.68rem;letter-spacing:.12em;text-transform:uppercase;color:var(--accent);border:1px solid var(--accent);border-radius:999px;padding:.2em .7em}
.badge{font-family:var(--f-mono);font-size:.68rem;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);border:1px solid var(--line-2);border-radius:999px;padding:.2em .7em}
.throughline{display:flex;align-items:center;justify-content:center;gap:.9rem;flex-wrap:wrap;margin-top:1.6rem;font-family:var(--f-mono);font-size:.82rem;letter-spacing:.06em;color:var(--ink-3)}
.throughline b{color:var(--ink)}
.throughline .ar{color:var(--accent)}
/* keeps each arrow with its item when the row wraps (EN below ~388px, DE below ~572px).
   The same .9rem gap as the outer row, so the unwrapped row is pixel-identical. */
.tl-step{display:inline-flex;align-items:center;gap:.9rem}
/* small muted aside under a block — a footnote, deliberately NOT a callout box: no border,
   no tint, no icon. CONTRAST, corrected: --ink-3 (#6b7280) on --paper (#f5f6f8) is 4.47:1,
   not the 4.83:1 first noted here — 4.83:1 is --ink-3 on --surface (#ffffff). At .9rem/400
   (14.4px, browser-measured on /services and /de/services) AA wants 4.5:1, so this pair is
   0.03 short. It is NOT special to .note: --ink-3 on --paper is the site's muted pattern
   (.throughline, .article .meta, .back, .langswitch all sit on it), so darkening one rule
   would buy 0.03 and cost consistency. Recorded as a token-level finding for --ink-3, not
   patched here. */
.note{margin-top:1.4rem;max-width:44rem;font-size:.9rem;line-height:1.6;color:var(--ink-3)}

/* ---------- about ---------- */
.about-inner{display:grid;grid-template-columns:1fr 1.35fr;gap:clamp(1.6rem,5vw,4rem);align-items:start}
.about-card{background:var(--ink);color:var(--on-dark);border-radius:var(--r);padding:clamp(1.6rem,3vw,2.2rem);position:sticky;top:88px}
.about-card .role{font-family:var(--f-mono);font-size:.76rem;color:#9db0ff;letter-spacing:.06em}
.about-card .nm{font-family:var(--f-display);font-weight:700;font-size:1.6rem;color:#fff;margin:.5rem 0 1rem;letter-spacing:-.02em}
.about-card ul{list-style:none;display:flex;flex-direction:column;gap:.7rem}
.about-card li{font-size:.9rem;color:var(--on-dark-mute);display:flex;gap:.6rem;line-height:1.45}
.about-card li::before{content:"→";color:var(--accent);flex-shrink:0}
.about-body p{margin-bottom:1.1rem;font-size:1.05rem}
.about-body p:last-child{margin-bottom:0}
.about-body strong{color:var(--ink);font-weight:600}

/* ---------- post cards: the /blog and /de/blog index grid ---------- */
/* `.paths` is gone from these three selectors (here, and at both breakpoints below): the
   landing page's door cards were its only consumer and they are `.plate`s now. `.posts` is
   untouched, so the blog index is unaffected — verified on /blog and /de/blog. */
.posts{display:grid;grid-template-columns:repeat(3,1fr);gap:1.1rem}
.post-card{display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--line);border-radius:var(--r);padding:1.4rem;transition:border-color .18s,transform .18s,box-shadow .18s}
.post-card:hover{border-color:var(--line-2);transform:translateY(-2px);box-shadow:0 18px 40px -28px rgba(14,18,32,.3)}
.post-card .meta{font-family:var(--f-mono);font-size:.72rem;color:var(--ink-3);letter-spacing:.04em;margin-bottom:.7rem;display:flex;gap:.6rem;align-items:center}
.post-card .cat{color:var(--accent)}
.post-card h3{margin-bottom:.5rem;font-size:1.08rem}
.post-card p{font-size:.92rem;color:var(--ink-2);flex:1}
.post-card .more{margin-top:1rem;font-size:.85rem;font-weight:600;color:var(--accent);display:inline-flex;gap:.4em;align-items:center}
.post-card:hover .more .arw{transform:translateX(3px)}

/* ---------- resources ---------- */
.res{display:grid;grid-template-columns:repeat(2,1fr);gap:1.1rem}
.res-card{display:flex;gap:1.1rem;align-items:flex-start;background:var(--surface);border:1px solid var(--line);border-radius:var(--r);padding:1.4rem;transition:border-color .18s,transform .18s,box-shadow .18s}
/* hover lift only on the linked variant — a .soon card is a <div> with nothing to click */
a.res-card:hover{border-color:var(--line-2);transform:translateY(-2px);box-shadow:0 18px 40px -28px rgba(14,18,32,.3)}
.res-ico{flex-shrink:0;width:42px;height:42px;border-radius:10px;background:var(--accent-tint);color:var(--accent);display:grid;place-items:center;font-family:var(--f-mono);font-size:.7rem;font-weight:500;letter-spacing:.04em}
.res-card h3{font-size:1.02rem;margin-bottom:.3rem}
.res-card p{font-size:.88rem;color:var(--ink-3)}
.res-card .get{margin-top:.7rem;font-size:.82rem;font-weight:600;color:var(--accent);display:inline-flex;gap:.4em;align-items:center}
/* no blanket opacity on .soon: with all four items in preparation it governs the whole
   /resources page, and compositing --ink-3 at 72% over --paper drops body copy to ~2.9:1,
   below AA. The unavailable state is carried by the grey badge, the muted "coming soon"
   label, and the absent link + hover instead — none of which touch text contrast. */
.res-card.soon .res-ico{background:#eef0f3;color:var(--ink-2)}
.res-card.soon .get{color:var(--ink-3)}

/* ---------- landing S4 — HOW TO BOOK (#contact) ---------- */
/* The ONE deliberate dark element above the footer: an inset card on a light section
   (variant A of the mockup; the fully-light variant B lost the pick, which is why
   --shadow-card and --paper-2 are not in :root). ONLY THE SECTION GROUND CHANGED, --ink ->
   --paper: .contact-actions, .contact-facts and .btn-on-dark are untouched and still sit on
   a dark surface, now the card's rather than the section's. */
.contact{background:var(--paper)}
/* padding-block, not the padding shorthand — see the note above .mast-inner.
   max-width narrows the element but leaves .container's inline gutter intact. */
.contact-inner{padding-block:clamp(3.2rem,7vw,5.5rem);max-width:46rem;text-align:center}
/* the card is NOT a .container, so a padding shorthand is correct here. */
.contact-card{
  background:var(--band-dark);color:var(--on-dark);border-radius:var(--r);
  padding:clamp(2.4rem,5vw,3.6rem) clamp(1.4rem,4vw,3rem);
  box-shadow:var(--shadow-lift);
}
/* --accent is 3.35:1 on --band-dark and fails AA — the same finding as the .page-head
   eyebrow, so the same #9db0ff (8.96:1). Both the label and its accent slash. */
.contact-card .sec-k,.contact-card .sec-k i{color:#9db0ff}
.contact-card h2{color:#fff;margin:.9rem 0 0}
.contact-card .lede{color:var(--on-dark-mute);font-size:1.08rem;margin:.9rem auto 1.8rem;max-width:30rem}
.contact-actions{display:flex;flex-wrap:wrap;gap:.8rem;justify-content:center}
/* align-items:center is load-bearing: the 44px touch target below 900px applies to the
   <a> only, which sets the flex line's cross size and stretches the sibling <span>s to
   match — without it their text sits ~12px above the email on the same line. */
.contact-facts{margin-top:2rem;font-family:var(--f-mono);font-size:.8rem;color:var(--on-dark-mute);display:flex;flex-wrap:wrap;align-items:center;gap:.4rem 1.2rem;justify-content:center}
.contact-facts a{color:#fff}
.contact-facts a:hover{color:#a9bbff}

/* ---------- footer ---------- */
/* --band-dark, not --ink: this is a band, not type. Identical value today, so the switch is
   a zero-pixel change on all 22 pages; .page-head deliberately keeps var(--ink) until the
   interior pages get their own pass. */
.footer{background:var(--band-dark);color:var(--on-dark-mute);border-top:1px solid var(--dark-line)}
.footer-inner{padding-block:2.2rem;display:flex;flex-wrap:wrap;gap:1rem;align-items:center;justify-content:space-between;font-size:.85rem}
.footer .brand{color:#fff;font-size:.98rem}
.footer-links{display:flex;flex-wrap:wrap;gap:.7rem 1.4rem}
.footer-links a:hover{color:#fff}
/* legal disclaimer. Colour must stay --on-dark-mute (7.36:1 on --ink) — it carries the
   RDG ancillary framing, so legibility is load-bearing: quieter by size, never by opacity.
   The explicit colour is required, not decorative: the base `p` rule would otherwise paint
   it --ink-2 on --ink. */
.footer-legal{padding-block:1.1rem 2.2rem;border-top:1px solid var(--dark-line);font-size:.8rem;color:var(--on-dark-mute);max-width:38rem}

/* ---------- reveal ---------- */
html.js .reveal{opacity:0;transform:translateY(16px);transition:opacity .6s ease,transform .6s ease}
html.js .reveal.in{opacity:1;transform:none}
/* `transition` carries !important like the other two. It did NOT before the QA pass, and
   that was a real cascade gap: `html.js .rule.reveal` (0,3,1) and
   `html.js .mast-h1 .ln.reveal:nth-child(2)` (0,4,1) are more specific than
   `html.js .reveal` (0,2,1), so a plain `transition:none` here LOST to them and those
   elements kept a live 0.85s transition and a 0.11s delay under reduce. Motion was still
   neutralised in fact — but only incidentally, because opacity and transform were pinned by
   !important and a transition cannot fire on a value that never changes. The guarantee now
   holds by the rule itself rather than by that side effect. */
@media (prefers-reduced-motion:reduce){html.js .reveal{opacity:1!important;transform:none!important;transition:none!important}}

/* --- the masthead's reveal devices. THEY MUST STAY BELOW THE REDUCED-MOTION RULE ABOVE.
   All three of that rule's declarations are !important, which is what lets a (0,2,1)
   selector beat the deeper ones below: opacity:1 and transform:none pin the two animated
   properties so nothing can move, and transition:none removes the durations and the stagger
   delays outright. VERIFIED, not assumed — with prefers-reduced-motion:reduce, toggling the
   .in class off and on again starts ZERO CSSTransitions on .rule.reveal, .ln.reveal,
   .mast-rung.reveal and .plate.reveal (Element.getAnimations() == []), against 1-2 each with
   no-preference as the positive control.
   Without JS there is no html.js, so none of this applies: every rule is full width, both
   headline lines are in place, nothing is hidden. --- */
/* the drawn rule: one class deeper than `.reveal`, so it overrides the fade with a wipe */
html.js .rule.reveal{opacity:1;transform:scaleX(0);transform-origin:left;transition:transform .85s cubic-bezier(.16,.84,.24,1)}
html.js .rule.reveal.in{transform:scaleX(1)}
/* stagger. transition-delay on a .reveal is free — see above. */
html.js .mast-h1 .ln.reveal:nth-child(2){transition-delay:.11s}
html.js .mast-rungs .mast-rung.reveal:nth-child(2){transition-delay:.07s}
html.js .mast-rungs .mast-rung.reveal:nth-child(3){transition-delay:.14s}
html.js .plates .plate.reveal:nth-child(2){transition-delay:.07s}
html.js .plates .plate.reveal:nth-child(3){transition-delay:.14s}

/* ---------- interior pages: /blog, /services, /resources, 404 ---------- */
/* the padding shorthand is correct here: .page-head WRAPS a .container rather than
   being one, so there is no inline gutter of its own to wipe (cf. .hero-inner) */
.page-head{background:var(--ink);color:var(--on-dark);padding:clamp(3rem,6vw,4.6rem) 0 clamp(2.4rem,5vw,3.6rem)}
/* THE CLAMP FLOOR IS LOAD-BEARING, and /de/services is what proves it. Since the
   service-ladder realignment the longest .page-head h1 on the site is the German one,
   "Steuern können Sie nur die KI, die Sie auch gefunden haben." — 59 characters against
   EN's 42 and the 45 of /de/resources, the next longest. Chrome 152 headless, webfonts
   loaded, measured in a real 360px layout viewport (a --window-size screenshot on macOS
   crops instead of reflowing and will tell you it fits):
     360px  DE 4 lines, box 324.81x138.19, 16.81px of the 324.8px column still free;
            EN 3 lines, box 324.81x103.64, 4.06px free — EN is the tighter of the two here.
     320px  DE 4 lines, 284.81x138.19, 13.46px free; EN 3 lines, 5.20px free. No page
            scroll at either width in either locale (documentElement.scrollWidth == clientWidth).
   4.5vw only bites above 711px, so every phone renders this heading at the 2rem/32px floor —
   the floor, not the vw term, is what keeps these strings inside the gutter. Between 711 and
   900 the type scales and the EN heading rides its column: at a 900px viewport it is 40.5px
   type on one line, 830.8px wide in an 836px column (5.19px clear), and it holds that single
   line all the way down to a 830px viewport, where the line is 765.98px in a 766px column —
   0.02px clear. At 829 it breaks to two lines and the pressure is gone. It never overflows;
   it runs out of room and wraps, which is correct behaviour — but 0.02px is the figure to
   respect if that EN string is ever lengthened.
   The hyphens/overflow-wrap note further down covers a DIFFERENT failure — one unbreakable
   German compound ("Datenschutzerklärung"). Neither services headline has a word longer than
   8 characters, so that rule is not what saves them; the clamp floor is. Re-measure at 360
   AND 320 in both locales before lengthening either string. */
.page-head h1{color:#fff;font-size:clamp(2rem,4.5vw,3rem);margin:1rem 0 .8rem}
.page-head p{color:var(--on-dark-mute);max-width:38rem;font-size:1.06rem}
.page-cta{margin-top:clamp(2rem,4vw,3rem)}
.article{max-width:44rem;margin:0 auto;padding-block:clamp(2.6rem,6vw,4rem)}
.article .meta{font-family:var(--f-mono);font-size:.76rem;color:var(--ink-3);letter-spacing:.04em;margin-bottom:1rem;display:flex;gap:.7rem;flex-wrap:wrap}
.article .cat{color:var(--accent)}
.article h1{font-size:clamp(1.9rem,4vw,2.7rem);line-height:1.12;margin-bottom:1.2rem}
.article h2{font-size:1.5rem;margin:2.4rem 0 .8rem}
/* German compounds outgrow the prose column: "Verbraucherstreitbeilegung" is one
   335px word at h2 size, which eats the gutter at 360px and pushes 32px of horizontal
   scroll at 320px. <html lang> is always set, so hyphens:auto breaks it correctly;
   overflow-wrap is the fallback where no hyphenation dictionary ships. Headings only —
   hyphenating inline <code> would corrupt identifiers.

   .page-head h1 is in this list for the same reason, measured on the same page family:
   "Datenschutzerklärung" is 20 characters at the 2rem clamp floor, ~357px against the
   284.8px .container leaves inside its 1.1rem gutter floor at 320px — ~72px of scroll,
   and still ~32px over at 360px. It is the longest unbreakable word any .page-head h1
   carries, which is why the band survived without this until /de/privacy shipped. Check
   it again before putting another German compound in a page-head heading. */
.page-head h1,.article h1,.article h2,.article h3{-webkit-hyphens:auto;hyphens:auto;overflow-wrap:break-word}
.article h3{font-size:1.16rem;margin:1.8rem 0 .5rem}
.article p{font-size:1.08rem;color:var(--ink-2);margin-bottom:1.2rem}
.article ul{margin:0 0 1.2rem 1.3rem}
.article li{margin-bottom:.5rem;color:var(--ink-2)}
.article blockquote{border-left:3px solid var(--accent);padding:.3rem 0 .3rem 1.3rem;margin:1.6rem 0;font-size:1.12rem;color:var(--ink);font-family:var(--f-display);font-weight:600;line-height:1.4}
/* the `:not(pre) >` is load-bearing, not decoration: a bare `.article code` also matches the
   <code> INSIDE a fenced block and paints a tinted, padded pill behind the whole prompt. This
   scopes the pill to inline spans so `.article pre` below needs no counter-rule to undo it. */
/* overflow-wrap is load-bearing too: an identifier with no hyphen or space offers the line
   breaker nothing, so `src/components/ThisIsAVeryLongIdentifierName.astro` in a paragraph
   overflowed the PAGE by 126px starting a line / 131px mid-sentence at 360px — a position
   difference, NOT a locale one: the token and the 324.8px column are identical in EN and DE,
   so both locales overflow by the same amount. `anywhere`, not `break-word`, so it also
   shrinks the min-content width rather than only breaking the visible line. */
.article :not(pre) > code{font-family:var(--f-mono);font-size:.88em;background:var(--accent-tint);color:var(--accent-press);padding:.12em .4em;border-radius:5px;overflow-wrap:anywhere}
/* fenced blocks quote system prompts verbatim — a quoted artifact, so it echoes the accent
   spine of .article blockquote above rather than adding a second kind of panel.
   overflow-x is REQUIRED and this rule is the ONLY thing supplying it: Shiki used to inline
   `overflow-x:auto` on every <pre>, and markdown.syntaxHighlight:false (astro.config.mjs)
   removed that. Drop it and an ~80-char prompt line scrolls the PAGE, not the block.
   Size is .84rem/13.4px so a 72-char line still fits the 44rem column unscrolled; it is
   above .75rem, so it deliberately stays OUT of the 680px mono floor list below — that rule
   is a floor, and adding this would lower it to 12px. */
/* the `pre code` half of this selector is required, not belt-and-braces: markdown emits
   <pre><code>, the visible glyphs live in the <code>, and the UA sheet DECLARES
   code{font-family:monospace} — a declared value beats the one inherited from <pre>. Without
   it the block renders in Menlo while the inline pills render in JetBrains Mono on the same
   page. Verify by computed style on the <code>, not on the <pre>: the <pre> lies. */
.article pre,.article pre code{font-family:var(--f-mono)}
.article pre{font-size:.84rem;line-height:1.62;color:var(--ink-2);background:var(--surface);border:1px solid var(--line);border-left:3px solid var(--accent);border-radius:0 var(--r-sm) var(--r-sm) 0;padding:1rem 1.15rem;margin:1.6rem 0;overflow-x:auto}
.article .lead{font-size:1.2rem;color:var(--ink);line-height:1.55}
.back{display:inline-flex;align-items:center;gap:.5em;font-family:var(--f-mono);font-size:.8rem;color:var(--ink-3);margin-bottom:1.4rem}
.back:hover{color:var(--accent)}

/* ---------- responsive ---------- */
@media (max-width:900px){
  /* THE NAV ROW, RE-MEASURED FOR THE GRADUATED CTA (service-ladder realignment, 2026-08-31 —
     nav.cta went from "Book an assessment" / "Assessment anfragen" to the smallest step,
     "Start with a two-day discovery" / "Mit einer zweitägigen Discovery starten"). The label
     is the longest string this row has ever carried, and it is the row's binding child.

     THE ROW'S FIRST FAILURE MODE IS THE BRAND WRAPPING, NOT HORIZONTAL OVERFLOW, and it
     happens ~60px earlier. Everything in the row except .brand is unshrinkable — the three
     nav labels are single words and .btn is white-space:nowrap — so .brand is the only flex
     item with give. One pixel below the row's natural width it shrinks toward its 62.5px
     min-content and "Martin Hübner" stacks over two lines (26.88px -> 53.75px) inside a row
     whose height is a fixed 66px. Overflow never gets a chance to happen. Measure the wrap.

     Chrome 152 headless, real self-hosted webfonts, document.fonts.ready awaited. Natural
     row width (sum of .brand + .nav-links + .nav-cta) is 721.06px EN / 794.25px DE, so the
     row is clean down to a .nav-inner CONTENT width of 722 (EN) / 795 (DE) and the brand is
     two lines at 721 / 794. The desktop row only ever renders at >=901px — at 900 the burger
     takes over and .nav-cta .btn goes display:none — so the binding case is a 901px viewport,
     where the content width is 837px (901 minus the 32px gutter cap each side). SLACK: 115px
     EN / 42px DE. Verified at 901 in both locales: no overflow, row 66px, brand one line,
     button one line at 50.6px. DE is the binding locale by a wide margin — 39 chars to EN's
     30, and the CTA cost +163px DE / +74px EN against the labels above it.

     BEFORE YOU LENGTHEN nav.cta AGAIN: the DE budget is 42px, which is about five average
     characters of this button's text (39 chars occupy 291.8px of glyphs, i.e. ~7.5px each).
     Measured, not derived: appending 6 chars at a 901px viewport takes the button from
     336.4px to 372.5px and the brand still holds one line; appending 12 takes it to 420.4px
     and the brand wraps. Re-measure, do not estimate.

     THE PREVIOUS FIGURES IN THIS NOTE (694/767 first overflow, 141/68 slack) WERE WRONG IN
     BOTH DIRECTIONS and are kept here so the mistake is not repeated. That pass compared
     scrollWidth against the width it was setting while .nav-inner still carried its 32px
     gutter, so it converged on min-content + padding-left — exactly 32px above the real
     content-width threshold — and, comparing only widths, it could not see the brand wrap
     that lands first. Understated the threshold, overstated the DE headroom.
     History, since each figure was a real constraint at the time (all measured under the old
     scrollWidth method, so read them as relative, not absolute): 620/636 with the short
     "Book an assessment" CTA and the markless brand; 716/732 with the "/ai-inventory" mark;
     ~700/~716 estimated for "/ai-gov"; ~724/~740 before the brand name became "Martin Hübner";
     ~860/~850 when the nav carried five links.
     900px is kept deliberately: DE's 42px still clears it, and a third scale would cost more
     than it buys.
     METHOD, if you re-do it: at a 1280px viewport (desktop nav rules active, gutter at its
     32px cap) set .nav-inner{max-width:none} and shrink its width one pixel at a time. That
     lets the flex children shrink the way they really do, which summing their min-content
     widths does not. Take the first width where EITHER max(child.getBoundingClientRect().right)
     passes the content-box right edge OR .brand's height passes one line-height. Never use
     scrollWidth: it includes padding-left and will shift every number by the gutter. Load the
     webfonts first; fallback metrics read ~16px narrower per row and will flatter the result. */
  .nav-links{display:none}
  .nav-links.open{display:flex;position:absolute;top:100%;left:0;right:0;flex-direction:column;align-items:stretch;gap:0;background:var(--surface);border-bottom:1px solid var(--line);padding:.5rem 0;box-shadow:0 24px 40px -30px rgba(14,18,32,.45)}
  .nav-links.open a{padding:.9rem clamp(1.1rem,4vw,2rem);text-align:left}
  /* the burger is the only nav control below 900px, so it is the one target that must not
     miss house rule 3's 44px. Its base box is the 22px svg plus .4rem padding = 34.8x34.8
     (browser-measured), so the min-* pair is what lifts it; align/justify keep the icon
     centred inside the larger hit area. It cannot change the row height — .nav-inner is a
     fixed 66px — and it widens the button by 9.2px, which at 320px leaves the brand's right
     edge (253.6px) and the burger's left edge (258px) ~4px apart. Re-measure that gap if
     either the brand mark or the icon size changes. */
  .nav-toggle{display:inline-flex;align-items:center;justify-content:center;min-width:44px;min-height:44px}
  .nav-cta .btn,.nav-bar-lang{display:none}
  .nav-links.open .nav-menu-cta{display:block;color:var(--accent);font-weight:600}
  .nav-links.open .nav-menu-lang{display:block;border-top:1px solid var(--line);margin-top:.35rem;padding-top:.35rem}
  .nav-links.open .langswitch{padding:0 clamp(.2rem,1.5vw,1.1rem)}
  .nav-links.open .langswitch a{min-width:44px;justify-content:center;padding:.7rem .5rem;text-align:center}
  /* touch targets: give the small text links a ~44px tall hit area */
  .brand{min-height:44px}
  .footer-links a{display:inline-flex;align-items:center;min-height:44px}
  .contact-facts a{display:inline-flex;align-items:center;min-height:44px}
  .thesis-inner{grid-template-columns:1fr;gap:1.2rem}
  .thesis .side{border-left:0;border-top:2px solid var(--accent);padding-left:0;padding-top:1rem}
  .about-inner{grid-template-columns:1fr}
  .about-card{position:static}
  .posts{grid-template-columns:1fr 1fr}
  /* landing masthead: the 22ch measure is a desktop device — below 900px the column is
     already narrower than the headline wants, and capping it costs a line. */
  .mast-h1{max-width:none}
  .mast-rung{grid-template-columns:minmax(0,11rem) minmax(0,1fr)}
  /* the doors stack. The flush first/last padding has to be reset with them, or the top and
     bottom rows lose their inline padding while the middle one keeps it. */
  .plates{grid-template-columns:1fr}
  .plate,.plate:first-child,.plate:last-child{padding:clamp(1.2rem,3vw,1.6rem) 0;border-left:0}
}
@media (max-width:680px){
  /* keep the smallest mono labels at 12px+ so they stay legible on phones. This rule is a
     FLOOR, not a cap — only add selectors whose base size is below .75rem. .contact-facts is
     deliberately excluded (the landing-simplification spec asked for it): its .8rem is a fixed
     12.8px with no vw term, so listing it here would lower it to 12px rather than raise it. */
  .eyebrow,.rung .step,.post-card .meta,.flagtag,.badge,.res-ico,
  .facts dt,.plate-k,.mast-rung .tag,.sec-k{font-size:.75rem}
  /* NO `.brand .mk` DOWNSCALE HERE ANY MORE, and the row it protected is now roomy.
     History, because this was the tightest measurement on the site: the mark
     ("/ai-inventory", 13 mono chars ≈ 131px at 1.05rem) was the widest thing in the
     collapsed nav row, and brand + burger came to ~278px against the 284.8px a 320px
     viewport leaves inside .container's 1.1rem gutter floor — ~7px, inside measurement
     error. The fix was to shrink the mark to .85em, which bought ~26px of real slack.
     The mark is GONE from Nav.astro, so the brand is the wordmark alone.
     RE-MEASURED at 320px (Chrome 141 headless, webfonts loaded, both locales — the row is
     identical in EN and DE because the brand is the only thing left in it): brand 121px
     wide, right edge at 139px; burger 44x44 at left 258 / right 302; 120px of clear ground
     between them, against the ~7px the marked row had. The constraint this rule existed for
     cannot recur unless a mark comes back — if one does, restore this rule AND re-measure
     both notes rather than trusting the old figures. */
  .rung{grid-template-columns:1fr;gap:.5rem}
  .rung .alt{justify-self:start;margin-top:.8rem}
  .posts,.res{grid-template-columns:1fr}
  /* the masthead rung list drops to one column: the 11rem label column above leaves the
     outcome ~150px at 360px, which is ~20 characters a line. */
  .mast-rung{grid-template-columns:1fr;gap:.35rem;padding:1.1rem 0}
  .facts .fact{grid-template-columns:1fr;gap:.15rem}
  /* .btn is white-space:nowrap, so a long CTA cannot shrink: at 320px the DE primary
     ("Readiness-Assessment anfragen →") is 310px in a 285px box — clipped flat by
     .mast{overflow:hidden} on the landing page (it was .hero before the masthead
     redesign; the overflow:hidden moved with the section), and 3-8px of page scroll on
     /services, /resources and blog posts. Wrapping is the only locale-agnostic fix.
     flex-basis must be 100%, not 0: with basis 0 the collapsed min-content lets both
     buttons share one line and the EN labels wrap to three lines at 320-425px. */
  .hero-actions .btn{flex:1 1 100%;justify-content:center;white-space:normal;text-align:center}
  /* standalone CTAs are not flex items — they only need permission to wrap */
  .page-cta .btn,.article .btn{white-space:normal;text-align:center}
}

/* Astro: auto-style the first paragraph of a markdown post as the lead */
.article-body > p:first-child{font-size:1.2rem;color:var(--ink);line-height:1.55}

/* ---------- language switch ---------- */
.langswitch{display:inline-flex;align-items:center;gap:.4rem;font-family:var(--f-mono);font-size:.8rem;color:var(--ink-3)}
.langswitch a{color:var(--ink-3);padding:.15rem .15rem;transition:color .15s}
.langswitch a:hover{color:var(--ink)}
.langswitch a.on{color:var(--accent);font-weight:500}
.langswitch .sep{color:var(--line-2)}
