/* fonts.css — @font-face declarations only. Depends on nothing (loaded before
   variables.css). Self-hosted, not a CDN request: files live in assets/fonts/,
   sourced from Fontsource's build of Newsreader / IBM Plex Sans / IBM Plex Mono
   (Google Fonts originals, SIL OFL 1.1 — license text alongside each family in
   assets/fonts/). Picked 2026-08-31 (IS-9) via a side-by-side comparison of real
   page content across all 3 roles, in both the light and dark token sets.

   Exactly the weights the site actually uses — see DEPENDENCY_MATRIX.md §5 for
   the CSS audit that produced this list. woff2 only: universal on any browser
   this site already assumes (details/summary, clamp(), 100svh), so no woff
   fallback is carried. font-display: swap — the page never blocks on these;
   system-stack fallback text is visible immediately and swaps in-place.

   LATIN-EXT (added 2026-09-08, IS-37). The original five files are Fontsource's
   *latin* subset, which does not contain ő ű Ő Ű — precisely and only the four
   characters unique to Hungarian. Every other Hungarian accent (á é í ó ö ú ü)
   is in the latin subset, so Hungarian text rendered correctly except for those
   four, which silently fell back to a system font: the same word in two
   different typefaces, which is what "garbled" looked like.

   Fix: a second @font-face per family/weight pointing at the latin-ext subset,
   restricted with unicode-range and declared AFTER the base face. Where the
   ranges overlap the later declaration wins, so ő/ű come from latin-ext; the
   base face is deliberately left unrestricted so that any character in neither
   range still has a font to fall back on. Cost: one extra ~16KB file per face,
   fetched only if the page actually contains a character in that range — so an
   English-only visitor downloads none of them. */

@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/newsreader-600.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/ibm-plex-sans-400.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/ibm-plex-sans-600.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/fonts/ibm-plex-sans-700.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/ibm-plex-mono-400.woff2') format('woff2');
}


/* ---------------------------------------------------------------------------
   Latin Extended-A/B — carries ő ű Ő Ű. Declared after the base faces on
   purpose: for codepoints in both ranges the later @font-face wins.
   --------------------------------------------------------------------------- */

@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/newsreader-latin-ext-600.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;
}

@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/ibm-plex-sans-latin-ext-400.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;
}

@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/ibm-plex-sans-latin-ext-600.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;
}

@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/fonts/ibm-plex-sans-latin-ext-700.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;
}

@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/ibm-plex-mono-latin-ext-400.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;
}
/* variables.css — design tokens. Loaded first; every other stylesheet reads from these. */
:root {
  /* Color — neutral white-gray is the primary/default surface (Aug 2026 redesign:
     moved off the original dark-first theme for a lighter, cleaner, services-focused
     read). All text/background pairs below are WCAG 2.1 AA-verified (>=4.5:1 for body
     text, checked with the real relative-luminance formula, not eyeballed) — see
     DEPENDENCY_MATRIX.md change log for the contrast figures. */
  --color-bg: #F7F8F9;
  --color-surface: #EDEFF1;
  --color-surface-raised: #FFFFFF;
  --color-border: #DBDEE2;
  --color-text: #1A1B1E;
  --color-text-muted: #565A61;
  --color-accent: #047857;       /* emerald — clears AA on light surfaces (5.16:1 on bg, 4.76:1 on surface) */
  --color-accent-strong: #036B4D; /* hover/active state, darker still (6.14:1 on bg) */
  --color-accent-ink: #047857;   /* kept as an alias for compatibility with the dark variant below */

  /* Type — serif for display (classical nod), a humanist sans for body/UI, a
     technical mono for the typed-loop line + card labels. Decided 2026-08-31
     (IS-9) — Newsreader / IBM Plex Sans / IBM Plex Mono, self-hosted (see
     fonts.css + assets/fonts/), still respecting the neurodivergent-friendly
     principle below: serif stays reserved for large display sizes only, body
     stays a plain, familiar humanist sans rather than anything novelty. Each
     stack keeps the previous system fallback last, so a font-load failure
     degrades to exactly what shipped before this decision. */
  --font-display: "Newsreader", ui-serif, Georgia, "Times New Roman", serif;
  --font-body: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --step--1: clamp(0.83rem, 0.8rem + 0.15vw, 0.9rem);
  --step-0:  clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
  --step-1:  clamp(1.25rem, 1.15rem + 0.5vw, 1.5rem);
  --step-2:  clamp(1.6rem, 1.4rem + 1vw, 2.1rem);
  --step-3:  clamp(2.1rem, 1.8rem + 1.5vw, 3rem);
  --step-4:  clamp(2.8rem, 2.2rem + 3vw, 4.5rem);

  /* Spacing scale — generous by default; wide breathing room around blocks
     of text is a deliberate cognitive-load reduction, not just aesthetics. */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 1rem;
  --space-4: 1.5rem;
  --space-5: 2.5rem;
  --space-6: 4rem;
  --space-7: 6rem;
  --space-8: 9rem;

  --header-h: 4.5rem;          /* fixed (non-fluid) header height — lets the hero calc a precise
                                  100svh-minus-header fill that's consistent at any viewport size. */
  --radius: 2px;               /* sharp, not soft — matches "modern tech-forward, sharper type" direction */
  --max-width: 72rem;
  --border-hairline: 1px solid var(--color-border);
}

