/* heartblicke — Stories Tell Us · landing page
   One screen, no scrolling. Everything scales with the viewport. */

/* ---------- Font (self-hosted, no Google request) ---------- */

@font-face {
  font-family: "Sen";
  src: url("assets/Sen-Variable.woff2") format("woff2");
  font-weight: 400 800;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Playfair Display";
  src: url("assets/PlayfairDisplay-Italic-Variable.woff2") format("woff2");
  font-weight: 400 900;
  font-style: italic;
  font-display: swap;
}

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

:root {
  --blue: #0890D1;
  --ink: #06222F;
  --white: #FFFFFF;
  --gold: #F0B429;
  --shade: rgba(4, 44, 68, 0.3);
  --shade-strong: rgba(4, 44, 68, 0.38);
  /* Film stock: the page's own deep blue rather than black, sheer enough
     that the building still reads through the frame */
  --strip: rgba(4, 44, 68, 0.55);
  /* Reach of the lamp held behind the film strips */
  --lamp-r: clamp(90px, 13vw, 260px);
  /* Sprocket holes: light, but held back from full white so the band
     settles into the page instead of cutting a hole in it */
  --perf-light: rgba(255, 255, 255, 0.5);

  --pad-x: clamp(20px, 5vw, 72px);

  /* Big words: grow with width, but never taller than the screen allows */
  --word: clamp(52px, min(9vw, 12.5vh), 170px);
  --date: clamp(28px, min(4vw, 6.2vh), 72px);
}

/* ---------- Base ---------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
  margin: 0;
}

body {
  font-family: "Sen", "Trebuchet MS", system-ui, sans-serif;
  color: var(--ink);
  background-color: var(--blue);
  background-image:
    linear-gradient(180deg, rgba(8, 144, 209, 0.62), rgba(4, 44, 68, 0.72)),
    url("assets/landing-building-background.jpg"),
    url("assets/landing-texture.jpg");
  background-size: cover, cover, 240px 240px;
  background-position: center, center, top left;
  background-repeat: no-repeat, no-repeat, repeat;
  background-blend-mode: normal, luminosity, normal;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Mobile browsers: keep our own type scale, no flash on tap */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
}

a {
  color: inherit;
  text-decoration: none;
  /* No double-tap-zoom pause when tapping the big words */
  touch-action: manipulation;
}

a:focus-visible {
  outline: 3px solid var(--white);
  outline-offset: 5px;
  border-radius: 4px;
}

/* ---------- Front light ---------- */
/* The same lamp that backlights the film, seen from the front: off the
   strips it falls on the printed photo, where it only warms what is
   already there. It is painted under the strips, so near the film its
   light also shows faintly through the stock. The script moves it and
   switches it on; with no script it never appears. */
.spill {
  position: fixed;
  top: 0;
  left: 0;
  width: calc(var(--lamp-r) * 4.2);
  height: calc(var(--lamp-r) * 3.4);
  pointer-events: none;
  background: radial-gradient(
    closest-side,
    rgba(255, 178, 118, 0.36) 0%,
    rgba(244, 128, 74, 0.26) 20%,
    rgba(212, 80, 46, 0.15) 44%,
    rgba(150, 46, 36, 0.06) 70%,
    transparent 100%
  );
  mix-blend-mode: screen;
  opacity: 0;
  transition: opacity 0.45s cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform;
}

.spill.is-on {
  opacity: 1;
}

@media (pointer: coarse) {
  .spill {
    display: none;
  }
}

/* ---------- Page frame ---------- */

.page {
  height: 100vh;
  height: 100dvh;
  display: grid;
  grid-template-rows: auto auto 1fr auto;
  padding:
    env(safe-area-inset-top, 0px)
    env(safe-area-inset-right, 0px)
    env(safe-area-inset-bottom, 0px)
    env(safe-area-inset-left, 0px);
}

/* ---------- Header ---------- */

.top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: clamp(64px, 9.8vh, 96px);
  padding: clamp(10px, 2vh, 18px) var(--pad-x) 0;
}

.brand img {
  display: block;
  height: clamp(36px, 5.6vh, 58px);
  width: auto;
}

/* EN / DE: two small frames, the current language lit */
.lang-toggle {
  display: flex;
  gap: 4px;
}

.lang-toggle button {
  min-width: 2.6em;
  padding: 0.5em 0.55em 0.42em;
  border: 1.5px solid rgba(255, 255, 255, 0.55);
  border-radius: 3px;
  background: transparent;
  color: var(--white);
  font: 800 clamp(13px, 1.05vw, 16px) / 1 "Sen", "Trebuchet MS", system-ui, sans-serif;
  letter-spacing: 0.1em;
  cursor: pointer;
  transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}

