/* ============================================================
   HC.F — HARDCORE FEIJENOORD
   Shared raw / web-1.0 visual system
   black canvas · blackletter · grain + scanlines · bare links
   ============================================================ */

/* Geen Google Fonts meer (28-09, review 9.2). Cousine (de mono voor labels) en
   UnifrakturCook (alleen reserve achter het merkfont) komen van next/font, dat
   ze bij het bouwen ophaalt en van ons eigen domein serveert; zie
   src/app/fonts.ts. Hier komen ze binnen als de variabelen --font-cousine en
   --font-blackletter, die elke root-layout op <html> zet. Vroeger stond hier
   een @import van fonts.googleapis.com: dan maakte elke bezoeker verbinding
   met Google. */

/* Old English Text MT — het merkfont. Monotype-webfontlicentie via MyFonts,
   zie de licentieparagraaf in README.md; het bestand mag daarom mee de repo
   en productie in.
   woff2 eerst: de browser pakt het eerste formaat dat hij kent, en woff2 is
   flink kleiner. swap zodat tekst zichtbaar blijft tijdens het laden — en
   omdat de fallback hieronder ook blackletter is, is die tussenstand geen
   serif-flits maar een verwante letter. */
/* LET OP: deze familie levert MAAR EEN SNIT (normal/400). Vraag je er font-weight:700
   op, dan heeft de browser geen vette letter om te pakken en moet hij hem verzinnen.
   Chromium doet dat niet en negeert de 700 stilzwijgend; WebKit/Safari doet het wel,
   door de letter met een offset dubbel te tekenen. Dat is precies zo breed als het
   klinkt: op 139px werd het wordmark 15px breder en kreeg elke letter een tweede,
   harde contour — het duidelijkst op de C en de F. Omdat Chromium de 700 negeerde,
   is alles wat ooit in Chrome is goedgekeurd de 400-weergave; Safari week af, niet
   andersom. Vandaar 400 overal waar --fraktur staat. Zet er niets anders neer zonder
   een echte vette snit toe te voegen. */
@font-face{
  font-family:'Old English Text MT';
  src:url('assets/fonts/old-english-text.woff2') format('woff2'),
      url('assets/fonts/old-english-text.woff') format('woff');
  font-weight:normal;
  font-style:normal;
  font-display:swap;
}

:root{
  --ink:#0a0a0a;          /* near-black canvas */
  --paper:#ededea;        /* off-white for scanned/sticker bits */
  --white:#f3f3f0;
  --bone:#cdc9c1;         /* muted label text */
  /* Gedimde tekst. Stond op #6f6c66 (3,78:1 op --ink); dat haalde de 4,5:1
     voor kleine tekst niet, en --dim staat juist op 10-12px (review 8.2). Zo
     weinig lichter als nodig, zelfde tint: 4,62:1. */
  --dim:#7d7a73;          /* faint mono */
  --line:#2a2926;         /* hairline */
  --line-2:#444039;
  --blood:#c01818;        /* used ONLY where photos already carry red */
  /* Rood voor TEKST (foutmeldingen). --blood haalt op zwart maar 3,19:1; dit
     is dezelfde tint, zo weinig lichter als nodig: 4,62:1 (review 8.2). Randen
     en vlakken blijven --blood. */
  --blood-tekst:#e63434;

  /* Beide blackletter-tokens wijzen naar hetzelfde merkfont. Ze waren
     gesplitst toen er twee families waren — UnifrakturCook voor logo en
     koppen, Grenze Gotisch voor knoppen en namen. Die splitsing bestaat niet
     meer, maar de namen blijven staan: ze zitten op ~24 plekken en een latere
     tweede snit kan er zo weer in. UnifrakturCook blijft als reserve, zodat
     de site bij een verlopen licentie terugvalt op een blackletter in plaats
     van op de systeem-serif. Hij wordt niet vooraf geladen: zolang Old English
     laadt, haalt de browser hem nooit op. --pirata is vervallen: nul aanroepen.
     De tweede waarde in elke var() is de terugval als een layout de variabele
     niet zet; zonder die terugval is de hele font-family ongeldig. */
  --fraktur:'Old English Text MT', var(--font-blackletter, serif), serif;
  --gothic:'Old English Text MT', var(--font-blackletter, serif), serif;
  --mono:var(--font-cousine, 'Courier New'), 'Courier New', monospace; /* functional labels */


  /* corner cluster while it sits on the hero — the two knobs to tune.
     --corner-veil  panel background there; replaces the opaque --ink, so the
                    see-through comes entirely from this one value.
     --corner-dim   opacity of the nav + toggles only. The arrow is left out:
                    it is the invitation to scroll and has to stay at full
                    strength, which is also why the opacity cannot live on the
                    container — a child cannot escape its parent's opacity. */
  --corner-veil:rgba(10,10,10,.45);
  --corner-dim:.8;

  /* Gloed om de productkaarten. Als rgba neergezet en niet als alpha op een
     bestaand token, want box-shadow neemt geen losse alpha — zelfde patroon
     als --corner-veil hierboven.

     BEWUSTE UITZONDERING op de meekantel-regel: dit token kantelt NIET mee,
     hij staat in het witte thema op transparent. Zie daar voor de reden. */
  --glow:rgba(243,243,240,.10);
}

/* ---------- light theme (white canvas) ----------
   flips the core canvas/foreground tokens; everything else is built on
   var(--ink)/var(--white), so the whole raw system inverts with it.
   toggled by hcf.js (body.theme-light), remembered in sessionStorage. */
