/* Subset + split by tools/fonts/build.sh; the -ext files only load if a character in their range is used */
@font-face{font-family:"Newsreader"; src:url(/assets/fonts/newsreader-roman.woff2) format("woff2"); font-weight:400; font-style:normal; font-display:swap; unicode-range:U+0020-007E, U+00B0}
@font-face{font-family:"Newsreader"; src:url(/assets/fonts/newsreader-roman-ext.woff2) format("woff2"); font-weight:400; font-style:normal; font-display:swap; unicode-range:U+00A0-00AF, U+00B1-00FF, U+0100-017F, U+2013-2014, U+2018-201D, U+2026}
@font-face{font-family:"Newsreader"; src:url(/assets/fonts/newsreader-italic.woff2) format("woff2"); font-weight:400; font-style:italic; font-display:swap; unicode-range:U+0020-007E, U+00B0}
@font-face{font-family:"Newsreader"; src:url(/assets/fonts/newsreader-italic-ext.woff2) format("woff2"); font-weight:400; font-style:italic; font-display:swap; unicode-range:U+00A0-00AF, U+00B1-00FF, U+0100-017F, U+2013-2014, U+2018-201D, U+2026}
@font-face{font-family:"IBM Plex Mono"; src:url(/assets/fonts/ibm-plex-mono.woff2) format("woff2"); font-weight:400; font-style:normal; font-display:swap; unicode-range:U+0020-007E, U+00B0}
@font-face{font-family:"IBM Plex Mono"; src:url(/assets/fonts/ibm-plex-mono-ext.woff2) format("woff2"); font-weight:400; font-style:normal; font-display:swap; unicode-range:U+00A0-00AF, U+00B1-00FF, U+0100-017F, U+2013-2014, U+2018-201D, U+2026}

:root{
  --bg:#EFEBE1; --ink:#15171C; --red:#B23A2E; --label:#3E434C;
  --serif:"Newsreader", Georgia, "Times New Roman", serif;
  --mono:"IBM Plex Mono", ui-monospace, Menlo, Consolas, monospace;
  --halo:0 0 2px var(--bg), 0 0 3px var(--bg), 0 0 4px var(--bg), 0 0 6px var(--bg);
}
*,*::before,*::after{box-sizing:border-box}
/* The hero section is taller than the screen; its stage stays pinned while that extra height scrolls by,
   and the scroll only drives transforms (app.js). */
html{background:var(--bg); -webkit-text-size-adjust:100%; text-size-adjust:100%; scrollbar-width:none}
/* keyboard and link scrolling glide too (wheel scrolling glides via app.js) */
@media (prefers-reduced-motion: no-preference){ html{scroll-behavior:smooth} }
html::-webkit-scrollbar{display:none}
body{margin:0; background:var(--bg); color:var(--ink); font-family:var(--serif); font-optical-sizing:auto; overscroll-behavior:none}
:focus-visible{outline:2px solid var(--red); outline-offset:3px}
.hero{position:relative; height:calc(100svh + 90svh)}
.stage{position:sticky; top:0; width:100%; height:100svh; overflow:hidden}
/* a fixed paper fringe at the screen edge: the pinned stage and the moving layers can round a fraction
   of a pixel apart, which would otherwise let a sliver of map show along the top */
.stage::after{content:""; position:absolute; top:0; right:0; bottom:0; left:0; box-shadow:inset 0 0 0 2px var(--bg); pointer-events:none}

/* ---- the map layer: one vector image plus the route and names, zoomed as a single composited layer ---- */
.world{position:absolute; left:0; top:0; width:0; height:0; will-change:transform}
/* the map itself: drawn once into this canvas by atlas-draw.js, placed and sized by app.js */
.world .atlas{position:absolute; display:block}
/* the route: three thin bars that grow by stretching (a transform, so drawing it never repaints anything);
   the city dots sit on the corners */
/* the transparent top and bottom borders keep the bar's edge inside its own layer, so it is smoothed when
   rotated (Safari leaves the outer edge of a rotated layer jagged) */
