@font-face {
  font-display: swap;
  font-family: "MetaPro";
  font-style: normal;
  font-weight: 400;
  src: url(https://cdn.egger.com/fonts/MetaPro.woff2) format("woff2");
}

@font-face {
  font-display: swap;
  font-family: "MetaPro";
  font-style: normal;
  font-weight: 500;
  src: url(https://cdn.egger.com/fonts/MetaPro-Medium.woff2) format("woff2");
}

@font-face {
  font-display: swap;
  font-family: "MetaPro";
  font-style: normal;
  font-weight: 700;
  src: url(https://cdn.egger.com/fonts/MetaPro-Bold.woff2) format("woff2");
}

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

:root {
  --egger-red: #e31937;
  --egger-red-hover: #d4082c;
  --bg: #ffffff;
  --bg-surface: #f5f5f5;
  --text: #101010;
  --text-muted: #666666;
  --border: #ddd;
  /* System Simplified-Chinese faces, named once because both the terms block
     and the Start button need them and neither MetaPro nor Arial Black carries
     CJK glyphs. Ordered macOS/iOS, older macOS, Windows, Linux; Android has
     none of them and resolves through the generic ending each stack to Noto
     Sans CJK, which is the right outcome anyway. */
  --cjk: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC";
  /* Still shallower than the 1.43x that "start 70% zoomed in" implies — the
     source is 1920 wide and a 43% upscale was visibly soft at the start of
     the pull — but raised from 1.18 to buy back per-frame motion: the pull
     covers so little ground per frame that it is sub-pixel for its whole
     length (~0.16px/frame at t=0, ~0.03px/frame by t=20s at 1.18 on a
     1387px-wide stage), and that is the regime where any whole-pixel snap or
     re-raster reads as stepping. 1.25 gives ~39% more travel per frame.
     The softness ceiling depends on how wide the stage actually is: at
     1387px (a typical windowed viewport) the 1920 source is still being
     downsampled at every point of the pull — upscaling would not begin until
     1.384 — so this costs nothing there. It only upscales on a true
     1920-wide stage (1080p fullscreen), and then by 25%, between the 18%
     that was accepted and the 43% that was not. Raise further only with a
     2880-wide re-export. */
  --slide-zoom-from: 1.25;
  /* Longer than a typical load, so the pull never finishes and sits still. */
  --slide-zoom-duration: 22s;
  --slide-fade: 1000ms;
  /* The first CONTENT slide (the second .loading-slide element — slide 1 is
     the flat lead-in, see index.html) fades in over that lead-in more slowly
     than every later crossfade, so it reads as a gradual emergence rather
     than a pop. Applied to .loading-slide:nth-of-type(2) only; every
     crossfade after it stays at --slide-fade. */
  --slide-fade-reveal: 1800ms;
}

html,
body {
  height: 100%;
  overflow: hidden;
}

body {
  font-family: "Arial Black", "Arial Bold", Gadget, sans-serif;
  background: var(--bg);
  color: var(--text);
}

/* --- Landing State --- */

#landing {
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* The terms block makes this page tall enough to overflow a short laptop
     viewport, which would put Start out of reach. `safe center` keeps the
     centring but stops it clipping the top once it scrolls; browsers without it
     fall back to the plain `center` above. */
  justify-content: safe center;
  width: fit-content;
  height: 100%;
  margin: 0 auto;
  padding: 2rem 1.5rem;
  gap: 2rem;
}

#landing .logo {
  width: 200px;
}

#landing .landing-heading {
  text-align: center;
}

#landing h1 {
  font-size: 2rem;
  line-height: 1.25;
  font-weight: 500;
  letter-spacing: 0.05em;
}

/* --- Demo title --- */
/* Names the configuration this tab is running (?title=Windows Gen6n_pro), so
   several demos open side by side stay tellable apart. */

.demo-title {
  font-size: 0.8125rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-muted);
}