.lang-toggle button[aria-pressed="true"] {
  border-color: var(--gold);
  background: var(--gold);
  color: var(--blue-deep, #063b5a);
}

.lang-toggle button:not([aria-pressed="true"]):hover {
  border-color: var(--white);
}

/* ---------- Hero ---------- */

.hero {
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  padding: clamp(12px, 3vh, 36px) var(--pad-x);
}

/* ---------- Film stock ---------- */
/* One length of film, shared by the date band and by each of the two
   links below it. Anything that wants to be a strip wears this and then
   lays out its own contents.

   The holes are two rows of gradient stops rather than artwork, so they
   stay sharp at any width and cost nothing to load. They keep their own
   scale instead of following the type, so a strip still reads as film
   whether it carries the dates or a word twice their size. There is no
   border: film has no white edge, the perforations draw it. */

.datestrip,
.reel {
  position: relative;
  --perf-w: clamp(6px, 0.56vw, 12px);
  --perf-h: clamp(7px, 0.66vw, 14px);
  --perf-gap: clamp(7px, 0.64vw, 14px);
  --perf-inset: clamp(4px, 0.42vw, 9px);
  /* What a row of perforations needs, with the type kept clear of it */
  --perf-band: calc(
    var(--perf-inset) * 2 + var(--perf-h) + clamp(2px, 0.6vh, 10px)
  );

  background-color: var(--strip);
  background-image:
    repeating-linear-gradient(
      90deg,
      transparent 0 var(--perf-gap),
      var(--perf-light) var(--perf-gap) calc(var(--perf-gap) + var(--perf-w))
    ),
    repeating-linear-gradient(
      90deg,
      transparent 0 var(--perf-gap),
      var(--perf-light) var(--perf-gap) calc(var(--perf-gap) + var(--perf-w))
    );
  background-size: 100% var(--perf-h);
  background-position:
    0 var(--perf-inset),
    0 calc(100% - var(--perf-inset));
  background-repeat: no-repeat;
}

/* The lamp. A light held behind the film and moved along it, so hovering
   reads as backlit stock rather than a band flipping colour. The JS below
   eases --lx / --ly after the pointer; with no script it blooms from the
   middle.

   The light falls off smoothly, like a real source: a hot, near-white core
   that burns out, then amber, then the safelight red bleeding into the
   dark. The pool is wider than tall because the light spreads along the
   strip.

   ::before is the glow through the emulsion. ::after is the light coming
   straight through the sprocket holes: the same pool, masked to the holes,
   added on top (screen) and much brighter, since nothing stands between
   them and the lamp. */
.datestrip::before,
.reel::before,
.datestrip::after,
.reel::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0;
  /* Slow enough that the light looks like it warms up rather than switches */
  transition: opacity 0.45s cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: none;
  mix-blend-mode: screen;
}

.datestrip::before,
.reel::before {
  mix-blend-mode: normal;
  background:
    radial-gradient(
      ellipse calc(var(--lamp-r) * 0.7) calc(var(--lamp-r) * 0.5) at var(--lx, 50%) var(--ly, 50%),
      rgba(255, 238, 208, 0.85) 0%,
      rgba(255, 196, 128, 0.55) 30%,
      rgba(255, 150, 80, 0.18) 65%,
      transparent 100%
    ),
    radial-gradient(
      ellipse calc(var(--lamp-r) * 1.9) calc(var(--lamp-r) * 1.4) at var(--lx, 50%) var(--ly, 50%),
      rgba(238, 112, 54, 0.9) 0%,
      rgba(220, 86, 42, 0.82) 14%,
      rgba(186, 58, 32, 0.66) 30%,
      rgba(146, 34, 24, 0.46) 48%,
      rgba(110, 22, 20, 0.26) 66%,
      rgba(80, 14, 16, 0.1) 84%,
      transparent 100%
    );
}