body.theme-light{
  --ink:#f1f0ec;          /* off-white canvas */
  --paper:#e0dccf;        /* taped sticker sheet, a touch darker than canvas */
  --white:#0b0b0a;        /* foreground → near-black */
  --bone:#403d34;         /* muted label text */
  --dim:#6e6c64;          /* faint mono; was #77746c (4,09:1), nu 4,61:1 (review 8.2) */
  --blood-tekst:#c01818;  /* op wit haalt het gewone rood het al: 5,4:1 */
  --line:#c9c5bb;         /* hairline */
  --line-2:#aaa69c;
  /* the veil follows the canvas: dark type needs a light panel to sit on, and
     needs more of it — a dark photo pulls a light panel towards the type. */
  --corner-veil:rgba(241,240,236,.70);
  /* Geen gloed op het witte canvas — een uitzondering, geen vergeten geval.
     Meekantelen levert hier in het ene thema het gewenste effect op en in het
     andere het tegenovergestelde: op zwart is een lichte gloed randlicht, maar
     op wit wordt een donkere gloed een slagschaduw, en dat is precies de
     iOS/Material-taal die deze site niet moet spreken. Op wit dragen de
     hairline en het canvasverschil de scheiding al. */
  --glow:transparent;
  /* --blood keeps its photo-red */
}
/* line-art logos are pre-inverted for the dark canvas — on white show as-is */
body.theme-light .invert{ filter:none; }
/* keep the scanned sticker sheet delineated on either canvas */
.paper{ border:1px solid var(--line); }

*{ box-sizing:border-box; }

html,body{ margin:0; padding:0; }

/* Contour die tekst overeind houdt terwijl de strobe eromheen flitst. Meer dan
   alleen het wordmark gebruikt hem, vandaar een token.

   STAAT OP body EN NIET OP :root, en dat is geen detail. Een custom property
   met een var() erin wordt opgelost op het element waar hij GEDECLAREERD staat.
   Op :root betekent dat: altijd de --ink van :root, dus altijd donker — ook in
   het lichte thema, want theme-light zet --ink pas om op body. Dan krijg je een
   donkere gloed om donkere letters en smeert de tekst dicht in plaats van dat
   hij oplicht. Op body lost var(--ink) wél naar de juiste kant op, want daar
   staat de omzetting van theme-light: donkere rand om een lichte letter op
   zwart canvas, lichte rand om een donkere letter op wit.

   Tekst op BEELD gebruikt hem niet — die heeft vaste waarden, zie README. */
body{ --halo:0 0 1px var(--ink), 0 0 3px var(--ink), 0 0 9px var(--ink); }

body{
  background:var(--ink);
  color:var(--white);
  font-family:var(--mono);
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  min-height:100vh;
  overflow-x:hidden;
}

/* ---------- micro type ---------- */
.mono{ font-family:var(--mono); }
.label{
  font-family:var(--mono);
  text-transform:uppercase;
  letter-spacing:.28em;
  font-size:11px;
  color:var(--bone);
}
.dim{ color:var(--dim); }

a{ color:inherit; }

/* bare zine text-link */
.tlink{
  font-family:var(--mono);
  text-transform:uppercase;
  letter-spacing:.22em;
  font-size:11px;
  color:var(--bone);
  text-decoration:none;
  padding-bottom:2px;
  border-bottom:1px solid transparent;
  transition:color .15s, border-color .15s;
}
.tlink:hover{ color:var(--white); border-bottom-color:var(--white); }

/* ---------- blackletter wordmarks ---------- */
.fraktur{ font-family:var(--fraktur); font-weight:400; line-height:1; }
.gothic{ font-family:var(--gothic); }

/* HC.F lockup rendered in type — "HC" + a dot span + "F", three flex items.
   Was H·C·F: two middots, an airy .18em gap and a lift on each dot. Both of
   those were there to serve the middot, and neither survives the period:

   - no lift. A middot hangs mid-em and had to be nudged onto the optical
     centre; the period lands on the baseline by itself, so translateY only
     pushes it off. Old English Text MT draws that period as a lozenge, not a
     round dot — that diamond is the typeface, not a rendering fault.
   - no gap, and NEGATIVE tracking. Old English is the wider face of the two:
     measured at 100px, HC.F runs 270px against UnifrakturCook's 187px, and
     its generous sidebearings made the lockup fall apart into "H C . F". The
     .04em that suited the old face works against this one. Stepped through
     .04 / 0 / -.02 / -.04 / -.06 / -.08em: at -.04em it reads as a single
     word with the period clearly between C and F, at -.06em the H and C
     start to merge and at -.08em they collide.

   - no colour of its own. The middots were separators and were dimmed to
     recede; this period is part of the name, so it inherits the wordmark's
     --white like any other character. The .dot span stays as a styling hook,
     but carries no rule now. */
/* Contour tijdens de strobe. Als PNG was dit onoplosbaar: het beeld was 85%
   transparant en na invert(1) wit, en de flits is óók wit — wit op wit, en
   elke poging het te redden vroeg om een plaat eronder. Tekst kan wel een
   eigen rand krijgen. De schaduw ligt achter de glief, dus hij vreet niets
   van de haarlijnen van de blackletter weg, en hij staat in --ink zodat hij
   met het thema meekantelt: donkere rand om een lichte letter op zwart canvas,
   lichte rand om een donkere letter op wit. Geen band, geen plaat — de letter
   houdt zichzelf overeind terwijl het eromheen flitst. */
body.strobing .hcf-type{ text-shadow:var(--halo); }
/* Ook de strobe-contour ligt op de hero vast: de flits kan daar wit of zwart
   zijn, maar de foto eronder is in beide thema's donker, dus de rand moet
   altijd donker zijn om de lichte letter eruit te lichten. */
body.strobing #hero .center .hcf-type{
  text-shadow:0 0 1px #0a0a0a, 0 0 3px #0a0a0a, 0 0 9px #0a0a0a,
              0 4px 26px rgba(0,0,0,.85);
}

.hcf-type{
  font-family:var(--fraktur);
  font-weight:400;        /* de familie heeft er maar een; zie de @font-face hierboven */
  margin:0;               /* de hero-lockup is een <h1>; die brengt eigen marges mee */
  letter-spacing:-.04em;
  line-height:.9;
  color:var(--white);
  display:inline-flex;
  gap:0;
  align-items:baseline;
}