/* Dark variant (IS-16). Three states: (1) no preference set anywhere — the
   site follows the OS/browser prefers-color-scheme; (2) the on-page toggle
   (main.js) sets data-theme="dark" or "light" explicitly and persists the
   choice in localStorage, which always wins over the OS preference. This was
   the site's original default theme (pre-Aug-2026 redesign); values verified
   WCAG-AA/AAA via relative-luminance calc — see DEPENDENCY_MATRIX.md change
   log for the figures (all >=6.7:1, most >=9:1). */
[data-theme="dark"] {
  --color-bg: #0A0B0D;
  --color-surface: #15171B;
  --color-surface-raised: #1D2025;
  --color-border: #2A2D33;
  --color-text: #F2F0EC;
  --color-text-muted: #9B968E;
  --color-accent: #E8A33D;
  --color-accent-strong: #F5B85A;
}

/* Auto-dark for visitors with no explicit on-page choice: mirrors the OS/
   browser preference. :not([data-theme="light"]) means an explicit "light"
   toggle choice always overrides this, same as [data-theme="dark"] above
   always applies regardless of OS preference. No JS required for this tier. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --color-bg: #0A0B0D;
    --color-surface: #15171B;
    --color-surface-raised: #1D2025;
    --color-border: #2A2D33;
    --color-text: #F2F0EC;
    --color-text-muted: #9B968E;
    --color-accent: #E8A33D;
    --color-accent-strong: #F5B85A;
  }
}
/* base.css — reset + base element styles. Depends on variables.css. */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
/* Smooth scroll only when the user hasn't asked the OS to reduce motion. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}
body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}
img, svg, video { max-width: 100%; display: block; }
h1, h2, h3, h4 {
  font-family: var(--font-display);
  line-height: 1.15;
  margin: 0 0 var(--space-3);
  font-weight: 600;
}
h1 { font-size: var(--step-4); }
h2 { font-size: var(--step-3); }
h3 { font-size: var(--step-2); }
p { margin: 0 0 var(--space-3); color: var(--color-text); }
a { color: var(--color-accent); text-decoration: none; }
a:hover, a:focus-visible { text-decoration: underline; }
ul, ol { padding-left: 1.2em; }
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
/* layout.css — section/grid primitives. Depends on variables.css. */
.wrap {
  max-width: var(--max-width);
  margin-inline: auto;
  padding-inline: var(--space-4);
}
.section {
  padding-block: var(--space-7);
  border-bottom: var(--border-hairline);
}
.section--tight { padding-block: var(--space-5); }
.grid {
  display: grid;
  gap: var(--space-4);
}
.grid--services {
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
}
.stack { display: flex; flex-direction: column; gap: var(--space-3); }
.cluster { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
@media (min-width: 60rem) {
  .split { display: grid; grid-template-columns: 1.1fr 0.9fr; gap: var(--space-6); align-items: start; }
}
/* components.css — nav, buttons, cards, the interpunct motif. Depends on variables.css, base.css. */

/* Decorative interpunct dividers — functional UI separators (typeline, footer
   credit line, section rule). Not the brand mark itself — see .brand__mark
   below (IS-10, 2026-08-31) for the header logo mark. */
.interpunct-divider {
  display: inline-block;
  margin-inline: var(--space-2);
  color: var(--color-accent);
}
.interpunct-rule {
  border: none;
  border-top: var(--border-hairline);
  position: relative;
  margin-block: var(--space-6);
}
.interpunct-rule::after {
  content: "·";
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  background: var(--color-bg);
  color: var(--color-accent);
  font-size: var(--step-2);
  padding-inline: var(--space-2);
}

/* Site header / nav — styled like the solid CTA button: solid emerald bar,
   light text (same background/foreground pairing as .btn--solid) instead of
   a translucent bar over the page background. */
.site-header {
  border-bottom: 1px solid var(--color-accent-strong);
  position: sticky;
  top: 0;
  background: var(--color-accent);
  z-index: 10;
  min-height: var(--header-h);
}
.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: var(--space-3);
}
.brand {
  font-family: var(--font-display);
  font-size: var(--step-1);
  color: var(--color-bg);
  font-weight: 600;
  letter-spacing: 0.01em;
}
/* Brand mark (IS-10, 2026-08-31) — replaces the old literal "·" character.
   Source: assets/img/interpunct-mark.svg (single evenodd path, true transparent
   hole) so it recolors cleanly per context via currentColor. Sized/colored
   here to match .brand__sub's off-white-on-emerald treatment. */
