/* ==========================================================================
   BCCRT.ORG — BACCARAT — main stylesheet
   ==========================================================================

   This file replaces reset.css + styles.css + fonts.css from the old site.
   Every value that affects the look is taken over 1:1 — the goal of this
   relaunch is "same pixels, cleaner code".

   What is new, at a glance
   ------------------------
   1. CASCADE LAYERS (@layer)
      The layer order is declared once, right below. A rule in a later layer
      always beats a rule in an earlier layer, no matter how specific the
      selectors are. That replaces the old "fight specificity with
      !important" approach: the three !important flags of the old CSS are
      gone. Supported in all browsers since 2022.

   2. NATIVE CSS NESTING
      Related rules (a component, its states and its media queries) are
      written inside one block, like in Sass — but without a preprocessor.
      The "&" stands for the parent selector. Supported since 2023.

   3. CLASS-BASED SELECTORS
      Styles hang on classes (.track, .site-header …) instead of bare
      element selectors (article, header …). The markup can then change
      (e.g. an <article> becomes a <section>) without breaking the design,
      and selectors no longer accidentally hit elements they weren't meant
      for (the old "svg { filter }" rule applied to EVERY svg on the page).

   4. STATE VIA ATTRIBUTES / STATE CLASSES
      JS no longer writes inline styles. It only toggles a state
      (aria-pressed, .is-active, .is-hidden) and the CSS decides what that
      state looks like. Design stays in CSS, logic stays in JS.

   5. DEAD CODE REMOVED
      Unused rules (h1, h4, ul/li, a, figure, .stroboscope, .wiggle,
      .cockroach, .fade-in-transition and the keyframes animation3d,
      wiggle, strobo-animation) and unused tokens were deleted.
   ========================================================================== */

@layer reset, tokens, base, layout, components, utilities;


/* ==========================================================================
   FONT
   ==========================================================================
   (Previously in fonts/fonts.css.)
   • woff2 is listed FIRST: browsers take the first format they support,
     and woff2 is ~30% smaller. The old file listed woff first, so every
     browser downloaded the bigger file.
   • font-display: swap shows the fallback font immediately and swaps in
     Maxi Sharp as soon as it has loaded, instead of hiding the text.
   • @font-face can't be put in a layer (it's a definition, not a style
     rule), so it lives outside.
   ========================================================================== */

@font-face {
  font-family: "Maxi Sharp";
  src:
    url("../fonts/ABCMaxiSharp-Regular.woff2") format("woff2"),
    url("../fonts/ABCMaxiSharp-Regular.woff") format("woff");
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}


/* ==========================================================================
   LAYER: reset
   ==========================================================================
   A trimmed version of the old reset.css (Andy Bell's "modern CSS reset").
   Only the rules that matter for the elements actually used are kept.
   ========================================================================== */

@layer reset {
  /* Width/height include padding and border — the intuitive box model. */
  *,
  *::before,
  *::after {
    box-sizing: border-box;
  }

  /* Remove default margins; spacing is set deliberately in "layout". */
  body,
  h1,
  h2,
  p {
    margin: 0;
  }

  body {
    min-height: 100vh;
    line-height: 1.4;
    /* Kept from the original. Note: "optimizeSpeed" turns off kerning and
       ligatures in some browsers, so it has a (small) visible effect on
       the type. Removing it would change the look, therefore it stays. */
    text-rendering: optimizeSpeed;
  }

  img {
    display: block;
    /* Original value: images never get wider than the viewport minus the
       side padding. */
    max-width: calc(100vw - (var(--space-df) * 2));
    /* NEW: the <img> tags now carry width/height attributes, so the
       browser can reserve the right amount of space before the image has
       loaded (no layout shift). "height: auto" keeps the aspect ratio
       when max-width shrinks the image. Visually identical to before. */
    height: auto;
  }

  /* Buttons don't inherit fonts by default. */
  button {
    font: inherit;
  }

  /* Respect the OS setting "reduce motion": (almost) switch off all
     animations, transitions and smooth scrolling. */
  @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;
    }
  }
}