/* inverted line-art logo image (black-on-white → white-on-black) */
.invert{ filter:invert(1) brightness(1.05) contrast(1.05); }

/* ---------- button ---------- */
.btn{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:.5em;
  min-height:48px;
  padding:.55em 1.6em;
  font-family:var(--gothic);
  font-weight:400;
  font-size:22px;
  letter-spacing:.02em;
  color:var(--white);
  background:transparent;
  border:1px solid var(--line-2);
  text-decoration:none;
  cursor:pointer;
  transition:background .15s, color .15s, border-color .15s;
}
.btn:hover{ background:var(--white); color:var(--ink); border-color:var(--white); }
.btn--solid{ background:var(--white); color:var(--ink); border-color:var(--white); }
.btn--solid:hover{ background:transparent; color:var(--white); border-color:var(--white); }

/* ---------- hairline rule ---------- */
.rule{ height:1px; background:var(--line); border:0; margin:0; }

/* ============================================================
   GRAIN + SCANLINE OVERLAY  (subtle, respects reduced-motion)
   ============================================================ */
.fx-scan, .fx-grain{
  position:fixed; inset:0; pointer-events:none; z-index:9000;
}
/* Zachte overgangen, geen harde randen. Met twee kleurstops (transparant tot
   2px, dan ineens donker tot 3px) zit er een scherpe grens in het patroon, en
   zodra de periode geen heel aantal device-pixels beslaat — bij 90% zoom op 2x
   is dat 5,4px — moet de browser die grens tússen twee pixels leggen. Dat
   afrondingsrestje verschuift van periode tot periode en dat leest als moiré.
   Drie stops met de donkerste in het midden maakt er een driehoeksgolf van:
   nergens een rand om tegenaan te botsen, dus elke zoomstand rondt hetzelfde
   af.
   De piek gaat daarbij omlaag van .5 naar .345, want een driehoek heeft een
   gemiddelde van de helft van zijn piek terwijl de oude blokgolf maar 1 van de
   3 pixels donker maakte. Zonder die correctie werd het patroon de helft
   zwaarder; met deze waarde is de gemeten gemiddelde donkerte gelijk aan
   voorheen, dus het effect oogt hetzelfde. Idem voor de grove variant (.266
   op een periode van 4px). */
.fx-scan{
  background:repeating-linear-gradient(
    0deg, rgba(0,0,0,0) 0, rgba(0,0,0,.345) 1.5px, rgba(0,0,0,0) 3px);
  opacity:.35;
  mix-blend-mode:multiply;
  animation:scan 60s linear infinite;
}
/* De drift schuift het patroon langs het pixelraster. Bleef er dan toch een
   restje beat over, dan zou die als BEWEGING lezen in plaats van als textuur —
   en dat is precies wat opvalt. Vandaar 9px per 60 seconden: 0,15px/s, ver
   onder wat je als beweging ziet, maar de laag staat niet helemaal stil.
   9px = precies 3 perioden van 3px, anders springt de lus. */
@keyframes scan{ from{ background-position:0 0; } to{ background-position:0 9px; } }

/* ---- scanlines op grofpixelige schermen --------------------------------
   Gemeten (Chromium, zelfde hero-uitsnede, 1x t/m 2x): de rasterisatie is
   dichtheidsonafhankelijk — donkerte 4,1% en amplitude 15,8% op élke dpr, en
   de periode schaalt netjes mee (3,02 device-px op 1x, 6,04 op 2x, fysiek
   dezelfde maat). De browser smeert dus niets uit; het verschil zit in de
   PIXELPITCH van het paneel. Op ~254 PPI (retina) ligt een periode van 3px
   onder wat het oog nog los ziet en leest het als textuur; op een extern
   scherm van ~100 PPI zijn de fysieke pixels groot genoeg om afzonderlijk te
   zien, dus leest dezelfde streep als een harde lijn en kan hij tegen het
   pixelraster van het paneel aan beaten — de 'vuil'-indruk.

   Pixelpitch is niet op te vragen in CSS: 1dppx is per definitie 96dpi, dus
   ook de dpi-eenheid is logisch, niet fysiek. dpr is wél een bruikbare proxy
   — 1x correleert sterk met de grovere externe schermen. Vandaar deze query.
   Ruimere periode (1 lijn per 4px i.p.v. per 3) én lagere opacity, zodat de
   fysieke indruk in de buurt komt van wat retina toont; bewust iets zachter
   dan gelijk, want dezelfde cijfers lezen daar harder. En rustiger: de drift
   is hier het meest zichtbaar, dus die halveert bijna in snelheid. */
@media (max-resolution: 1.5dppx){
  .fx-scan{
    background:repeating-linear-gradient(
      0deg, rgba(0,0,0,0) 0, rgba(0,0,0,.266) 2px, rgba(0,0,0,0) 4px);
    opacity:.26;
    animation:scan-coarse 80s linear infinite;
  }
  /* 12px = 3 perioden van 4px; zelfde 0,15px/s als de fijne variant */
  @keyframes scan-coarse{ from{ background-position:0 0; } to{ background-position:0 12px; } }
}

.fx-grain{
  z-index:9001;
  inset:-60%;
  width:220%; height:220%;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  opacity:.06;
  animation:grain .42s steps(3) infinite;
}
@keyframes grain{
  0%{ transform:translate(0,0); }
  33%{ transform:translate(-3%,2%); }
  66%{ transform:translate(2%,-2%); }
  100%{ transform:translate(-2%,-1%); }
}

/* faint vignette so type holds on busy photos */
.fx-vignette{
  position:fixed; inset:0; pointer-events:none; z-index:8999;
  background:radial-gradient(120% 120% at 50% 40%, transparent 55%, rgba(0,0,0,.55) 100%);
}

@media (prefers-reduced-motion: reduce){
  .fx-scan, .fx-grain{ animation:none; }
}