.brand__mark {
  display: inline-flex;
  width: 0.85em;
  height: 0.85em;
  margin-inline: 0.35em;
  vertical-align: -0.08em;
  color: var(--color-bg);
}
.brand__mark svg { display: block; width: 100%; height: 100%; }
.brand__sub {
  font-size: 0.55em;
  vertical-align: 2px;
  color: color-mix(in srgb, var(--color-bg) 75%, transparent);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
/* Theme toggle (IS-16, 2026-08-31). Sits inside <nav>, left of the mobile
   disclosure, so nav becomes an explicit flex row (it only ever had one
   child before this). Hidden via the HTML `hidden` attribute until main.js
   confirms it's ready to handle clicks — a toggle that does nothing on a
   JS-off visit would be worse than no toggle (DEPENDENCY_MATRIX.md §4).
   Icon swap uses the exact same [data-theme]/prefers-color-scheme logic as
   variables.css's auto-dark block, so the icon always matches the theme
   actually in effect, including for OS-dark visitors who never clicked
   anything. Sized/bordered to match .nav-toggle's own treatment. */
nav[aria-label="Primary"] {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.2em;
  height: 2.2em;
  background: none;
  border: 1px solid color-mix(in srgb, var(--color-bg) 40%, transparent);
  border-radius: var(--radius);
  color: var(--color-bg);
  cursor: pointer;
}
.theme-toggle__icon {
  width: 1.1em;
  height: 1.1em;
}
.theme-toggle__icon--sun { display: none; }
[data-theme="dark"] .theme-toggle__icon--moon { display: none; }
[data-theme="dark"] .theme-toggle__icon--sun { display: inline; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle__icon--moon { display: none; }
  :root:not([data-theme="light"]) .theme-toggle__icon--sun { display: inline; }
}

/* Nav collapse (IS-18, 2026-08-30): below 40rem the brand + all 4 nav
   links no longer reliably fit one row (confirmed on iPhone 12, 24.4rem
   wide) — even after dropping the subtitle, which was the previous fix
   for this and is superseded by this one (see change log). Rather than
   keep guessing a width for the brand text to shrink at, the nav itself
   collapses behind a disclosure once real viewport width drops below the
   threshold — the alternative considered was shrinking/abbreviating the
   logo, rejected because it doesn't scale cleanly across the very wide
   range of real phone widths and ties this fix to IS-10's still-undecided
   mark design. Built as a native <details>/<summary> disclosure rather
   than a JS-toggled button specifically so it needs zero JavaScript —
   DEPENDENCY_MATRIX.md §4 requires the site stay fully usable with JS
   disabled, and a JS-only toggle would have made the nav unreachable on
   narrow screens with JS off. main.js's old nav-toggle listener (from the
   original scaffold, never actually wired to markup) is removed as dead
   code — see change log. */
.nav-toggle {
  display: none;
  flex-direction: column;
  gap: 4px;
  background: none;
  border: 1px solid color-mix(in srgb, var(--color-bg) 40%, transparent);
  border-radius: var(--radius);
  padding: 0.55em 0.7em;
  cursor: pointer;
  list-style: none;
  width: fit-content;
}
.nav-toggle::-webkit-details-marker { display: none; }
.nav-toggle::marker { content: ""; }
.nav-links {
  display: flex;
  gap: var(--space-4);
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: var(--step--1);
}
.nav-links a { color: color-mix(in srgb, var(--color-bg) 80%, transparent); }
.nav-links a:hover { color: var(--color-bg); }
.nav-toggle__bar {
  display: block;
  width: 1.1em;
  height: 2px;
  background: var(--color-bg);
  border-radius: 1px;
}

@media (min-width: 40.01rem) {
  /* <details> hides its non-summary content by default unless [open] is set.
     Desktop never opens or closes the disclosure — it just needs the list
     visible unconditionally, so this overrides that native hiding. */
  .nav-details:not([open]) > .nav-links { display: flex !important; }

  /* Nav links read before the controls: links on the left, then the language
     and theme toggles at the far right. The buttons come first in the markup
     because on mobile the burger belongs rightmost, so the desktop order is
     corrected here rather than by duplicating markup. */
  .site-header nav { display: flex; align-items: center; gap: var(--space-3); }
  .nav-details { order: -1; }

  /* ...and as of Chrome 131 that override alone stopped working, which is why
     the desktop nav went invisible (IS-37). Closed <details> content now lives
     inside a ::details-content pseudo-element carrying
     content-visibility: hidden — a property `display` on the child cannot
     defeat, so the <ul> laid out at zero width with nothing painted. Browsers
     that don't implement ::details-content ignore this rule harmlessly and are
     still covered by the display override above. */
  .nav-details::details-content { content-visibility: visible; }
}

@media (max-width: 40rem) {
  .site-header .wrap { position: relative; }
  .nav-toggle { display: inline-flex; }
  .nav-links {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    gap: 0;
    background: var(--color-accent);
    border-top: 1px solid var(--color-accent-strong);
    padding: var(--space-2) var(--space-4) var(--space-3);
  }
  .nav-details[open] > .nav-links { display: flex; }
  .nav-links li { padding-block: var(--space-2); }
}

/* Buttons */
.btn {
  display: inline-block;
  border: 1px solid var(--color-accent);
  color: var(--color-accent);
  padding: 0.7em 1.4em;
  border-radius: var(--radius);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.02em;
}
.btn:hover, .btn:focus-visible {
  background: var(--color-accent);
  color: var(--color-bg);
  text-decoration: none;
}
.btn--solid {
  background: var(--color-accent);
  color: var(--color-bg);
}
.btn--solid:hover { background: var(--color-accent-strong); }

/* Language toggle (IS-37). Deliberately a sibling of the theme toggle rather
   than an item inside the nav list: switching language is not "navigation to
   another page of this site", and burying it in a collapsed disclosure made it
   effectively undiscoverable. Matches .theme-toggle's box so the pair reads as
   one control cluster. */
.lang-toggle {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 700;
  letter-spacing: 0.04em;
  line-height: 1;
  color: var(--color-bg);
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--color-bg) 45%, transparent);
  border-radius: 2px;
  padding: 0.45rem 0.55rem;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
}
.lang-toggle:hover {
  background: color-mix(in srgb, var(--color-bg) 14%, transparent);
  border-color: var(--color-bg);
}