#landing .demo-title {
  margin-top: 0.875rem;
}

.stream-header .demo-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 0 1rem;
}

#landing .controls {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2rem;
  margin-top: 4rem;
}

/* Only while the terms are on screen — once accepted, the landing page keeps
   its original airier spacing. */
#disclaimer:not([hidden]) ~ .controls {
  margin-top: 2rem;
}

/* --- Terms disclaimer --- */
/* Body copy inherits Arial Black from <body>, which is unreadable at paragraph
   length, so this block opts into the regular-weight brand face. */

.disclaimer {
  max-width: 34rem;
  font-family: "MetaPro", Arial, Helvetica, sans-serif;
  font-weight: 400;
  text-align: left;
}

.disclaimer-accept {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  margin-bottom: 1.5rem;
  font-size: 1rem;
  cursor: pointer;
}

.disclaimer-accept input {
  flex-shrink: 0;
  width: 1.125rem;
  height: 1.125rem;
  accent-color: var(--egger-red);
  cursor: pointer;
}

.disclaimer h2 {
  margin-bottom: 1rem;
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.4;
}

.disclaimer p {
  font-size: 0.9375rem;
  line-height: 1.55;
}

/* Neither MetaPro nor Arial carries CJK glyphs, so the Chinese terms would
   otherwise be assembled from whatever last-resort face the browser reaches
   for. Naming the system Simplified faces after MetaPro rather than before it
   keeps the Latin runs ("TrendCast") on brand — fallback is per glyph, so only
   the characters MetaPro lacks drop through. */
.disclaimer:lang(zh) {
  font-family: "MetaPro", var(--cjk), Arial, Helvetica, sans-serif;
}

/* Body copy only. CJK sets solid — no ascenders, descenders or spaces to open
   it up — so the Latin leading above reads cramped over a paragraph this long.
   The heading keeps its tighter leading: headings take less than body copy in
   CJK too, and this one is a single line at every width the block allows. */
.disclaimer:lang(zh) p {
  line-height: 1.8;
}

/* --- Buttons --- */

.btn-primary {
  background: var(--egger-red);
  color: #fff;
  border: none;
  padding: 0.75rem 2rem;
  font-family: inherit;
  font-size: 1rem;
  font-weight: 500;
  text-transform: uppercase;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.15s;
}

.btn-primary:hover {
  background: var(--egger-red-hover);
}

.btn-primary:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Every button carries its own Chinese label (`LABELS` in ui-language.js) while
   inheriting the Arial Black body face, which has no CJK either. Keyed off the
   lang attribute each button sets for itself rather than off <html lang>: that
   one is set only when the disclaimer opens, so a returning visitor — who never
   sees it — keeps the markup's English on the document all visit. The
   disclaimer rule above is the opposite case and does inherit it. Same ordering
   as that block: the display face stays first, so a future label that mixes
   Latin in keeps it, and fallback is per glyph. The uppercasing is a no-op on
   CJK, and .btn-primary's 500 lands on PingFang SC's real Medium rather than a
   synthesized weight. */
.btn-primary:lang(zh),
.btn-secondary:lang(zh),
.btn-end:lang(zh) {
  font-family: "Arial Black", "Arial Bold", var(--cjk), Gadget, sans-serif;
}

/* --- Streaming State --- */

#streaming {
  display: none;
  flex-direction: column;
  height: 100%;
}

#streaming.active {
  display: flex;
}

#landing.hidden {
  display: none;
}

/* White rather than --bg-surface. The loader now sits below this header
   instead of covering the whole viewport (see #loading), so the header is
   visible for the whole load — and --bg-surface (#f5f5f5) is one level away
   from the loader's backdrop (#f6f6f6), which made the two merge into a
   single grey field. The border-bottom keeps the edge readable against the
   backdrop and against #stream-container's white. --bg-surface itself is
   left alone: config.css uses it too. */
.stream-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1rem;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