/* opt-in: push grain / scanline / vignette BEHIND the content so the photos
   and articles stay clean and the texture only lives in the canvas around them.
   used on the front page + shop (body.fx-behind). The hero is edge-to-edge
   imagery and would lose its texture that way, so it carries its own copy on
   top of the photo — see #hero .fx-scan / #hero .fx-grain below. */
body.fx-behind .fx-scan,
body.fx-behind .fx-grain,
body.fx-behind .fx-vignette{ z-index:-1; }

/* ============================================================
   HERO  (full-bleed slideshow — first section of the front page)
   Was its own page (lander.html) until index + lander were merged
   into one scrolling page. It lives here instead of inline in
   index.html so it stays a self-contained, reusable block.

   Stacking, all inside the ROOT context on purpose (#hero itself
   keeps z-index:auto, so it is NOT a stacking context):
     slides    z auto  — the photos
     #strobe   z 1     — injected on <body> by hcf.js, so the flash
                         reaches the hero and the feed alike
     .scrim    z 2     — darkening, also tempers the flash over the
                         imagery the way the old lander's opacity did
     .fx-*     z 3     — grain + scanlines over the photo
     .center   z 5     — logo + tag, stays readable through it all
   ============================================================ */
#hero{
  position:relative;      /* was fixed as a standalone page — must be in flow
                             now so the feed scrolls out from under it */
  height:100vh;           /* fallback for browsers without svh */
  height:100svh;          /* svh, not vh: the mobile address bar would clip it */
  overflow:hidden;
  background:#000;
}

/* the hero is always imagery, so it stays light-on-dark in BOTH themes —
   hence the literal values below instead of the canvas tokens. */
#hero .slides{ position:absolute; inset:0; }
#hero .slide{
  position:absolute; inset:0;
  opacity:0;
  transition:opacity 1.8s ease-in-out;
  will-change:opacity;
}
#hero .slide.on{ opacity:1; }
#hero .slide img{
  position:absolute; inset:0;
  width:100%; height:100%;
  object-fit:cover;
  object-position:center 38%;
  transform:scale(1.06);              /* slow ken-burns drift */
  transition:transform 7s linear;
  filter:grayscale(.12) contrast(1.02);
}
#hero .slide.on img{ transform:scale(1.14); }

/* darkening so the mark reads over any frame of the slideshow */
#hero .scrim{
  position:absolute; inset:0; z-index:2;
  background:
    radial-gradient(70% 50% at 50% 48%, rgba(0,0,0,.5) 0%, rgba(0,0,0,0) 70%),
    radial-gradient(110% 90% at 50% 42%, rgba(0,0,0,.15) 0%, rgba(0,0,0,.62) 70%, rgba(0,0,0,.86) 100%),
    linear-gradient(180deg, rgba(0,0,0,.45) 0%, transparent 22% 60%, rgba(0,0,0,.7) 100%);
}

/* the page runs body.fx-behind, which parks the shared texture behind the
   content. The hero would come out clean that way, so it gets its own pair
   clipped to the section, above the photo and below the logo. */
#hero .fx-scan, #hero .fx-grain{ position:absolute; z-index:3; }
#hero .fx-scan{ inset:0; }

/* centred lockup */
#hero .center{
  position:absolute; inset:0; z-index:5;
  display:flex; flex-direction:column;
  align-items:center; justify-content:center;
  text-align:center; padding:24px;
  gap:30px;
}
/* Het wordmark is tekst, geen PNG meer. De maat is afgeleid uit wat het beeld
   toonde: dat was 460px breed maar de inkt vulde daarvan maar 76,9%, dus de
   zichtbare letters waren 354px — bij deze lockup hoort daar font-size 139px
   bij. Onder de 575px viewport groeide het beeld mee als 80vw; dat is voor de
   letters 24vw, dus min() doet hier wat de oude media-query deed en er is geen
   apart breekpunt meer nodig.
   De slagschaduw blijft: die hield de letter leesbaar op elke hero-foto, en
   dat is nu net zo goed nodig. */
#hero .center .logo{
  font-size:min(139px, 24vw);
}
/* Vaste waarden, geen tokens. De hero is altijd fotografie en die foto
   kantelt NIET mee met het thema — hij blijft donker op een wit canvas. Een
   letter op var(--white) zou daar donker worden en dan staat er donker op
   donker. Zelfde reden voor de contour hieronder. Zie de regel in README.md:
   tekst op beeld ligt vast, tekst op canvas kantelt mee. */
#hero .center .hcf-type{
  color:#f3f3f0;
  text-shadow:0 4px 26px rgba(0,0,0,.85);
}
/* GEEN text-transform. Arie schrijft de tagline zelf in het Hero-veld en
   bepaalt daarmee de schrijfwijze; de CSS hoort dat niet te overrulen. Stond
   hier tot 19-09 op uppercase.

   De tracking blijft .42em, en dat is gemeten, niet gegokt: --mono is Cousine,
   een monospace, dus elke glief heeft dezelfde loopbreedte. "Hardcore —
   Feijenoord" is 235,7px breed in kapitalen en 235,7px in kleine letters —
   exact gelijk. De hero-centrering verschuift dus geen pixel en er is geen
   reden de spatiëring bij te stellen. Smaller kan wel (.26em geeft 198,7px)
   als het te luchtig oogt, maar dan wijkt deze regel af van het andere
   mono-meubilair, dat overal ruim gespatieerd staat. */
#hero .center .tag{
  font-family:var(--mono);
  letter-spacing:.42em;
  font-size:11px; color:#cdc9c1;
  margin-top:-8px;
}

/* The hero has no bottom chrome left. It held the slideshow dots and a
   "Rotterdam-Zuid" label; the dots went when the hero became something you
   scroll past rather than sit on, and the label followed — the corner cluster
   is the only furniture the hero needs. */

@media (max-width:640px){
  #hero .slide img{ object-position:center 42%; }
  #hero .center{ gap:24px; }
}
@media (prefers-reduced-motion: reduce){
  #hero .slide{ transition:opacity .4s; }
  #hero .slide img, #hero .slide.on img{ transform:none; transition:none; }
}