/* Cards (services grid) */
.card {
  background: var(--color-surface);
  border: var(--border-hairline);
  border-radius: var(--radius);
  padding: var(--space-4);
}
.card__label {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--color-accent);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  display: block;
  margin-bottom: var(--space-2);
}
.card__achievements {
  list-style: none;
  padding: 0;
  margin: var(--space-3) 0 0;
  font-size: var(--step--1);
  color: var(--color-text-muted);
}
.card__achievements li {
  padding-left: 1.1em;
  position: relative;
  margin-bottom: var(--space-2);
}
.card__achievements li::before {
  content: "·";
  color: var(--color-accent);
  position: absolute;
  left: 0;
  font-weight: 700;
}

/* Product rows inside a pillar card (IS-33): name / one-sentence catchphrase /
   quote link, stacked. Kept deliberately plain — the card is the unit of
   emphasis, the products are its contents. */
.card__products {
  list-style: none;
  padding: 0;
  margin: var(--space-3) 0 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.card__products li {
  padding-top: var(--space-3);
  border-top: var(--border-hairline);
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.card__products strong {
  font-weight: 700;
}
/* A product name links to its own page once that page exists (IS-35). Until
   all six exist, some names are links and some aren't — intentional, and
   invisible to a reader who isn't diffing the rows. */
.card__products strong a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid var(--color-accent);
}
.card__products strong a:hover { color: var(--color-accent); }
.card__products span {
  font-size: var(--step--1);
  color: var(--color-text-muted);
}
.card__products a {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--color-accent);
  text-decoration: none;
  align-self: flex-start;
}
.card__products a:hover { text-decoration: underline; }
.card__products a::after { content: " →"; }