/* ==========================================================================
   LAYER: tokens
   ==========================================================================
   Design tokens as custom properties. Only tokens that are actually used
   were kept (the old file also defined --off-white, --beige, --green, a
   text-size scale and --space-xs/-sm that nothing referenced).
   ========================================================================== */

@layer tokens {
  :root {
    /* --- Spacing ---------------------------------------------------------
       A doubling scale based on the base unit (18px):
       df 18px · md 36px · lg 72px · xl 144px · xxl 288px */
    --space-unit: 18px;
    --space-scale-ratio: 2;
    --space-df: var(--space-unit);
    --space-md: calc(var(--space-scale-ratio) * var(--space-df));
    --space-lg: calc(var(--space-scale-ratio) * var(--space-md));
    --space-xl: calc(var(--space-scale-ratio) * var(--space-lg));
    --space-xxl: calc(var(--space-scale-ratio) * var(--space-xl));

    /* --- Colours --------------------------------------------------------- */
    --white: rgb(252 252 252);
    --red: rgb(255 33 29);
    --black: rgb(28 24 22);
    /* Modern space-separated rgb() syntax; the old comma syntax still
       works, this is just the current standard notation. Same colours. */

    /* --- Video -----------------------------------------------------------
       Maximum height of a track video. Portrait screens get a bit more. */
    --video-max-height: 85vh;

    @media (orientation: portrait) {
      --video-max-height: 90vh;
    }
  }
}


/* ==========================================================================
   LAYER: base
   ==========================================================================
   Page-wide defaults.
   ========================================================================== */

@layer base {
  html,
  body {
    font-family: "Maxi Sharp", Helvetica, Arial, sans-serif;
    min-height: 100%;
    background: var(--black);
    /* Prevent horizontal scrolling (e.g. by the huge red headlines).
       "clip" instead of the old "hidden": both cut off what sticks out,
       but "hidden" has a nasty side effect — setting overflow-x to hidden
       silently turns overflow-y into "auto" and makes <body> a scroll
       container of its own. Features that depend on "the nearest scroll
       container" then talk to <body> instead of the page: CSS scroll
       snapping stopped working because of this (see snap.css), and
       position: sticky would break the same way. "clip" has no such side
       effect. Visually identical. */
    overflow-x: clip;
  }

  /* NEW: smooth scrolling when jumping to a track.
     Previously: "html:focus-within { scroll-behavior: smooth }" — that only
     kicks in while an element on the page has focus. Chrome focuses the
     clicked clock link, Safari does NOT focus links on click, so Safari
     users got a hard jump instead of a smooth scroll. Applied to <html>
     directly it works everywhere. Wrapped in a media query so people who
     asked their OS for reduced motion get the instant jump. */
  @media (prefers-reduced-motion: no-preference) {
    html {
      scroll-behavior: smooth;
    }
  }

  /* The whole page fades in over 3 seconds on load. */
  body {
    animation: fade-in 3s ease forwards;
  }

  @keyframes fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
}


/* ==========================================================================
   LAYER: layout
   ==========================================================================
   The big building blocks of the page and the spacing between them.
   ========================================================================== */