/* --s (set by app.js) scales the fine details with the map, so line weights and small labels keep the
   same proportions at any browser zoom or screen scaling */
.seg{position:absolute; height:calc(4px + 2.2px * var(--s, 1)); margin-top:calc(-2px - 1.1px * var(--s, 1)); border:2px solid transparent; border-width:2px 0; background:linear-gradient(var(--red), var(--red)) padding-box; transform-origin:0 50%; will-change:transform}
.place{position:absolute; left:calc(var(--x) * var(--u) * 1px); top:calc(var(--y) * var(--u) * 1px); transform:translate(calc(4px * var(--s, 1)), calc(-100% - 2px * var(--s, 1)));
  font-size:calc(11px * var(--s, 1)); line-height:1; color:var(--label); white-space:nowrap; text-shadow:var(--halo)}
.place.big{font-size:calc(13px * var(--s, 1))}
.world[data-density="mid"] .place.small, .world[data-density="low"] .place:not(.big){display:none}
.pin{position:absolute; left:calc(var(--x) * var(--u) * 1px); top:calc(var(--y) * var(--u) * 1px); width:0; height:0; will-change:transform}
.pin .dot, .pin .ring{position:absolute; border-radius:50%; will-change:transform, opacity}
.pin .dot{--d:calc(7px * var(--s, 1)); left:calc(var(--d) / -2); top:calc(var(--d) / -2); width:var(--d); height:var(--d); background:var(--red)}
.pin .ring{--d:calc(19px * var(--s, 1)); left:calc(var(--d) / -2); top:calc(var(--d) / -2); width:var(--d); height:var(--d); border:calc(1.4px * var(--s, 1)) solid var(--red)}
.pin .name{position:absolute; font-size:calc(var(--city, 20px) * var(--s, 1)); line-height:1; white-space:nowrap; text-shadow:var(--halo); will-change:transform, opacity}
.pin.leuven .name{left:calc(15px * var(--s, 1)); bottom:calc(3px * var(--s, 1))}
.pin.lausanne .name, .pin.zagreb .name{right:calc(-12px * var(--s, 1)); top:calc(13px * var(--s, 1))}

/* ---- the frame: paper margins and a thin neatline, opened by scrolling ---- */
/* four paper strips that slide outwards and four hairlines that slide and stretch: only cheap transforms while scrolling */
.frame{position:absolute; top:0; right:0; bottom:0; left:0; pointer-events:none}
.frame i{position:absolute; will-change:transform}
.frame .bar{background:var(--bg)}
.frame .line{background:var(--ink)}
.deg{position:absolute; pointer-events:none; will-change:transform}
.deg .in{position:absolute; top:0; right:0; bottom:0; left:0}
.deg span{position:absolute; font-size:max(9px, 10px * var(--s, 1)); line-height:1; color:var(--label); white-space:nowrap; will-change:transform, opacity}
.names{position:absolute; top:0; right:0; bottom:0; left:0; will-change:opacity}
.deg.top span, .deg.bottom span{top:50%; transform:translate(-50%, -50%)}
.deg.left span{left:50%; transform:translate(-50%, -50%) rotate(-90deg)}
.deg.right span{left:50%; transform:translate(-50%, -50%) rotate(90deg)}

/* ---- wordmark: placed in CSS so it never moves once the script runs (app.js uses the same numbers) ---- */
.intro{position:absolute; z-index:1; --m0:clamp(22px, 4.5vmin, 60px)}
@media (min-aspect-ratio: 5/4){ .intro{--h:min(7.8vw, 13.8svh); left:max(calc(var(--m0) + 24px), min(6.7vw, 11.9svh)); top:50%; transform:translateY(-50%)} }
@media (max-aspect-ratio: 5/4){ .intro{--h:min((100vw - 2 * var(--m0) - 30px) * .17, 14svh); left:calc(var(--m0) + max(12px, 3.5vw)); top:calc(var(--m0) + max(20px, 5svh))} }
h1{margin:0; font-weight:400; font-size:var(--h); line-height:1; letter-spacing:-0.027em; white-space:nowrap}
/* optical left edge: the C's bowl sits on the U's stem (0.106em of the wordmark; C ink starts at 0.07em of this),
   overshooting it by a hair the way round letters do */
