/* Prisma — shared foundation.
   Load this before any section stylesheet. Sections own their layout;
   everything reusable (faces, palette, type scale, quality floor) lives here. */

/* ---------- Faces ---------- */

/* Unicase display face — the one the PRISMA wordmark itself is set in.
   Lowercase input still renders as caps-styled glyphs. Use with restraint:
   headings that need to read as an extension of the logo, nothing else. */
@font-face {
  font-family: 'Breve';
  src: url('../assets/design/fonts/BREVE2.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

/* Technical/drafting face, genuine lowercase. Carries the nav and any
   utility text — labels, captions, data. */
@font-face {
  font-family: 'ISOCPEUR';
  src: url('../assets/design/fonts/ISOCPEUR.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

/* ---------- Tokens ---------- */

:root {
  /* Palette. --blue-deep is the page ground; the chevron art is drawn in
     lighter tints of it, so it is the only background any section sets. */
  --blue-deep: #00486C;
  --paper: #f4f7f9;
  --paper-dim: rgba(244, 247, 249, 0.68);

  /* Panels are opaque, so they sit cleanly on top of the chevron art rather
     than letting its diagonals read through them.

     C91 M42 Y13 K36 from the CorelDRAW source, which converts to (14,93,139)
     and matches the (6,91,131) the reference PNG exports. The C91 M42 Y13
     K53 variant converts to (11,68,102) — that is the page ground, not a
     panel, so both the gallery tile and the lightbox panels use this one. */
  --panel-gallery: #0e5d8b;
  --panel-open: #0e5d8b;

  /* One step up from --panel-open/--panel-gallery — an "elevated surface"
     tone reused wherever something needs to read as floating above the
     base panel (a cta card's hover, the lightbox pdf box, the 404 cta's
     hover). Was four independent `#12699b` literals across services.css/
     events.css/about.css/not-found.css before 2026-09-16; consolidated
     since all four were already documented as intentionally the same
     value, not four values that happened to coincide. */
  --panel-elevated: #12699b;

  /* The logo's own two colours, sampled straight out of
     assets/logo/logo_main_transparent_bg.webp — they are the only two saturated
     values anywhere in that file, so they are the brand, not an approximation.

     Accents only, and sparingly: neither reads on --panel-gallery (red lands
     at 1.6:1 against it, green at 1.9:1). Both carry --paper text on their own
     ground instead — red 4.7:1, which passes AA. If a green ground is ever
     wanted, derive a darker shade of it (#007134 gives 6.2:1) rather than
     using the flat value, which only manages 3.8:1. */
  --brand-red: #e31e24;
  --brand-green: #009846;

  /* Shadow ground — the deep end of the blue, for tinted drop shadows. */
  --shadow-ink: 0, 24, 38;

  /* Type roles */
  --font-display: 'Breve', sans-serif;
  --font-utility: 'ISOCPEUR', sans-serif;

  /* Type scale — container-height based (cqh, against .hero's own
     container-type:size box, itself locked to the 4032x2039 design aspect —
     see hero.css), because sections are full-height canvases scaled from
     that fixed design and must hold their proportions against the art, not
     against text column width. Was vh; vh only agreed with cqh at the one
     viewport shape the design was tuned at (4032:2039) — cqh keeps that
     agreement at every window size now that .hero is aspect-locked, instead
     of drifting the way raw vh did against an arbitrary viewport. Below the
     mobile breakpoint these are redefined entirely (rem/clamp-based, no
     stage container to measure against) — see the mobile block below. */
  --size-nav: 3.12cqh;
  --size-heading: 2.85cqh;
  --size-body: 2.45cqh;
  --size-caption: 1.85cqh;
  --lh-body: 3cqh;
  --lh-caption: 2.26cqh;
  --track-nav: 0.02em;

  /* Interaction */
  --focus-ring: 2px solid var(--paper);
  --focus-offset: 4px;
  --ease-ui: 160ms ease;

  /* Motion. --ease-move carries the section-to-section choreography: the
     chevrons travel, then --delay-settle lets the incoming content fade up
     only once they have landed, so nothing is moving while it is read. */
  --ease-move: 700ms cubic-bezier(0.22, 1, 0.36, 1);
  --ease-fade: 420ms ease;
  --delay-settle: 560ms;
}

/* ---------- Reset ---------- */

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  background: var(--blue-deep);
}

/* Every page fades its own body in on load, rather than just cutting to
   fully-painted content — the counterpart to the top-progress bar's
   deferred navigation (top-progress.js): that bar gives the moment of
   *leaving* a page a visible cue, this gives the moment of *arriving* at
   one the same treatment. Reuses --ease-fade (the same fade timing
   .section-layer/.cookie-notice already use) rather than inventing a new
   duration. top-progress.js adds `.is-ready` on every page after a real
   paint of this hidden state has happened (double `requestAnimationFrame`,
   the same technique about.js's own first-section reveal relies on) —
   without that, adding the class in the same tick as the element's first
   style calculation risks the browser coalescing the two and skipping the
   transition entirely. */
body {
  opacity: 0;
  transition: opacity var(--ease-fade);
}

body.is-ready {
  opacity: 1;
}

/* Stage mode (desktop/laptop/tablet-landscape, default): body centers .hero
   as a flex item so .hero can be held to its own locked aspect ratio
   (hero.css) without stretching to fill an arbitrary viewport shape —
   letterbox/pillarbox bars are just body's own background showing through,
   which already matches .hero's, so no extra markup is needed for them. */
body {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100dvh;
  overflow: hidden;
}

/* ---------- Top progress bar ---------- */

/* Shared across every page (top-progress.js drives it), not just the hero
   stage — a thin bar across the top of the viewport that plays whenever a
   click is about to leave the current page for another real one: the
   contact card's Privacy Notice/Imprint links, the cookie notice's Cookie
   Policy link, a legal page's own home/back-to-contact header links, the
   404 page's back-to-homepage button. Deliberately NOT wired into the
   hero's own section-to-section navigation (about/services/products/
   events/contact) — that curtain already animates via the chevrons
   (hero.css's --ease-move), so this bar would be redundant there; it exists
   specifically for the moments that otherwise cut over with no visual cue
   at all, an actual document navigation.
   Fixed to the viewport rather than any one page's own layout, so it needs
   no per-page positioning rule. z-index 300 — above every other fixed
   element any page defines (`.social-linkedin`/`.cookie-notice` at 200,
   the lightbox at 30), so it stays visible regardless of what else is on
   screen when a navigation fires. */
.top-progress {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 3px;
  z-index: 300;
  pointer-events: none;
  overflow: hidden;
}

.top-progress__bar {
  display: block;
  height: 100%;
  width: 100%;
  background: var(--brand-red);
  transform: scaleX(0);
  transform-origin: left;
  opacity: 0;
}

/* `.is-active`/`.is-done` are two distinct legs, not one transition: the
   fill races out to 85% immediately on click (never a hard-coded 100%, so
   it never visibly "finishes" before the browser actually has the next
   page ready), then `.is-done` (armed by top-progress.js once its own
   fixed duration has elapsed) snaps the remainder to full and fades the
   bar out — in practice the real navigation almost always cuts the
   animation short first, the same way a browser's own load bar behaves.
   Animates only `transform`/`opacity`, per this project's own animation
   rule. */
.top-progress.is-active .top-progress__bar {
  transition: transform 650ms cubic-bezier(0.22, 1, 0.36, 1), opacity 160ms ease;
  transform: scaleX(0.85);
  opacity: 1;
}

.top-progress.is-done .top-progress__bar {
  transition: transform 200ms ease, opacity 300ms ease 150ms;
  transform: scaleX(1);
  opacity: 0;
}

/* ---------- Language switcher ---------- */

/* Shared by every page (hero.*.html and the four legal pages, all three
   languages) — a small white block holding square flag tiles, loosely
   matching `references site/language switcher.jpg` (that reference itself
   uses a dark, rounded pill — both the fill and the corners were changed
   on request 2026-09-16: white instead of dark, square instead of rounded).
   Only the two *non-current* language flags ever appear; lang-switcher.js
   sets the current one's `hidden` attribute (native — no extra CSS needed,
   the UA default `[hidden] { display: none }` is enough) and rewrites the
   other two hrefs to the equivalent page in that language. Positioning
   (fixed stage corner on hero.*.html desktop/laptop, embedded in
   `.mobile-nav` there on phone/tablet, in-flow inside `.legal-header` on
   the four standalone pages) is each page type's own concern — see
   hero.css/legal.css. `--paper`, not a flat `#fff` — the site's own token
   for "white," matching every other white surface here. The tile borders
   below are a dark tint (visible against this light fill) — they were a
   light tint before the background itself flipped from dark to white, in
   the same request. */
.lang-switcher {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px;
  border-radius: 10px;
  background: var(--paper);
  box-shadow: 0 6px 16px -4px rgba(var(--shadow-ink), 0.55);
}

.lang-switcher__link {
  display: block;
  width: 28px;
  height: 20px;
  line-height: 0;
  border-radius: 0;
  overflow: hidden;
  box-shadow: inset 0 0 0 1px rgba(var(--shadow-ink), 0.18);
  transition: transform var(--ease-ui), filter var(--ease-ui), box-shadow var(--ease-ui);
}

.lang-switcher__link svg {
  display: block;
  width: 100%;
  height: 100%;
}

.lang-switcher__link:hover {
  transform: translateY(-1px);
  filter: brightness(1.06);
  box-shadow: inset 0 0 0 1px rgba(var(--shadow-ink), 0.4);
}

.lang-switcher__link:active {
  transform: translateY(0) scale(0.94);
  filter: brightness(0.92);
}

/* lang-switcher.js sets the native `hidden` attribute on whichever flag
   matches the current page's own language. The UA stylesheet's own
   `[hidden] { display: none }` normally handles that for free, but author
   rules always outrank UA ones at equal specificity regardless of source
   order — so the `display: block` above would otherwise silently win and
   the current-language flag would never actually disappear. Restated here
   to make the hide take effect. */
.lang-switcher__link[hidden] {
  display: none;
}

/* ---------- Card CTA chevron ---------- */

/* Paper plate, red chevron — the same glyph the gallery steps through its
   photographs with, so the page's one arrow shape does this job too. Red on
   paper is the only place either brand colour is safe as a mark (see
   "Brand colours" in CLAUDE.md). Shared by `.services-card--cta` and
   `.events-card--cta` (their own files still declare the `--cta` rule
   itself, since each scopes a differently-named local padding override —
   `--card-pad`/`--events-card-pad` — but this button was a byte-for-byte
   duplicate between the two, consolidated here 2026-09-16). */
.services-card__go,
.events-card__go {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 3.4cqw;
  height: 3.4cqw;
  background: var(--paper);
  color: var(--brand-red);
  transition: transform var(--ease-ui);
}

.services-card__go svg,
.events-card__go svg {
  display: block;
  width: 34%;
  height: auto;
}

.services-card--cta:hover,
.events-card--cta:hover {
  background: var(--panel-elevated);
}

.services-card--cta:hover .services-card__go,
.events-card--cta:hover .events-card__go {
  transform: translateX(0.35cqw);
}

.services-card--cta:active .services-card__go,
.events-card--cta:active .events-card__go {
  transform: translateX(0.15cqw);
}

/* ---------- Quality floor ---------- */

/* Keyboard focus must stay visible on the dark ground in every section. */
:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* Present to the DOM (crawlers, screen readers) but not painted on screen. */
.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;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------- Mobile flow ---------- */

/* The one mode switch for the whole site: phones and tablets (both
   portrait and landscape, up to 1024px wide — see the "Tablet tier" block
   below for the width band's own reasoning) get a normal stacked,
   continuously-scrolling layout instead of the fixed-aspect chevron
   "stage" (see hero.css). Keyed on aspect ratio as well as width, because
   the underlying problem the stage-mode fix (hero.css) solves is
   aspect-ratio drift — a portrait tablet/phone needs this mode for that
   reason regardless of width, and the max-width clause is what additionally
   pulls in *landscape* tablets (aspect > 1, so the aspect clause alone
   wouldn't catch them) up to 1024px, plus acting as a safety net for
   narrow-but-landscape desktop windows below that width. This exact query
   is repeated in every section stylesheet that needs a mobile/tablet
   override — keep it identical everywhere it appears. */
@media (max-width: 1024px), (max-aspect-ratio: 1/1) {
  /* The arrival fade above costs real First Contentful Paint time here
     (a fully-transparent element doesn't count as "contentful," so FCP
     waits out the double-rAF handshake plus the 420ms transition) —
     not worth it on mobile/tablet, where load speed matters most. Skip
     it: render at full opacity from the first frame, no transition to
     wait on. `top-progress.js` still adds `.is-ready` unconditionally;
     at this tier it's just a harmless no-op. Desktop/laptop keep the
     fade unchanged. */
  body {
    display: block;
    height: auto;
    min-height: 100dvh;
    overflow: visible;
    opacity: 1;
    transition: none;
  }

  /* While a lightbox is open, only its own `.lightbox { overflow-y: auto; }`
     should scroll — reported: scrolling outside the lightbox's own content
     (its fixed backdrop is undimmed and easy to touch by accident, and
     `.mobile-nav`'s fixed header sits above the lightbox in z-index too)
     could still move the page underneath it. `body`'s own `overflow:
     visible` above is what makes that possible — a scroll gesture that
     doesn't land on something scrollable bubbles up to the document and
     scrolls it. Locking `body` back to `overflow: hidden` whenever
     `.lightbox` carries `.is-open` removes that fallback entirely, so a
     stray gesture over the backdrop or header has nothing left to scroll.
     `:has()` reads this straight off the lightbox's own state with no JS
     changes needed — `.lightbox` is a `.hero` descendant, `.hero` a `body`
     descendant, and `:has()` matches on any descendant, not just a direct
     child. */
  body:has(.lightbox.is-open) {
    overflow: hidden;
  }

  :root {
    /* Mobile abandons the stage/container premise entirely, so these need
       a genuinely separate, conventional rem/clamp scale rather than a unit
       swap on the desktop values. Placeholders pending a render-and-measure
       pass, same as every other tuned number in this codebase. */
    --size-nav: clamp(1rem, 4vw, 1.25rem);
    --size-heading: clamp(1.5rem, 6vw, 2rem);
    --size-body: clamp(0.95rem, 4vw, 1.05rem);
    --size-caption: clamp(0.8rem, 3.2vw, 0.9rem);
    --lh-body: 1.6;
    --lh-caption: 1.5;
  }
}

/* Tablet tier: 768px-1024px width, either orientation — layers on top of
   the flow block above the same way the "laptop" tier layers on top of
   each section file's own desktop `.hero{}` block (see hero.css/
   services.css/etc). The phone clamps above already saturate at their own
   max well before 768px width (e.g. --size-nav's 4vw term passes its
   1.25rem ceiling at ~31px viewport width), so a tablet rendered under the
   flow block alone would sit at the exact same capped size as a large
   phone — these higher caps are what actually make tablet text/spacing
   read as larger than phone, not just a wider viewport. First-pass values,
   no tablet reference mockup exists yet — see CLAUDE.md's "Tablet tier"
   section. */
@media (min-width: 768px) and (max-width: 1024px) {
  :root {
    --size-nav: clamp(1.25rem, 2.4vw, 1.5rem);
    --size-heading: clamp(2rem, 3.4vw, 2.5rem);
    --size-body: clamp(1.05rem, 2vw, 1.2rem);
    --size-caption: clamp(0.9rem, 1.6vw, 1rem);
    --lh-body: 1.65;
    --lh-caption: 1.5;
  }
}