@layer layout {
  /* --- Vertical rhythm ----------------------------------------------------
     Every direct child of <main> that FOLLOWS another child gets 288px of
     space above it ("lobotomised owl" selector: * + *).
     :where() reduces the specificity of the whole selector to zero, so any
     component can override the margin with a simple class (e.g. the
     footer below) — without depending on source order or !important. */
  :where(.site-main > * + *) {
    margin-top: var(--space-xxl);
  }

  /* NEW: <main> becomes a "size container" for its width.
     This lets elements inside it use the unit "cqi" (container query
     inline size): 100cqi = the content width of <main>. The tracks need
     that to work out their own height for centring (see .track,
     scroll-margin-top). Why not simply 100vw? On systems with permanently
     visible scrollbars (Windows, some Mac settings), 100vw includes the
     scrollbar, so the result would be off by a few pixels.
     Side effects: none visible. "inline-size" containment only means the
     width of <main> no longer depends on its content — it already takes
     the full page width as a normal block element anyway. */
  .site-main {
    container-type: inline-size;
  }

  /* --- Fixed background video -------------------------------------------- */
  .bg-video {
    position: fixed;
    top: 0;
    left: 0;
    z-index: -100;
    width: 100%;
    height: 100vh;
    max-height: 100vh;
    object-fit: cover;
    background: var(--black);
  }

  /* --- Bottom gradient overlay ------------------------------------------- */
  .bottom-gradient {
    position: fixed;
    inset: auto 0 0; /* = top: auto; right: 0; bottom: 0; left: 0 */
    z-index: 1000;
    height: 22vh;
    /* (The old rule had a dead fallback "background: rgb(2, 0, 36)" that
       was always overridden by the gradient — removed.) */
    background: linear-gradient(
      0deg,
      rgb(28 24 22 / 1) 0%,
      rgb(28 24 22 / 0.1) 80%,
      rgb(28 24 22 / 0) 100%
    );
  }

  /* --- Header (mute button + clock) --------------------------------------
     Landscape: a full-height block at the top of the page; its children
     (button and clock) are position: fixed.
     Portrait: the header itself is fixed and only shows the mute button. */
  .site-header {
    height: 100vh;
    padding: var(--space-df);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    transition: opacity 0.4s;

    @media (orientation: portrait) {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      z-index: 1001;
      width: 100%;
      max-height: 84vh;
      justify-content: flex-start;
    }

    /* Set by JS while the footer is visible, so the credits are readable
       and the (invisible) clock links can't be clicked by accident.
       (Previously JS wrote style.opacity / style.pointerEvents inline.) */
    &.is-hidden {
      opacity: 0;
      pointer-events: none;
    }
  }

  /* --- Footer -------------------------------------------------------------- */
  .site-footer {
    box-sizing: content-box;
    max-inline-size: 100ch;
    margin-inline: auto;
    margin-top: 80vh;
    margin-bottom: 45vh;
    padding-inline: var(--space-df);
    color: var(--white);
    font-size: 1.4em;
    text-align: center;
  }
}


/* ==========================================================================
   LAYER: components
   ========================================================================== */