/* ---------------------------------------------------------------------------
   PRODUCT PAGES (IS-35) — assessment.html and the five product pages that
   follow it. Everything here is shared by all of them; nothing is specific to
   one product, so a new product page needs no new CSS.
   --------------------------------------------------------------------------- */

/* "All services" breadcrumb back to the homepage catalogue. */
.back-link {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--color-text-muted);
  text-decoration: none;
  display: block;
  margin-bottom: var(--space-3);
}
.back-link::before { content: "← "; }
.back-link:hover { color: var(--color-accent); }

.product-hero h1 { margin-top: var(--space-2); }
.lede {
  font-size: var(--step-1);
  max-width: 38rem;
  color: var(--color-text-muted);
}

/* Duration / effort / price / location strip under the product headline.
   Deliberately terse — a buyer should be able to qualify themselves out in
   about three seconds without reading a paragraph. */
.facts {
  list-style: none;
  padding: 0;
  margin: var(--space-4) 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--color-text-muted);
}
.facts li + li::before {
  content: "·";
  color: var(--color-accent);
  font-weight: 700;
  margin-right: var(--space-3);
}

/* Numbered engagement process. Uses a CSS counter rather than the native
   list marker so the number can carry the accent colour and the mono face. */
.process {
  list-style: none;
  counter-reset: step;
  padding: 0;
  margin: var(--space-4) 0 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.process li {
  counter-increment: step;
  padding-left: 2.5rem;
  position: relative;
}
.process li::before {
  content: counter(step, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 0.1em;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--color-accent);
  font-weight: 700;
}
.process strong { display: block; }
.process span {
  display: block;
  margin-top: 0.25rem;
  color: var(--color-text-muted);
}

/* Two-column card grid — "who it's for", deliverables, the responsibilities
   pair. Collapses to one column well before the services grid does, because
   these cards carry sentences rather than labels. */
.grid--two {
  grid-template-columns: repeat(auto-fit, minmax(21rem, 1fr));
}

/* Package list reuses .card__products' row rhythm outside a card. */
.packages { margin-bottom: var(--space-4); }
.packages li:first-child { border-top: none; padding-top: 0; }

/* Services index (IS-38). Rows rather than cards: this page exists to be
   scanned and compared, and the facts strip under each service is the reason
   to come here instead of reading the homepage. */
.service-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-top: var(--space-4);
}
.service-list__item {
  padding-top: var(--space-4);
  border-top: var(--border-hairline);
}
.service-list__item:first-child { border-top: none; padding-top: var(--space-2); }
.service-list__item h3 { margin-bottom: var(--space-2); }
.service-list__item h3 a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid var(--color-accent);
}
.service-list__item h3 a:hover { color: var(--color-accent); }
.service-list__item p { max-width: 46rem; }
.service-list__item .facts { margin-top: var(--space-3); }
.service-list__item .cluster { margin-top: var(--space-3); }