.datestrip::after,
.reel::after {
  background: radial-gradient(
    ellipse calc(var(--lamp-r) * 1.6) calc(var(--lamp-r) * 1.1) at var(--lx, 50%) var(--ly, 50%),
    rgba(255, 252, 240, 1) 0%,
    rgba(255, 226, 170, 0.95) 14%,
    rgba(255, 168, 96, 0.7) 32%,
    rgba(214, 84, 44, 0.38) 55%,
    transparent 85%
  );
  -webkit-mask-image:
    repeating-linear-gradient(90deg, transparent 0 var(--perf-gap), #000 var(--perf-gap) calc(var(--perf-gap) + var(--perf-w))),
    repeating-linear-gradient(90deg, transparent 0 var(--perf-gap), #000 var(--perf-gap) calc(var(--perf-gap) + var(--perf-w)));
  mask-image:
    repeating-linear-gradient(90deg, transparent 0 var(--perf-gap), #000 var(--perf-gap) calc(var(--perf-gap) + var(--perf-w))),
    repeating-linear-gradient(90deg, transparent 0 var(--perf-gap), #000 var(--perf-gap) calc(var(--perf-gap) + var(--perf-w)));
  -webkit-mask-size: 100% var(--perf-h);
  mask-size: 100% var(--perf-h);
  -webkit-mask-position: 0 var(--perf-inset), 0 calc(100% - var(--perf-inset));
  mask-position: 0 var(--perf-inset), 0 calc(100% - var(--perf-inset));
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* The script lights a strip whenever the lamp's pool reaches it, even
   with the pointer just off the film; without it, hover does the job. */
.datestrip.is-lit::before,
.reel.is-lit::before,
.datestrip.is-lit::after,
.reel.is-lit::after,
html:not(.has-lamp) .datestrip:hover::before,
html:not(.has-lamp) .reel:hover::before,
html:not(.has-lamp) .datestrip:hover::after,
html:not(.has-lamp) .reel:hover::after {
  opacity: 1;
}

/* The type is printed on the film, so the lamp lights it from behind too:
   a soft warm halo rather than a hard outline. */
.datestrip > *,
.reel .word--action {
  transition: filter 0.45s cubic-bezier(0.22, 1, 0.36, 1), transform 0.22s ease;
}

.datestrip.is-lit > *,
.reel.is-lit .word--action,
html:not(.has-lamp) .datestrip:hover > *,
html:not(.has-lamp) .reel:hover .word--action {
  filter:
    drop-shadow(0 0 0.18em rgba(255, 196, 130, 0.45))
    drop-shadow(0 0.046em 0 var(--shade-strong));
}

/* Whatever a strip carries rides above the lamp, so the light washes the
   film without washing out the words printed on it. */
.datestrip > *,
.reel > * {
  position: relative;
  z-index: 1;
}

/* ---------- Date strip ---------- */
/* Full-bleed between the sponsors and the headline. */

.datestrip {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  /* justify-content centres the row; this centres the text inside a child
     that takes a whole line of its own — "2026" on phones. */
  text-align: center;
  column-gap: 0.6em;
  padding-inline: var(--pad-x);
  padding-block: var(--perf-band);
  margin-top: clamp(6px, 1.4vh, 16px);
  font-size: var(--date);
  font-weight: 800;
  line-height: 1.1;
  text-transform: uppercase;
}


.datestrip:focus-visible {
  outline-offset: -5px;
}

.dates__run {
  color: var(--gold);
  letter-spacing: 0.16em;
  text-shadow: 0 0.05em 0 var(--shade-strong);
}

/* "2026" and the ampersand set in the same face as the dates, so the
   whole line reads as one date */
.dates__year,
.dates__amp {
  color: var(--gold);
  letter-spacing: 0.16em;
  text-shadow: 0 0.05em 0 var(--shade-strong);
}

/* STORIES / TELL / US */

/* The headline owns its own space: it floats in the middle of the hero,
   clear of the links anchored at the bottom (see .actions). All three
   words share one centre line — a stack, not a staircase. */
.words {
  margin: clamp(16px, 3.4vh, 40px) 0 auto;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  font-size: var(--word);
  font-weight: 800;
  line-height: 0.86;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.word {
  display: inline-block;
  color: var(--white);
  filter: drop-shadow(0 0.046em 0 var(--shade));
}

/* Watercolour fill from the poster, where the browser supports it */
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .word {
    background-image: url("assets/landing-cloud.jpg");
    background-size: 6em 6em;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
  }
}

.word--stories {
  background-position: 0 0;
}

/* Centring is handled once on .words. All each word carries here is its
   own window onto the watercolour, so the fill keeps moving down and
   across the phrase instead of repeating identically three times. */
.word--tell {
  background-position: -2.2em -1.7em;
}

.word--us {
  background-position: -0.8em -3.4em;
}

/* ---------- Headline flashlight ---------- */
/* A circle that inverts whatever it passes over, background and type
   alike. It belongs to STORIES TELL US alone — the script above only
   switches it on inside that block, so it never crosses a film strip
   and turns the safelight red to cyan. */

.lens {
  position: fixed;
  top: 0;
  left: 0;
  width: clamp(150px, 15vw, 280px);
  aspect-ratio: 1;
  border-radius: 50%;
  pointer-events: none;
  z-index: 5;
  opacity: 0;
  transition: opacity 0.3s ease;
  backdrop-filter: invert(1);
  -webkit-backdrop-filter: invert(1);
}

.lens.is-on {
  opacity: 1;
}

@media (pointer: coarse), (prefers-reduced-motion: reduce) {
  .lens {
    display: none;
  }
}

/* ---------- Programme / registration ---------- */

/* Each link rides its own length of film. The pair bookends the foot of
   the page — one strip cut by the left edge, one by the right — so both
   read as film running on past the screen. Anchored to the bottom of the
   hero so the pair sits as its own unit, clear of the headline above. */
.actions {
  align-self: stretch;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  row-gap: 0.4em;
  /* Floor for the daylight between the strips, should they ever wrap */
  column-gap: clamp(16px, 2.2vw, 56px);
  margin-top: auto;
  margin-bottom: clamp(18px, 4.4vh, 56px);
  margin-inline: calc(var(--pad-x) * -1);
  line-height: 1.05;
}

/* The strip is the reel; the link inside carries the padding, so the
   whole strip is one hit area and hovering anywhere on it reacts. */
.reel {
  display: flex;
}

/* The outer ends of the pair keep the page's text margin, so the words
   line up with STORIES and with the dates above them even though the
   film itself runs to the screen edge. */
.reel:first-child .word--action {
  padding-left: var(--pad-x);
}

.reel:last-child .word--action {
  padding-right: var(--pad-x);
}

.word--action {
  font-family: "Playfair Display", Georgia, "Times New Roman", serif;
  font-style: italic;
  font-weight: 500;
  /* Big: the pair runs almost the full width on one line */
  font-size: min(6.5vw, 10.5vh, 140px);
  letter-spacing: 0;
  white-space: nowrap;
  /* Clear of the perforations, plus room for the italic's descenders */
  padding-block: calc(var(--perf-band) + 0.08em);
  padding-inline: 0.36em;
  background-position: -1.6em -2.4em;
  transition: transform 0.22s ease, filter 0.22s ease;
}

/* On touch screens the links are the page's main controls:
   pad them out so each one is a comfortable tap target. The strip
   grows with them, so the tap area and the film stay the same shape. */
@media (pointer: coarse) {
  .word--action {
    padding-block: calc(var(--perf-band) + 0.24em);
  }
}

/* Every word lifts a little under the pointer, TELL included — the
   phrase is one gesture, so the middle word shouldn't sit dead while
   the two either side of it answer. TELL stays a span, not a link:
   it lifts, but it keeps the default cursor and makes no promise. */
.word {
  transition: transform 0.18s ease, filter 0.18s ease;
}

.word:hover {
  transform: translateY(-0.03em);
  filter: drop-shadow(0 0.075em 0 var(--shade-strong));
}

/* ---------- Entrance ---------- */
/* On load the phrase builds up word by word, then the links follow */

@keyframes rise-in {
  from {
    opacity: 0;
    transform: translateY(18px);
  }
}

.words .word,
.actions {
  animation: rise-in 0.8s cubic-bezier(0.22, 1, 0.36, 1) backwards;
}

.word--stories {
  animation-delay: 0.15s;
}

.word--tell {
  animation-delay: 0.6s;
}

.word--us {
  animation-delay: 1.05s;
}

/* The links come in while "US" is still rising, so the sequence
   lands fast instead of trailing off */
.actions {
  animation-delay: 0.5s;
}

/* ---------- Legal links ---------- */

.legal {
  display: flex;
  justify-content: flex-end;
  gap: 1.5em;
  padding: 0 var(--pad-x) clamp(12px, 2.4vh, 22px);
  font-size: clamp(13px, 1vw, 15px);
  font-weight: 700;
}

.legal a:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ---------- Phones and portrait tablets ---------- */

@media (max-width: 900px) and (orientation: portrait) {
  :root {
    --pad-x: clamp(18px, 5.6vw, 32px);
    --word: clamp(44px, min(18.5vw, 9.2vh), 110px);
    --date: clamp(22px, 7.6vw, 40px);
  }

  .top {
    flex-wrap: nowrap;
    column-gap: 8px;
    min-height: 0;
    padding-top: 10px;
  }

  .brand img {
    height: clamp(32px, 9vw, 44px);
  }

  .dates__year {
    flex-basis: 100%;
  }

  .dates__run {
    letter-spacing: 0.06em;
  }

  /* The phrase is a centered stack at every width now, so phones need
     no override for it — see .words. */

  /* Headline and links read as one centered group, close together */
  .words {
    margin-bottom: 0;
  }

  /* Two strips stacked, each running the full width of the screen —
     the same full bleed the date strip gets up top. */
  .actions {
    flex-direction: column;
    align-self: stretch;
    align-items: stretch;
    row-gap: clamp(10px, 1.6vh, 22px);
    margin-top: clamp(16px, 3.5vh, 44px);
    margin-bottom: 0;
    margin-inline: calc(var(--pad-x) * -1);
  }

  /* Bigger and heavier on phones. Sized to the line: the longest
     word ("Full programme") fills the text width exactly and
     shrinks with the screen instead of running off it. The link
     fills its strip so the whole strip stays one tap target. */
  .word--action {
    flex: 1;
    text-align: center;
    font-size: clamp(22px, 12.3vw, 56px);
    font-weight: 700;
    padding-inline: 0.1em;
  }

  /* Both strips run the full width here, so the words centre in them
     instead of hugging the page's text margin the way they do when the
     pair sits side by side. */
  .reel:first-child .word--action,
  .reel:last-child .word--action {
    padding-inline: 0.1em;
  }

  /* One even filter across the whole screen, top to bottom.
     The photo runs lighter in the middle of its frame, so the
     overlay's alpha is tuned per band to even out the result. */
  body {
    background-image:
      linear-gradient(
        180deg,
        rgba(4, 44, 68, 0.78),
        rgba(4, 44, 68, 0.89) 50%,
        rgba(4, 44, 68, 0.8)
      ),
      url("assets/landing-building-background.jpg"),
      url("assets/landing-texture.jpg");
  }

  .legal {
    justify-content: flex-start;
  }
}

/* ---------- Very short screens (e.g. phones in landscape) ---------- */
/* Compress the scale so the page still fits one screen —
   never scrollable, even on short screens. */

@media (max-height: 520px) {
  :root {
    --word: clamp(40px, min(9vw, 13.5vh), 130px);
    --date: clamp(20px, min(3vw, 5.2vh), 44px);
  }

  .top {
    min-height: 0;
  }

  /* The side-by-side pair stays on one line and fits the low screen */
  .word--action {
    font-size: calc(var(--word) * 0.55);
  }
}

/* ---------- Motion preferences ---------- */

@media (prefers-reduced-motion: reduce) {
  .word,
  .datestrip::before,
  .reel::before {
    transition: none;
  }

  .word:hover {
    transform: none;
  }

  .words .word,
  .actions {
    animation: none;
  }
}

/* ---------- Partners and funders ---------- */
/* The last row of the page frame: four labelled groups of white marks
   on the photo, the same set that closes every inner page. */

.site-sponsors {
  display: grid;
  grid-template-columns: repeat(4, auto);
  justify-content: space-between;
  align-items: start;
  gap: 18px 32px;
  padding: clamp(14px, 2.4vh, 26px) var(--pad-x) clamp(14px, 2.4vh, 24px);
  border-top: 1px solid rgba(255, 255, 255, 0.18);
}

.site-sponsors__group {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.site-sponsors__label {
  color: rgba(255, 255, 255, 0.72);
  font-size: clamp(10px, 0.8vw, 12px);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.site-sponsors__logos {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 22px;
}

.site-sponsors__logos a {
  display: block;
  line-height: 0;
  filter: drop-shadow(0 2px 0 var(--shade));
}

.site-sponsors__logos a:hover,
.site-sponsors__logos a:focus-visible {
  opacity: 0.8;
}

.site-sponsors__logo {
  display: block;
  width: auto;
  max-width: 100%;
  height: clamp(32px, 4.6vh, 46px);
}

.site-sponsors__logo--fakk { height: clamp(48px, 6.8vh, 68px); }
.site-sponsors__logo--karlstor { height: clamp(40px, 5.8vh, 58px); }
.site-sponsors__logo--zfkt { height: clamp(46px, 6.5vh, 65px); }
.site-sponsors__logo--heidelberg { height: clamp(28px, 4vh, 40px); }
.site-sponsors__logo--stura { height: clamp(38px, 5.6vh, 56px); }

@media (max-width: 900px) and (orientation: portrait) {
  /* Phones scroll a little rather than crushing the phrase to fit the
     partners row. That takes lifting the one-screen lock on the body, and
     letting the root grow with the page so the photo backdrop runs all the
     way down instead of stopping at the first screen. */
  html,
  body {
    height: auto;
  }

  body {
    overflow: visible;
  }

  .page {
    height: auto;
    min-height: 100vh;
    min-height: 100dvh;
  }

  .site-sponsors {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    justify-content: start;
    gap: 16px 24px;
    margin-top: clamp(16px, 3vh, 32px);
  }
}

@media (max-height: 520px) {
  .site-sponsors { display: none; }
}