@layer components {

  /* --- Mute button ----------------------------------------------------------
     The button has no text; the cockroach GIF is the label.
     aria-pressed="true" means "sound is ON" and shows the active GIF. */
  .mute-button {
    position: fixed;
    top: 2em;
    z-index: 10001;
    width: 32vw;
    max-width: 220px;
    height: 24vw;
    max-height: 150px;
    border: none;
    background: url("../assets/cockroach-funktion-one.gif") no-repeat;
    background-size: contain;
    cursor: pointer;
    /* "all" is spelled out (the old value was just ".5s"). It animates
       the rotation and — in Chromium — even cross-fades the GIF swap. */
    transition: all 0.5s;

    &[aria-pressed="true"] {
      background-image: url("../assets/cockroach-funktion-one-active.gif");
    }

    @media (orientation: landscape) {
      transform: rotate(90deg);
    }

    @media (orientation: portrait) {
      position: relative;
      top: 0;
      min-width: 100%;
      min-height: 30vh;
      background-position: center;
    }
  }

  /* --- Clock navigation ----------------------------------------------------- */
  .clock {
    max-height: 100vh;
    /* These four were an inline style="" on the <svg> elements. */
    fill-rule: evenodd;
    clip-rule: evenodd;
    stroke-linejoin: round;
    stroke-miterlimit: 2;
  }

  /* Portrait artwork: hidden in BOTH orientations — exactly like on the
     old site (there it took two separate rules to achieve this). */
  .clock--portrait {
    display: none;
  }

  /* Landscape artwork: fixed over the page; hidden on portrait screens. */
  .clock--landscape {
    position: fixed;
    z-index: 10000;
    width: 100%;

    @media (orientation: portrait) {
      display: none;
    }
  }

  /* Subtle dark halo so the white digits stay readable on bright video
     frames. Previously a filter: drop-shadow() on the whole .clock — that
     forced the browser to re-blur the entire full-width overlay whenever
     any digit changed. Now a plain stroke drawn BEHIND the fill
     (paint-order), so only its outer half shows as a rim: no blur, cheap.
     Width/opacity (2.5px, 30%) were tuned by eye to match the old soft
     2px shadow at 50% as closely as possible.
     non-scaling-stroke keeps the rim at screen pixels despite the SVG's
     many transform matrices. Scoped to the digit paths, because the
     invisible hit circles (fill="none") would otherwise inherit the stroke
     and show up as black rings. */
  .clock-digit > path {
    stroke: rgb(0 0 0 / 0.3);
    stroke-width: 2.5px;
    paint-order: stroke;
    vector-effect: non-scaling-stroke;
  }

  /* The number of the track that is currently in view. JS sets
     .is-active on the digit group; the paths inside turn red and glow.
     Previously: class "active-fill" was added to every single path,
     with "fill: red !important". Because the SVG colours are now
     presentation attributes (fill="white"), any normal CSS rule beats
     them — no !important needed.
     Note: "red" (pure #f00) is intentionally NOT var(--red); this is the
     exact colour the old site used for the digits. */
  .clock-digit.is-active > path {
    fill: red;
    filter: drop-shadow(0 0 2px var(--red));
  }

  /* HOVER HOOK — currently without styles, so nothing changes visually.
     JS adds .is-hovered to a digit while its link is hovered with the
     mouse or focused with the keyboard (see main.js, "Hover / focus
     state"). A plain :hover can't be used here, because the clickable
     circles and the visible digits are separate SVG elements.
     Example — uncomment to try:

  .clock-digit.is-hovered > path {
    fill: var(--red);
    transition: fill 0.2s;
  }

     Tip: transitions on SVG "fill" work in all modern browsers. Because
     .is-active comes first in this file, an active digit that is also
     hovered gets the hover style — swap the order of the two rules if the
     active state should win. */

  /* >>> TEST (requested): yellow hover + 1s transition >>>>>>>>>>>>>>>>>>>>
     Delete this block to return to the original look.

     The transition sits on the BASE state of the digit paths, so it runs
     in both directions (fade in on hover, fade out on leave). Note that it
     also affects the red "active" highlight: the current number now fades
     in/out over 1s, and during a clock jump the numbers flown past leave
     a fading trail. Only "fill" is transitioned: animating the
     drop-shadow filter of the active digit as well meant re-computing a
     blur on every frame (for several digits at once during a jump). */
  .clock-digit > path {
    transition: fill .1s;
  }

  .clock-digit.is-hovered > path {
    fill: yellow;
  }
  /* <<< TEST <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */

  /* --- Track ----------------------------------------------------------------
     One <article> per track: a centred column holding the video. */
  .track {
    /* Maximum width of the track column. Kept in a custom property
       because it is needed twice: for max-inline-size AND for the
       height calculation in scroll-margin-top below. */
    --track-max-width: 80ch;

    box-sizing: content-box;
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    max-inline-size: var(--track-max-width);
    margin-inline: auto;
    padding-inline: var(--space-df);

    /* When jumping to a track via the clock, stop so that the track sits
       vertically centred in the viewport:
         offset from the top = (viewport height − track height) / 2

       CHANGED: previously "calc((100vh - var(--video-max-height)) / 2)".
       That assumed the video is ALWAYS exactly --video-max-height tall.
       But the video is 9:16 at 100% of the column width, so on narrow
       screens (phones) its width runs out first and it ends up SHORTER
       than the max-height. Example iPhone 375×812: video 603px tall,
       old offset 41px from the top but 169px from the bottom — visibly
       too high. Now the real height is calculated the same way the
       browser lays out the video:

         column width  = min(max width, width of <main> − padding left/right)
                       = min(var(--track-max-width), 100cqi − 2 × padding)
         track height  = min(video max-height, column width × 16 / 9)

       (100cqi = content width of <main>, see .site-main in the layout
       layer. "ch" here resolves with the track's own font size, exactly
       like in max-inline-size, so both always match.)
       If the 9:16 ratio of .track__video ever changes, change the 16 / 9
       here as well. */
    scroll-margin-top: calc(
      (
        100vh -
        min(
          var(--video-max-height),
          min(var(--track-max-width), 100cqi - 2 * var(--space-df)) * 16 / 9
        )
      ) / 2
    );

    @media (orientation: landscape) {
      --track-max-width: 65ch;
    }

    /* Portrait: the fixed header covers the top of the screen, so the
       first track starts one full screen further down.
       Previously "article:nth-child(2) { margin-top: 100vh !important }" —
       which depended on the exact position in the DOM. */
    &:first-of-type {
      @media (orientation: portrait) {
        margin-top: 100vh;
      }
    }
  }

  .track__video {
    position: relative;
    z-index: 101;
    /* Full width of the track column. CHANGED (10.10.2026): previously
       "width: 100%". Safari (WebKit, tested 26.6) has a bug with that
       combination — percentage width + aspect-ratio inside a flex column
       with horizontal padding: it lays the video out 606px wide, but
       calculates the 9:16 height from the column width INCLUDING the
       padding (642px) → the video came out 1142px instead of 1078px tall.
       Visible only on tall windows (from ~1350px viewport height; below
       that, max-height cuts it anyway), and it made clock jumps land too
       low, because scroll-margin-top in .track assumes exact 9:16.
       "align-self: stretch" (with the default width: auto) fills the
       column just the same, but without a percentage — Safari then gets
       the height right. Chrome/Firefox: identical result either way. */
    align-self: stretch;
    max-height: var(--video-max-height);
    object-fit: cover;
    /* NEW: all videos are 9:16 (720×1280). Declaring the ratio lets the
       browser reserve the final size immediately. Before, tracks 01 and
       24 (no poster) started as a 300×150 box and jumped to full height
       once the video metadata arrived — which could also throw off a
       smooth scroll that was in progress. The final layout is identical. */
    aspect-ratio: 9 / 16;
  }

  /* Tracks 02–23: cover image behind the video (the URL itself is inline
     in the HTML, because it differs per track). */
  .track__video--cover {
    background-color: var(--black);
    background-position: center;
    background-size: cover;
    background-repeat: no-repeat;
  }

  /* Tracks 01 and 24: the video is practically invisible and only serves
     as audio source; the logo sits on top.
     (Previously class "hidden" — renamed because "hidden" usually means
     display: none / the HTML hidden attribute, which this is not. 0.01
     instead of 0 keeps browsers from treating the video as invisible and
     throttling it.) */
  .track__video--audio-only {
    opacity: 0.01;
  }

  .track__logo {
    position: absolute;
    top: 50%;
    left: 50%;
    z-index: 102;
    transform: translate(-50%, -50%);
  }

  /* --- Red questions between the tracks ------------------------------------ */
  .question {
    color: var(--red);
    font-size: clamp(4.6rem, 10vw, 12rem);
    /* font-weight is deliberately left at the browser default for h2
       (bold). The font only ships a Regular cut, so the browser
       synthesises the bold — that's part of the look. */
    line-height: 0.88;
    letter-spacing: -0.01em;
    text-align: center;
    text-transform: uppercase;
    text-shadow: 0 0 4px var(--red);
  }
}


/* ==========================================================================
   LAYER: utilities
   ========================================================================== */

@layer utilities {
  /* Hide something visually but keep it available to screen readers
     (used for the page's <h1>). The standard, battle-tested pattern. */
  .visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
}