/* Footer */
.site-footer {
  padding-block: var(--space-5);
  color: var(--color-text-muted);
  font-size: var(--step--1);
}
.site-footer a { color: var(--color-text-muted); }
.site-footer a:hover { color: var(--color-accent); }

/* Hero typed-loop line. The static text inside #typeline-text (see index.html)
   is the no-JS fallback and must always be a complete, readable sentence on
   its own — main.js clears and re-drives it only if it actually runs. */
/* Hero fills exactly one screen (viewport minus the fixed-height header) and
   centers its content, regardless of screen size or orientation — 100svh
   (falls back to 100vh) tracks the actual visible viewport on mobile instead
   of jumping when browser chrome shows/hides. */
.hero {
  position: relative;
  overflow: hidden; /* safety clip if a heading ever can't be shrunk to fit (see main.js) */
  /* Cancel the generic .section padding-block — vertical spacing here is
     fully owned by .hero .wrap's own (scalable) padding below, since the
     section as a whole now has to fit exactly one screen. */
  padding-block: 0;
  min-height: calc(100vh - var(--header-h));
  min-height: calc(100svh - var(--header-h));
  display: flex;
  align-items: center;
  justify-content: center;
}
.hero .wrap {
  width: 100%;
  padding-block: calc(var(--space-6) * var(--hero-scale, 1));
  text-align: center;
}
/* --hero-scale is 1 (i.e. normal size) until main.js measures the actual
   header height and available viewport space on this device and dials it
   down — only as far as needed — so the heading, typeline and CTA always
   fit one screen without guessing at breakpoints. No-JS fallback: normal
   full size, same as the rest of the page. */
/* The heading has its own scale variable, separate from --hero-scale below:
   it has to shrink much further on narrow screens to stay on one line than
   the vertical rhythm (padding/typeline) ever needs to for short screens —
   sharing one variable between both made the typeline shrink to the point
   of illegibility on narrow-but-tall phones. See main.js. */
.hero h1 {
  font-size: calc(var(--step-4) * var(--hero-h1-scale, 1));
  line-height: 1.1;
  text-transform: uppercase;
}
/* Forced to a single line only once main.js is confirmed running (it's the
   one measuring the shrink needed to make that true) — the no-JS fallback
   below lets it wrap normally rather than risk clipped/overflowing text. */
.hero.is-fitted h1 {
  white-space: nowrap;
}
.hero .typeline {
  font-size: calc(var(--step-1) * var(--hero-scale, 1));
  margin-top: calc(var(--space-3) * var(--hero-scale, 1));
}
/* CTA docks to the hero's bottom-right corner at a fixed inset, independent
   of heading/typeline length, screen size, or orientation. */
.hero__cta {
  position: absolute;
  right: var(--space-4);
  bottom: var(--space-4);
}
.typeline {
  font-family: var(--font-mono);
  font-size: var(--step-1);
  letter-spacing: 0.02em;
  color: var(--color-accent);
  margin: var(--space-3) 0 0;
}
.typeline .cursor {
  display: inline-block;
  width: 2px;
  height: 0.9em;
  margin-left: 2px;
  vertical-align: -0.1em;
  background: var(--color-accent);
}
/* Blink only once JS has actually taken over the line (class added in main.js) —
   a static page with JS disabled/failed never shows a blinking cursor next to
   text that isn't going anywhere. */
.typeline .cursor.is-active {
  animation: typeline-blink 1s step-end infinite;
}
@keyframes typeline-blink { 50% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .typeline .cursor.is-active { animation: none; }
}

/* Current-page nav indicator (aria-current="page", set per-file in the
   header — see index.html/about.html/contact.html HEADER comments).
   Predictable "you are here" signal, low-cost accessibility/UX addition. */
.nav-links a[aria-current="page"] {
  color: var(--color-bg);
  font-weight: 700;
}