/* ---------- shared selection ---------- */
::selection{ background:var(--white); color:var(--ink); }

/* on-brand keyboard focus ring — keeps the raw look, drops the UA blue.
   mouse / scripted focus stays clean; keyboard focus gets a bone outline. */
:focus:not(:focus-visible){ outline:none; }
:focus-visible{ outline:2px solid var(--bone); outline-offset:2px; }

/* skinny scrollbar for the raw look */
::-webkit-scrollbar{ width:10px; height:10px; }
::-webkit-scrollbar-track{ background:#000; }
::-webkit-scrollbar-thumb{ background:#2c2a27; border:2px solid #000; }
::-webkit-scrollbar-thumb:hover{ background:#403d38; }

/* ============================================================
   STROBOSCOOP  (shared — flashing layer + toggle)
   markup is injected by hcf.js, styling lives here so every
   page gets the same behaviour. respects reduced-motion.
   ============================================================ */
#strobe{
  position:fixed; inset:0; z-index:1; pointer-events:none;
  background:#fff; display:none;
  animation:strobe .09s steps(1) infinite;
}
body.strobing #strobe{ display:block; }
@keyframes strobe{
  0%   { background:#ffffff; }
  20%  { background:#000000; }
  40%  { background:#ff1414; }
  60%  { background:#000000; }
  80%  { background:#ffffff; }
  100% { background:#000000; }
}

/* lift the standard content wrapper above the strobe so it stays readable. */
body.strobing .wrap{ position:relative; z-index:2; }

/* FRONT PAGE — the flash has to stay semi-transparent, otherwise it paints
   right over the hero slideshow and the visitor lands on a flat flickering
   rectangle: with the strobe on, an opaque layer at
   z-index 1 hides the photos completely. The .scrim above it only darkens —
   it can never bring the imagery back. The old lander solved this with
   `.stage #strobe{ z-index:3; opacity:.6 }` while the strobe lived inside the
   slideshow; now that it is one body-level layer over hero AND feed, the
   opacity moves to the page, the way the shop already does it. The feed keeps
   its full-strength flicker around the posts — those sit in solid --ink boxes,
   so only the background flash softens. */
body.page-home #strobe{ opacity:.6; }

/* SHOP — full strength, unlike the hero. Tempering exists to keep content
   readable THROUGH the flash, and on the shop nothing lies under it any more:
   each product card is one opaque block that sits above the flash, image and
   text together. What is left to flash is the space around and between those
   blocks, and that surface is small — at .6 it read as a grey border rather
   than a strobe. So no override here; #strobe keeps its own full opacity.
   The detail and cart panels are opaque and sit far above this, so they carry
   their own copy — see the strobe block in shop.html. */

/* ============================================================
   CORNER CLUSTER  (bottom-right — nav links + strobe/theme toggles)
   markup is injected by hcf.js. One fixed container holds the whole
   stack instead of positioning each element, so nothing can drift
   apart and a later block (the scroll arrow) is just one more child.
   Everything is built on the canvas tokens, so the cluster inverts
   with the theme; the solid --ink panel keeps it legible while the
   strobe flashes behind it.
   ============================================================ */
#hcf-corner{
  position:fixed; right:16px; bottom:16px; z-index:9500;
  display:flex; flex-direction:column; align-items:stretch;
  background:var(--ink);
  border:1px solid var(--line-2);
  box-shadow:0 6px 22px -8px rgba(0,0,0,.9);
}

/* nav links — same block as the toggles, but quieter: these are links,
   not buttons (lighter weight, muted until hovered, no press state). */
#hcf-corner .hcf-nav{ display:flex; flex-direction:column; }
#hcf-corner .hcf-nav a{
  display:flex; align-items:center; gap:9px;
  padding:7px 16px 8px;
  font-family:var(--gothic); font-weight:400; font-size:17px;
  letter-spacing:.02em; line-height:1.25;
  color:var(--bone); text-decoration:none;
  transition:color .15s;
}
#hcf-corner .hcf-nav a + a{ border-top:1px solid var(--line); }
/* Zelfde hover als de toggles en het pijltje: een achtergrondvlak, niet alleen
   een iets lichtere letter. Die kleurstap alleen (--bone naar --white, 205 naar
   243) las niet als hover — hij bestond wel, maar viel niet op naast de
   knoppen eronder die een heel vlak invullen. */
#hcf-corner .hcf-nav a:hover{ background:var(--line); color:var(--white); }

/* empty square that lines the labels up with the icons on the toggles
   below; it fills in on the current page. */
#hcf-corner .hcf-nav .mk{ width:12px; height:12px; display:block; flex:none; }
#hcf-corner .hcf-nav .lb{ border-bottom:1px solid transparent; }
#hcf-corner .hcf-nav a.active{ color:var(--white); }
#hcf-corner .hcf-nav a.active .mk{ background:var(--white); }
#hcf-corner .hcf-nav a.active .lb{ border-bottom-color:var(--white); }

/* Bestemming nog niet bepaald. Leest als link, gedraagt zich niet als link, en
   ziet er ook niet uit als de rest — anders lijkt het een kapotte link in plaats
   van iets dat nog moet komen. --dim kantelt met het thema mee, dus dit klopt op
   het zwarte en op het witte canvas. Het vierkantje ervoor dimt mee: dat hoort
   bij de regel, niet bij de status. */
#hcf-corner .hcf-nav a[aria-disabled="true"],
#hcf-corner .hcf-nav a[aria-disabled="true"]:hover{
  cursor:default; color:var(--dim);
  /* Nadrukkelijk GEEN hover-vlak: een gedimde link die oplicht zodra je hem
     aanraakt, belooft dat hij werkt. Dat is precies wat we niet willen zeggen
     zolang "Uit" nergens heen gaat. */
  background:none;
}

/* the toggles — rows in the same panel, so they drop their own frame and
   are separated by rules instead. the heavier rule marks the group break
   between the nav links above and the buttons below. */
#strobeToggle, #themeToggle{
  display:flex; align-items:center; gap:9px;
  background:none; color:var(--white);
  border:0; border-top:1px solid var(--line);
  font-family:var(--gothic); font-weight:400; font-size:18px;
  letter-spacing:.02em; padding:9px 16px 10px; cursor:pointer;
  text-align:left;
}
#strobeToggle{ border-top-color:var(--line-2); }
#strobeToggle:hover, #themeToggle:hover{ background:var(--line); }

#strobeToggle .st, #themeToggle .st{
  font-family:var(--mono); font-size:10px; letter-spacing:.24em;
  text-transform:uppercase; color:var(--bone);
  /* Vaste maat, anders verspringt het hele hoekcluster bij het omschakelen.
     Gemeten: ZWART is 42,02px en WIT 25,20px, en dat verschil van 16,81px sloeg
     één op één door naar de clusterbreedte (158,45 tegen 141,64). Alleen kleur
     hoort te kantelen, geen maat.

     Vijf tekens is het langste label (ZWART). ch is de tekenbreedte van de mono
     zonder de letter-spacing, dus die telt er apart bij op — CSS zet de spatie
     ook achter het laatste teken, dus vijf keer, niet vier. Bij 10px komt dat
     uit op 30 + 12 = 42px, gelijk aan de meting.

     Geldt ook voor de strobe-toggle. Daar is AAN even breed als UIT en gebeurt
     er nu niets, maar dat is toeval van gelijke tekenaantallen — één woord
     anders vertalen en het verspringt alsnog. */
  min-width:calc(5ch + 5 * 0.24em);
}

/* strobe toggle LED */
#strobeToggle .led{
  width:10px; height:10px; border-radius:50%;
  background:#332; border:1px solid #555; display:block;
}
body.strobing #strobeToggle .led{
  background:#ff1414; border-color:#ff8080;
  box-shadow:0 0 8px 2px rgba(255,20,20,.8);
  animation:led .09s steps(1) infinite;
}
@keyframes led{ 50%{ opacity:.15; } }

/* theme toggle swatch — universal split black/white light-dark mark */
#themeToggle .sw{
  width:12px; height:12px; display:block;
  border:1px solid var(--line-2);
  background:linear-gradient(90deg, #0a0a0a 0 50%, #f3f3f0 50% 100%);
}

/* the scroll arrow — third block, walks the page's sections top to bottom and
   flips to ↑ in the last one. Wider padding than the rows above it: it is the
   one control you aim for without reading. */
#hcf-arrow{
  display:flex; align-items:center; justify-content:center;
  background:none; color:var(--white);
  border:0; border-top:1px solid var(--line-2);
  font-family:var(--mono); font-size:19px; line-height:1;
  padding:11px 16px 13px; cursor:pointer;
}
#hcf-arrow:hover{ background:var(--line); }
#hcf-arrow .ar{ display:block; }