.soon{margin:calc(var(--h) * .14) 0 0 calc(var(--h) * 0.106 - 0.085em); font-family:var(--mono); font-size:max(0.9375rem, var(--h) * 0.2); line-height:1.2;
  letter-spacing:0.14em; text-transform:uppercase; color:var(--red)}
.sr{position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap}
/* browsers from before the small-viewport units (Chrome 108, Safari 15.4, Firefox 101, all 2022): the same in vh */
@supports not (height:1svh){
  .hero{height:calc(100vh + 90vh)} .stage{height:100vh}
  @media (min-aspect-ratio: 5/4){ .intro{--h:min(7.8vw, 13.8vh); left:max(calc(var(--m0) + 24px), min(6.7vw, 11.9vh))} }
  @media (max-aspect-ratio: 5/4){ .intro{--h:min((100vw - 2 * var(--m0) - 30px) * .17, 14vh); top:calc(var(--m0) + max(20px, 5vh))} }
}

/* until app.js has measured the screen, show the wordmark on paper only */
/* the map, frame and degree labels show once the map is drawn; the route and the cities wait for the entrance */
html:not(.laid) .world, html:not(.laid) .frame, html:not(.laid) .deg{visibility:hidden}
html:not(.ready) .seg, html:not(.ready) .pin{visibility:hidden}

/* ---- the one-time entrance: the line draws, each city pops in as it arrives, then "Coming soon" is typed ---- */
@media (prefers-reduced-motion: no-preference){
  /* the line reaches each city at --t: 0.5s, 1.51s, 1.84s (from the side lengths and the draw easing) */
  .leuven{--t:.5s} .lausanne{--t:1.51s} .zagreb{--t:1.84s}
  .ready .pin .dot{animation:pop .45s cubic-bezier(.3,1.8,.5,1) calc(var(--t) - .06s) both}
  .ready .pin .ring{animation:pop-ring .55s cubic-bezier(.3,1.6,.5,1) calc(var(--t) + .06s) both}
  .ready .pin .name{animation:rise .45s cubic-bezier(.2,.7,.2,1) calc(var(--t) + .12s) both}
  @keyframes pop{from{opacity:0; transform:scale(0)} to{opacity:1; transform:none}}
  @keyframes pop-ring{from{opacity:0; transform:scale(.37)} to{opacity:1; transform:none}}
  @keyframes rise{from{opacity:0; transform:translateY(10px)} to{opacity:1; transform:none}}

  .soon{--adv:calc(1ch + 0.14em); display:flex; align-items:center}
  .soon span{display:block; overflow:hidden; white-space:nowrap; width:0; animation:type 2.09s step-end 3.1s forwards}
  .soon::after{content:""; width:.62em; height:1.05em; margin-left:.08em; background:currentColor; opacity:0; animation:caret 5.24s step-end 3.1s forwards}
  @keyframes type{0%{width:0} 47.85%{width:calc(1 * var(--adv))} 52.15%{width:calc(2 * var(--adv))} 55.98%{width:calc(3 * var(--adv))}
    61.24%{width:calc(4 * var(--adv))} 64.11%{width:calc(5 * var(--adv))} 68.42%{width:calc(6 * var(--adv))} 77.51%{width:calc(7 * var(--adv))}
    81.34%{width:calc(8 * var(--adv))} 85.17%{width:calc(9 * var(--adv))} 88.52%{width:calc(10 * var(--adv))} 94.26%{width:calc(11 * var(--adv))} 100%{width:calc(11 * var(--adv))}}
  @keyframes caret{0%{opacity:1} 8.59%{opacity:0} 17.18%{opacity:1} 48.47%{opacity:0} 57.06%{opacity:1} 65.65%{opacity:0} 74.24%{opacity:1} 82.82%{opacity:0} 91.41%{opacity:1} 100%{opacity:0}}
}