/* Contact form (contact.html) — plain HTML form styled to match the button
   language above. Posts to /api/contact, a Cloudflare Pages Function that
   delivers by email via Resend (IS-14) — works without JS (native POST +
   redirect) and is progressively enhanced by main.js's initContactForm()
   to swap in an inline confirmation instead. Field set (Salutation, First
   Name, Last Name, Position, Company, Email, Subject, Message — IS-31)
   is shared with the "Ask for quotation" flow from the service cards
   (IS-32), which pre-fills Subject via a URL param — see
   prefillQuoteSubject() in main.js. See DEPENDENCY_MATRIX.md §7/§9. */
.contact-form {
  max-width: 46rem;
  margin-top: var(--space-5);
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4) var(--space-4);
}
@media (min-width: 40rem) {
  .contact-form {
    grid-template-columns: 1fr 1fr;
  }
}
.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.field--full {
  grid-column: 1 / -1;
}
/* The submit button row and the fallback note aren't .field elements but
   are still direct grid children of .contact-form — span both columns so
   they don't get squeezed into a single half-width cell. */
.contact-form > .cluster,
.contact-form > .form-note {
  grid-column: 1 / -1;
}
.field label {
  font-size: var(--step--1);
  font-weight: 600;
}
.field .hint {
  font-weight: 400;
  color: var(--color-text-muted);
}
.field input,
.field select,
.field textarea {
  font: inherit;
  color: var(--color-text);
  background: var(--color-surface-raised);
  border: var(--border-hairline);
  border-radius: var(--radius);
  padding: 0.65em 0.85em;
}
.field textarea {
  resize: vertical;
  min-height: 10rem;
  font-size: var(--step-0, 1rem);
}
.form-note {
  font-size: var(--step--1);
  color: var(--color-text-muted);
}


/* Contact form: honeypot + real submission feedback (IS-14) */
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
.form-success {
  font-size: var(--step-1, 1.1rem);
  background: var(--color-surface);
  border-left: 3px solid var(--color-accent);
  padding: var(--space-3);
  border-radius: var(--radius);
}
.form-error {
  font-size: var(--step--1);
  color: var(--color-text);
  background: color-mix(in srgb, red 12%, var(--color-surface));
  border-left: 3px solid red;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  margin: 0 0 var(--space-3);
}
/* utilities.css — small helper classes. Depends on variables.css. */
.text-muted { color: var(--color-text-muted); }
.text-accent { color: var(--color-accent); }
.text-center { text-align: center; }
.mono { font-family: var(--font-mono); }
.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--color-accent);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* Skip link — visually hidden until keyboard focus reveals it at the top of
   the page. Neurodivergent/keyboard-navigation aid: predictable, always in
   the same place, never announced elsewhere on the page. */
.skip-link {
  position: absolute;
  top: -3rem;
  left: var(--space-3);
  z-index: 100;
  background: var(--color-accent);
  color: var(--color-bg);
  padding: 0.6em 1em;
  border-radius: var(--radius);
  font-size: var(--step--1);
  font-weight: 600;
  transition: top 0.15s ease;
}
.skip-link:focus {
  top: var(--space-3);
}
@media (prefers-reduced-motion: reduce) {
  .skip-link { transition: none; }
}

/* Legal notice / impresszum fine print in the footer. Kept visually quiet
   (small, muted, separated by a hairline) so it stays present and
   prominent per Hungarian/EU law without competing with the page's
   primary services content. */
.legal-notice {
  margin: var(--space-3) 0 0;
  padding-top: var(--space-3);
  border-top: var(--border-hairline);
  max-width: 48rem;
  font-size: var(--step--1);
  line-height: 1.6;
  color: var(--color-text-muted);
}

/* Reading-width constraint for prose blocks on product pages (IS-35).
   Deliberately NOT applied by narrowing `.wrap` itself: `.wrap` is centred, so
   a narrower wrap shifts its section inward and breaks the left alignment that
   every other section on the page shares. This constrains the content and
   leaves the section's left edge where it belongs. */
.measure { max-width: 46rem; }