/* ---- meer / minder — inklappen op een telefoon in portret -------------
   Daar nam het volle cluster bijna de halve schermhoogte in en vocht het
   onderin de hero met het mark, de tagline en "winkel". Ingeklapt blijven
   Winkel en Strobe staan; Thuis, Uit en Thema zitten achter "Meer".

   BUITEN deze mediaquery bestaat de knop niet (display:none) en verandert er
   niets: desktop, tablet en liggend blijven exact zoals ze waren.

   Ingeklapte rijen gaan NIET op display:none maar op hoogte nul en
   visibility:hidden. Zo tellen ze mee voor de breedte, en verspringt het
   cluster niet bij het wisselen: de breedste rij (Thema ZWART) bepaalt de maat
   in beide standen. Zelfde reden voor de twee woorden in de knop, die over
   elkaar in één cel staan — zie hcf.js. visibility:hidden haalt ze ook uit de
   tabvolgorde en uit wat een schermlezer voorleest. */
#hcf-meer{ display:none; }

@media (max-width:640px) and (orientation:portrait){
  #hcf-meer{
    display:flex; align-items:center; gap:9px;
    background:none; color:var(--bone);
    border:0; border-top:1px solid var(--line);
    /* zelfde maat en register als de navlinks bovenin */
    font-family:var(--gothic); font-weight:400; font-size:17px;
    letter-spacing:.02em; line-height:1.25;
    padding:7px 16px 8px; cursor:pointer; text-align:left;
    -webkit-tap-highlight-color:transparent;
  }
  #hcf-meer:active{ background:var(--line); color:var(--white); }
  /* Rijen van 44px hoog (review 8.8): op de telefoon is dit bediening met de
     duim. Winkel en Meer waren 37px, Strobe 42px. Alleen de padding; letter en
     breedte blijven. De ingeklapte rijen (hieronder) wegen zwaarder en blijven
     op hoogte nul. */
  #hcf-corner .hcf-nav a,
  #hcf-meer{ padding-top:11px; padding-bottom:12px; }
  #strobeToggle,
  #themeToggle{ padding-top:10px; padding-bottom:11px; }
  #hcf-meer .mk{ width:12px; height:12px; display:block; flex:none; }
  #hcf-meer .mm{ display:grid; }
  #hcf-meer .mm > span{ grid-area:1 / 1; }
  #hcf-meer .mm-minder{ visibility:hidden; }
  #hcf-corner.is-open #hcf-meer .mm-meer{ visibility:hidden; }
  #hcf-corner.is-open #hcf-meer .mm-minder{ visibility:visible; }

  #hcf-corner:not(.is-open) .hcf-nav a:not([data-nav="winkel"]),
  #hcf-corner:not(.is-open) #themeToggle{
    height:0; padding-top:0; padding-bottom:0;
    border-top-width:0; overflow:hidden; visibility:hidden;
  }
  /* Winkel is ingeklapt de bovenste zichtbare rij; zijn scheidingslijn met de
     (onzichtbare) rij erboven hoort dan niet bovenaan het paneel te staan. */
  #hcf-corner:not(.is-open) .hcf-nav a[data-nav="winkel"]{ border-top-width:0; }

  /* Het pijltje in het cluster is hier alleen "terug naar boven", en alleen
     zodra je voorbij de hero bent (hcf.js zet dan .is-voorbij-hero, via de
     bestaande IntersectionObserver). Op de hero is het cluster Winkel, Strobe,
     Meer en niets anders: de hint dat er meer onder zit is daar het losse
     pijltje onderin de hero. Die twee bestaan nooit tegelijk.
     Op desktop, tablet en liggend blijft het pijltje zoals het was. */
  #hcf-corner:not(.is-voorbij-hero) #hcf-arrow{ display:none; }
}