.stream-header .logo {
  height: 28px;
}

.stream-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* Chinese swaps the font stack here — see .btn-end:lang(zh) up in Buttons. */
.btn-end {
  background: var(--egger-red);
  color: #fff;
  border: none;
  padding: 0.4rem 1rem;
  font-family: inherit;
  font-size: 0.8rem;
  text-transform: uppercase;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.15s;
}

.btn-end:hover {
  background: var(--egger-red-hover);
}

/* Same as .btn-end: the :lang(zh) override lives up in Buttons. */
.btn-secondary {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
  padding: 0.4rem 1rem;
  font-family: inherit;
  font-size: 0.8rem;
  text-transform: uppercase;
  border-radius: 4px;
  cursor: pointer;
  transition:
    background 0.15s,
    border-color 0.15s;
}

.btn-secondary:hover {
  background: var(--bg);
  border-color: var(--text-muted);
}

/* White, matching .stream-header above: this colour is what shows in the
   video's letterbox bars, so the whole stream view reads as one white field
   rather than a black box hung under a white header. */
#stream-container {
  flex: 1;
  position: relative;
  background: var(--bg);
  overflow: hidden;
}

#stream-container video {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* --- Loading Overlay --- */

/* Lives inside #stream-container (see index.html) so it shares the video's
   full box exactly — same position, same size — rather than covering the
   whole viewport including the header above it. #loading-stage positions and
   centres itself (see below, `position: absolute; inset: 0; margin: auto`)
   against this element as its containing block — that only requires #loading
   to itself be positioned, which it already is, so #loading needs no
   flex/grid centring of its own: `display: block` is enough, and
   #loading-stage is its only child besides the live region (#loading-status,
   moved out to a body-level element — see index.html — precisely so it is
   never gated by this display:none).

   Backdrop: a flat #F6F6F6 for the whole load — the exact colour of slide 1,
   the empty-room lead-in frame (see index.html and loading-slideshow.js's
   FIRST_SLIDE_MS) — so the letterbox bars around #loading-stage are
   indistinguishable from that lead-in slide at t=0. It used to animate from
   this colour to #000000 to meet #stream-container's black at handover; now
   that #stream-container is white (see above), no such fade is needed — the
   one remaining step, #F6F6F6 to the container's #FFFFFF, happens inside the
   400ms opacity crossfade of .is-dismissing below. */
#loading {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 100;
  background-color: #f6f6f6;
}

#loading.active {
  display: block;
}

/* Fades out to reveal the stream underneath, which is already live and
   composited by this point. */
#loading.is-dismissing {
  opacity: 0;
  transition: opacity 400ms ease-out;
}

/* The video's own contain-box, reproduced exactly: 16:9, capped to whatever
   space #loading actually has in each dimension, so this resolves to the
   same rectangle <video>'s `object-fit: contain` resolves to (see
   #stream-container video above) at every size — not just when both happen
   to be full-bleed. This is what the zoom (on #loading-slides, below) has to
   stay inside: because it clips at THIS box rather than at the full
   container, the letterbox bars it reveals around the edges match the
   video's at every point in the zoom, not only once the zoom finishes.
   Has to be a separate element from the scaled wrapper — scaling the clipper
   would scale the clip along with it.

   `position: absolute; inset: 0; margin: auto` is load-bearing, not
   decorative: this element's only child (#loading-slides) is itself
   `position: absolute`, so it contributes zero in-flow content. Without an
   absolute box of its own to size against, `aspect-ratio` has nothing to
   transfer a dimension from — both axes fall back to their (zero) content
   size, `max-width`/`max-height` cap that zero rather than sizing it, and
   the element resolves to 0×0 at every container size, painting nothing for
   the full 15-20s load. `inset: 0` gives it a containing block the full
   size of #loading to resolve `auto` margins and the aspect-ratio against;
   `margin: auto` on all sides is what then shrinks and centers it to the
   16:9 contain-box within that block, on both axes, the same way
   `object-fit: contain` centers the video. If you're tempted to simplify
   this back to `position: relative` — don't; that was tried, and it is
   exactly what reintroduces the 0×0 invisible loader described above. */
#loading-stage {
  position: absolute;
  inset: 0;
  margin: auto;
  aspect-ratio: 16 / 9;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  overflow: hidden;
}

/* One continuous pull-back across the whole build-up, on the wrapper rather
   than on individual slides, so swapping slides never interrupts the camera
   move. The room assembles itself as the camera draws back. */
#loading-slides {
  position: absolute;
  inset: 0;
  transform: scale(var(--slide-zoom-from));
  /* Promotes this to its own compositor layer up front, and pins the scale
     the layer is rastered at, rather than letting the browser decide both
     partway through a 22s scale animation. Chrome will composite an
     animating transform on its own, but for a *scale* it picks a raster
     scale and can re-raster when the scale drifts far enough from it — and
     each re-raster of nine stacked 1920x1080 images is a hitch in a pull
     that only moves a fraction of a pixel per frame anyway (see
     --slide-zoom-from), so a single dropped or repeated frame is a
     comparatively large fraction of the motion and shows. Declared here
     rather than on .is-running so the layer exists before the animation
     starts, not as its first act. */
  will-change: transform;
}

#loading-slides.is-running {
  animation: slide-zoom-out var(--slide-zoom-duration) ease-out forwards;
}

@keyframes slide-zoom-out {
  to {
    transform: scale(1);
  }
}

.loading-slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* #loading-stage (above) is already sized to exactly the video's 16:9
     contain-box, so the slides — themselves 16:9 — only need to fill it.
     `cover` and `contain` are equivalent at matching ratios; `cover` is used
     because it can't leave a sub-pixel bar of its own if the stage's rounded
     pixel size ever comes out a hair off 16:9, where `contain` could. */
  object-fit: cover;
  opacity: 0;
  transition: opacity var(--slide-fade) ease-in-out;
}

/* Slide 1 (the flat lead-in, first-of-type) fades in from #loading's
   backdrop, but that fade is invisible — the backdrop is the exact same
   colour (#f6f6f6) it is, by design (see #loading above). The
   emergence that actually needs to read as gradual is the one after it:
   slide 2, the first CONTENT slide, fading in over the lead-in. That's
   nth-of-type(2), not first-of-type, and it gets the slower
   --slide-fade-reveal duration. Every crossfade after this one stays at
   --slide-fade. */
.loading-slide:nth-of-type(2) {
  transition-duration: var(--slide-fade-reveal);
}

/* Slides are opaque and stack in document order, so a slide fading in simply
   covers the one before it. Nothing is ever faded back out, which means there
   is no window where two half-transparent slides show the background through
   them. */
.loading-slide.is-active {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  #loading-slides,
  #loading-slides.is-running {
    animation: none;
    transform: none;
    /* Nothing moves here, so the promoted layer --slide-zoom-from's
       will-change asks for would be paid for and never used. */
    will-change: auto;
  }

  /* .loading-slide:nth-of-type(2) (above) has higher specificity than
     .loading-slide alone, so it would otherwise keep --slide-fade-reveal
     (1800ms) here even though every other crossfade is shortened — listing
     it explicitly is what makes the shortened duration win for that slide
     too. */
  .loading-slide,
  .loading-slide:nth-of-type(2) {
    transition-duration: 200ms;
  }
}

/* --- Accessibility utilities --- */

/* Standard clip-rect pattern: keeps content in the accessibility tree (and
   therefore announceable by a screen reader) while removing it from visual
   layout entirely. `display: none` / `visibility: hidden` would hide it from
   assistive tech too, which defeats the point of a live region. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* --- Error Message --- */

#error-message {
  color: var(--egger-red);
  font-size: 0.875rem;
  min-height: 1.25rem;
}