/* ---- the cluster goes see-through while you are on the hero ----------
   There the imagery leads and scrolling is the action, so the whole block
   recedes — opacity on the cluster itself, not on the type, and the opaque
   --ink panel swaps for the semi-transparent --corner-veil. Both are needed:
   fading only the text leaves a solid black rectangle with faint letters on
   it, which is exactly as dominant as before. Together the slideshow reads
   straight through the cluster.

   Hover or keyboard focus anywhere in it brings the whole thing back at
   once — whoever moves into that corner wants the entire set of controls,
   not the single item under the pointer.

   Wrapped in (hover: hover) deliberately. Touch has no hover, so phones keep
   the cluster solid — there it is navigation, not a refinement, and a
   half-visible link is a link you tap by accident. */
#hcf-corner{ transition:background-color .15s; }
#hcf-corner > *{ transition:opacity .15s; }
@media (hover: hover){
  body.at-hero #hcf-corner{ background:var(--corner-veil); }
  body.at-hero #hcf-corner > :not(#hcf-arrow){ opacity:var(--corner-dim); }
  body.at-hero #hcf-corner:hover,
  body.at-hero #hcf-corner:focus-within{ background:var(--ink); }
  /* :not(#hcf-arrow) carries the specificity of an ID, so the rules that undo
     the fade have to repeat it or they lose to the line above. */
  body.at-hero #hcf-corner:hover > :not(#hcf-arrow),
  body.at-hero #hcf-corner:focus-within > :not(#hcf-arrow){ opacity:1; }
}
@media (prefers-reduced-motion: reduce){
  #hcf-corner, #hcf-corner > *{ transition:none; }
}

/* ---- landscape phone: the cluster lies down ---------------------------
   Stacking is an assumption, not a law. On a 360px-tall screen the column
   ran 254px — 70% of the height — and buried the wordmark. There is no
   vertical room there but plenty of width, so the same controls become one
   44px bar along the bottom.

   Full width rather than tucked bottom-right: at 568px the bar is ~470px, so
   the corner version would leave an 80px sliver of dead space and read as a
   fragment. Edge to edge it reads as what it is, and it cannot reach the
   vertically-centred wordmark at all. 44px is also a proper touch target.

   Height, not width, is the trigger — a narrow portrait phone has room to
   stack, a short landscape one does not. */
@media (orientation: landscape) and (max-height: 480px){
  #hcf-corner{
    left:0; right:0; bottom:0;
    height:44px;
    flex-direction:row; align-items:stretch;
    border:0; border-top:1px solid var(--line-2);
    box-shadow:0 -6px 22px -12px rgba(0,0,0,.9);
  }
  #hcf-corner .hcf-nav{ flex-direction:row; flex:1; }
  #hcf-corner .hcf-nav a{ flex:1; justify-content:center; padding:0 8px; }
  #hcf-corner .hcf-nav a + a{ border-top:0; border-left:1px solid var(--line); }

  #strobeToggle, #themeToggle{
    border-top:0; border-left:1px solid var(--line);
    justify-content:center; padding:0 13px;
  }
  #strobeToggle{ border-left-color:var(--line-2); }
  /* the state words go first: the LED already carries the strobe state, and
     the canvas in front of you tells you which theme is on */
  #strobeToggle .st, #themeToggle .st{ display:none; }

  #hcf-arrow{
    border-top:0; border-left:1px solid var(--line-2);
    padding:0 20px;
  }
}

/* prefers-reduced-motion: geen strobe, en dus ook geen knop ervoor (review
   8.1). Niet vertragen maar weg: .6s gaf op /shop nog 4 flitsen per seconde,
   en de eigen laag van het productdetail (shop.css) viel er helemaal buiten.
   Een mediaquery volgt de instelling ook als die verandert terwijl de pagina
   open staat; hcf.js zet dan ook body.strobing uit. Zonder de strobeknop is
   Thema de eerste knop, dus die krijgt de zwaardere scheidingslijn. */
@media (prefers-reduced-motion: reduce){
  #strobe, .detail .strobe-in, #strobeToggle{ display:none !important; }
  /* In de gate: alleen "Betreden zonder strobe", en de waarschuwing die bij "met"
     hoort gaat mee weg. */
  #hcf-gate [data-gate-enter="met"], #hcf-gate .gate-letop{ display:none !important; }
  #themeToggle{ border-top-color:var(--line-2); border-left-color:var(--line-2); }
}

/* ============================================================
   ENTRY GATE  (photosensitive-epilepsy warning overlay)
   shown once per session, then remembered in sessionStorage.
   #hcf-let-op is de waarschuwing bij de eerste keer strobe aan
   (hcf.js): hetzelfde paneel, met één regel en twee knoppen.
   ============================================================ */
body.gate-open{ overflow:hidden; }

#hcf-let-op{
  position:fixed; inset:0; z-index:9999;
  background:rgba(0,0,0,.88);
  display:flex; align-items:center; justify-content:center;
  padding:24px;
}

#hcf-gate{
  position:fixed; inset:0; z-index:9999;
  background:#000;
  display:flex; align-items:center; justify-content:center;
  padding:24px;
}
#hcf-gate .gate-panel, #hcf-let-op .gate-panel{
  width:100%; max-width:540px; text-align:center;
  background:#050505; border:1px solid var(--line-2);
  padding:44px 34px 30px;
  box-shadow:0 30px 80px -30px #000;
}
#hcf-gate .gate-mark{
  font-family:var(--fraktur); font-weight:400;
  font-size:56px; line-height:.95; letter-spacing:.02em;
  color:var(--white); margin-bottom:20px;
}
#hcf-gate .gate-body, #hcf-let-op .gate-body{
  font-family:var(--mono); font-size:12.5px; line-height:1.8;
  color:var(--bone); max-width:46ch; margin:0 auto 28px;
}
/* "Binnen wordt niet gerookt." hoort niet bij de waarschuwing erboven: het is
   een losse mededeling van het huis. Vandaar een eigen alinea met wat lucht
   ertussen, in plaats van een derde zin die eraan vast lijkt te zitten. */
#hcf-gate .gate-body--los{ margin-top:-14px; }
#hcf-gate .gate-body b{ color:var(--white); font-weight:700; }
/* De waarschuwing hoort bij de knoppen eronder, niet bij de tekst erboven:
   minder lucht naar de knoppen dan naar de rest. */
#hcf-gate .gate-letop{ margin-bottom:16px; }
#hcf-gate .gate-actions, #hcf-let-op .gate-actions{
  display:flex; gap:12px; flex-wrap:wrap; justify-content:center;
}
/* In de gate altijd onder elkaar: boven met strobe, onder zonder. Naast
   elkaar zou het op een breed scherm links/rechts worden. */
#hcf-gate .gate-actions{ flex-direction:column; align-items:center; }
#hcf-gate .gate-foot{
  margin-top:26px; padding-top:18px; border-top:1px solid var(--line);
  font-size:9px; letter-spacing:.28em;
}

/* Het paneel is altijd zwart (#000 / #050505), ook in het lichte thema — een
   waarschuwing hoort er in beide standen hetzelfde uit te zien. De tekstkleuren
   erin kantelden wél mee, en dan staat er donkere tekst op zwart: gemeten
   contrast 1,3:1, praktisch onleesbaar. Hier houden ze dus hun donkere waarden.
   Zichtbaar zodra iemand het lichte thema aanheeft en de gate opnieuw krijgt. */
body.theme-light #hcf-gate .gate-mark,
body.theme-light #hcf-gate .gate-body b,
body.theme-light #hcf-gate .gate-actions .btn,
body.theme-light #hcf-let-op .gate-actions .btn{ color:#f3f3f0; }
body.theme-light #hcf-gate .gate-body,
body.theme-light #hcf-let-op .gate-body,
body.theme-light #hcf-gate .gate-foot{ color:#cdc9c1; }
body.theme-light #hcf-gate .gate-actions .btn,
body.theme-light #hcf-let-op .gate-actions .btn{ border-color:#443f39; background:transparent; }
body.theme-light #hcf-gate .gate-actions .btn--solid,
body.theme-light #hcf-let-op .gate-actions .btn--solid{ background:#f3f3f0; color:#0b0b0a; border-color:#f3f3f0; }

@media (max-width:560px){
  /* De regelafbreking uit de tekst van Arie werkt op een breed scherm; hier
     loopt de regel toch al om en blijft "ogen." anders alleen op een regel
     achter. Smal leest het als doorlopende tekst. */
  #hcf-gate .gate-body br{ display:none; }
  #hcf-gate .gate-panel, #hcf-let-op .gate-panel{ padding:34px 22px 24px; }
  #hcf-gate .gate-mark{ font-size:42px; }
  #hcf-gate .gate-actions, #hcf-let-op .gate-actions{ flex-direction:column; }
  #hcf-gate .gate-actions .btn, #hcf-let-op .gate-actions .btn{ width:100%; }
}

/* ============================================================
   COLOFON — gedeeld door de footer van /thuis en /shop
   ============================================================
   Mono, klein, dim: furniture, hetzelfde register als "verzending uit
   Rotterdam · prijzen incl. 21% btw". Hoort bij het meubilair, niet bij de
   inhoud, dus geen eigen ruimte eromheen die er nadruk op legt.

   De markup komt uit src/components/Colofon.tsx. Die staat hier en niet in
   thuis.css of shop.css omdat dit de enige stylesheet is die beide pagina's
   laden — anders zou de regel twee keer onderhouden moeten worden. */
.colofon{
  margin:16px 0 0; font-family:var(--mono); font-size:11px;
  letter-spacing:.06em; color:var(--dim);
  /* Regelhoogte 2 en niet alleen op smalle schermen: met drie credits loopt de
     regel ook op /thuis om (de feedkolom is 760px breed), en dan zitten de
     onderlijnen van de links tegen de volgende regel aan. */
  line-height:2;
}
.colofon a{
  color:var(--bone); text-decoration:none;
  border-bottom:1px solid var(--line-2);
}
.colofon a:hover{ color:var(--white); border-bottom-color:var(--white); }
/* Een credit breekt nooit in zichzelf: alleen na een · mag de regel om. */
.colofon__stuk{ white-space:nowrap; }

@media (max-width:640px) and (orientation:portrait){
  /* Onderaan de pagina staat het hoek-cluster (met ↑ zo'n 163px hoog, 16px van
     de rand) anders over het colofon heen, precies over de credits. Zoveel
     ruimte onder de footer dat het colofon er helemaal boven uit komt. Geldt
     voor beide footers: het cluster staat op elke pagina.

     `body` ervoor is nodig, niet sierlijk: thuis.css en shop.css laden ná dit
     bestand en zetten allebei een padding-shorthand op dezelfde footer. Zonder
     dat extra stukje selector wint die shorthand en valt het cluster weer over
     de credits. */
  body footer.foot, body footer.shop-foot{
    padding-bottom:calc(env(safe-area-inset-bottom, 0px) + 212px);
  }
}
