/* Interface motion, driven by motion.js. Load last. Without the script none of
   these states are set, and the interface is the static one. */

/* Header: it stays in front while scrolling. Wide layouts compact it as the page
   starts to move (it sticks partly above the viewport while its contents move
   down to stay centred) and fade in a glass that blends into the content
   below. Narrow layouts step it aside while reading down and bring it back on
   any scroll up. */
.site-header {
  --header-collapse:32px;
  --header-steps-aside:0
}

@media(min-width:1500px) {
  .site-header {
    --header-collapse:50px
  }
}

@media(max-width:1220px) {
  .site-header {
    --header-collapse:26px
  }
}

@media(max-width:1050px) {
  .site-header {
    --header-collapse:0px;
    --header-steps-aside:1
  }
}

/* Compaction moves the header's contents with the scroll, so reduced motion
   keeps the full-height bar. */
@media(prefers-reduced-motion:reduce) {
  .site-header {
    --header-collapse:0px
  }
}

html {
  scroll-padding-top:calc(var(--header-offset,8px) + 20px)
}

.site-header[data-motion] {
  position:sticky;
  top:calc(var(--header-collapse) * -1);
  z-index:50
}

.site-header[data-motion]>* {
  translate:0 calc(var(--header-shift,0) * .5px)
}

/* The glass, then a short unblurred fall-off below it. (A mask would give one
   smooth fade, but Chromium then paints the masked layer beneath the page.) */
.site-header[data-motion]::before,.site-header[data-motion]::after {
  content:'';
  position:absolute;
  z-index:-1;
  left:calc(50% - var(--page-width,100vw) / 2);
  width:var(--page-width,100vw);
  opacity:0;
  pointer-events:none;
  transition:opacity 360ms var(--ease-out)
}

.site-header[data-motion]::before {
  top:0;
  bottom:0;
  background:var(--header-glass);
  -webkit-backdrop-filter:blur(20px) saturate(170%);
  backdrop-filter:blur(20px) saturate(170%)
}

.site-header[data-motion]::after {
  top:100%;
  height:28px;
  background:linear-gradient(var(--header-fade),transparent)
}

.site-header[data-scrolled]::before,.site-header[data-scrolled]::after {
  opacity:1
}

@media(max-width:1050px) {
  .site-header[data-motion] {
    transition:translate 380ms var(--ease-out)
  }
  /* Keyboard focus inside keeps it shown; a tap's focus does not. */
  .site-header[data-motion][data-away]:not(:has(:focus-visible)) {
    translate:0 calc(-100% - 30px)
  }
}

@media print {
  .site-header[data-motion] {
    position:relative;
    top:0;
    translate:none
  }
  .site-header[data-motion]>* {
    translate:none
  }
  .site-header[data-motion]::before,.site-header[data-motion]::after,body::before,.ambient {
    display:none
  }
}

/* Navigation: a soft pill follows the hovered link and the red underline
   glides to the current page. Both take their first place without moving. */
.desktop-nav[data-glide] {
  position:relative
}

.desktop-nav[data-glide] .nav-link:after {
  content:none
}

.desktop-nav[data-glide]::before,.desktop-nav[data-glide]::after {
  content:'';
  position:absolute;
  left:0;
  pointer-events:none
}

.desktop-nav[data-glide]::before {
  top:50%;
  height:32px;
  margin-top:-16px;
  width:calc(var(--hover-w,0px) + 24px);
  translate:calc(var(--hover-x,0px) - 12px) 0;
  border-radius:var(--radius-control);
  background:var(--nav-glide);
  opacity:0;
  scale:.94;
  transition:opacity var(--t-fast) var(--ease-out),scale var(--t-base) var(--ease-out)
}

.desktop-nav[data-hover]::before {
  opacity:1;
  scale:1
}

.desktop-nav[data-glide]::after {
  bottom:6px;
  height:2px;
  border-radius:2px;
  background:var(--red);
  width:var(--current-w,0px);
  translate:var(--current-x,0px) 0;
  opacity:0
}

.desktop-nav[data-current]::after {
  opacity:1
}

.desktop-nav[data-glide-ready]::before {
  transition:opacity var(--t-fast) var(--ease-out),scale var(--t-base) var(--ease-out),translate var(--t-spring) var(--ease-spring),width var(--t-spring) var(--ease-spring)
}

.desktop-nav[data-hover-snap]::before {
  transition:opacity var(--t-fast) var(--ease-out),scale var(--t-base) var(--ease-out)
}

.desktop-nav[data-glide-ready]::after {
  transition:translate var(--t-spring) var(--ease-spring),width var(--t-spring) var(--ease-spring),opacity var(--t-fast) var(--ease-out)
}

/* Segmented controls: the raised choice glides to the new one, then hands back
   to the button itself. */
:is(.range-tabs,.metric-tabs) {
  position:relative;
  isolation:isolate
}

:is(.range-tabs,.metric-tabs)[data-glide]::before {
  content:'';
  position:absolute;
  z-index:-1;
  left:0;
  top:0;
  width:var(--seg-w);
  height:var(--seg-h);
  translate:var(--seg-x) var(--seg-y);
  border-radius:var(--radius-control);
  background:var(--segment-active);
  box-shadow:0 1px 2px #0f22481f;
  transition:translate var(--t-spring) var(--ease-spring),width var(--t-spring) var(--ease-spring),height var(--t-spring) var(--ease-spring)
}

:is(.range-tabs,.metric-tabs)[data-glide-snap]::before {
  transition:none
}

:is(.range-tabs,.metric-tabs)[data-glide]>[aria-pressed=true] {
  background:transparent;
  box-shadow:none
}

:is(.range-tabs,.metric-tabs)[data-glide-end]>button {
  transition:none
}

/* Glass surfaces catch a soft light under a fine pointer: a wide glow behind
   the content and a brighter edge nearest the pointer. The position properties
   do not inherit, so moving the light restyles only the surface. */
@property --px {
  syntax:'<length-percentage>';
  inherits:false;
  initial-value:50%
}

@property --py {
  syntax:'<length-percentage>';
  inherits:false;
  initial-value:0px
}

:is(.panel,.tracking-panel,.game-stat) {
  position:relative;
  isolation:isolate
}

:is(.stat-card,.ledger-card,.game-stat,.hero-ccu-card,.news-card) {
  isolation:isolate;
  --sheen-size:340px
}

:is(.stat-card,.ledger-card,.game-stat,.hero-ccu-card,.news-card)::before,
:is(.stat-card,.ledger-card,.game-stat,.hero-ccu-card,.news-card)::after {
  --px:inherit;
  --py:inherit;
  content:'';
  position:absolute;
  inset:0;
  border-radius:inherit;
  pointer-events:none;
  opacity:0;
  transition:opacity 480ms var(--ease-out)
}

:is(.stat-card,.ledger-card,.game-stat,.hero-ccu-card,.news-card)::before {
  z-index:-1;
  background:radial-gradient(var(--sheen-size) circle at var(--px) var(--py),var(--sheen-fill),transparent 70%)
}

:is(.stat-card,.ledger-card,.game-stat,.hero-ccu-card,.news-card)::after {
  padding:1px;
  background:radial-gradient(calc(var(--sheen-size) * .55) circle at var(--px) var(--py),var(--sheen-edge),transparent 70%);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;
  mask:linear-gradient(#000 0 0) content-box exclude,linear-gradient(#000 0 0)
}

:is(.stat-card,.ledger-card,.game-stat,.hero-ccu-card,.news-card)[data-lit]::before,
:is(.stat-card,.ledger-card,.game-stat,.hero-ccu-card,.news-card)[data-lit]::after {
  opacity:1;
  transition-duration:var(--t-base)
}

/* Rows in rankings and lists catch a soft light at the pointer. Every cell paints
   the same viewport-anchored gradient, so it reads as one light across the row. */
.games-table tbody tr[data-lit] td,:is(.mover-button,.news-row)[data-lit] {
  background:radial-gradient(260px circle at var(--mx) var(--my),var(--row-light),transparent 72%) fixed,var(--row-hover)
}

.insight-history-item[data-lit] {
  background:radial-gradient(260px circle at var(--mx) var(--my),var(--row-light),transparent 72%) fixed,var(--control-fill-hover)
}

/* Small cards lean toward the pointer on a spring (motion.js sets the angles). */
[data-tilt] {
  transform:perspective(900px) rotateX(calc(var(--tilt-x,0) * 1deg)) rotateY(calc(var(--tilt-y,0) * 1deg)) scale(var(--tilt-s,1))
}

/* Controls press in quickly and spring back. */
.button,.icon-button {
  transition-property:background-color,border-color,box-shadow,color,transform;
  transition-duration:var(--t-fast),var(--t-fast),var(--t-base),var(--t-fast),var(--t-spring);
  transition-timing-function:var(--ease-out),var(--ease-out),var(--ease-out),var(--ease-out),var(--ease-spring)
}

.button:active,.icon-button:active {
  transition-duration:var(--t-fast),var(--t-fast),var(--t-base),var(--t-fast),90ms;
  transition-timing-function:var(--ease-out)
}

.button:active {
  transform:scale(.97)
}

.icon-button:active {
  transform:scale(.9)
}

/* Full-width buttons darken instead: shrinking would pull their edges in far
   enough that a press near one could be released off the button. */
:is(.settings-logout,.settings-admin,.login-button,.confirm-row .button):active {
  transform:none;
  filter:brightness(.92)
}

/* A single band of light crosses a primary button when the pointer arrives. */
.button-primary {
  position:relative;
  overflow:hidden;
  isolation:isolate
}

.button-primary::after {
  content:'';
  position:absolute;
  inset:0;
  background:linear-gradient(105deg,transparent 32%,#ffffff38 50%,transparent 68%);
  translate:-105% 0;
  pointer-events:none
}

@media(hover:hover) and (pointer:fine) {
  .button-primary:hover::after {
    translate:105% 0;
    transition:translate 760ms var(--ease-out)
  }
}

.theme-toggle[data-turn] svg {
  animation:theme-turn var(--t-spring) var(--ease-spring) backwards
}

@keyframes theme-turn {
  from {
    opacity:0;
    transform:rotate(-90deg) scale(.55)
  }
}

/* Dialogs settle in on a spring. Backwards fill only, so no transform is left
   behind to offset anything positioned inside them. */
@media(prefers-reduced-motion:no-preference) {
  dialog[open] {
    animation:dialog-in 520ms var(--ease-spring) backwards
  }
  dialog[open]::backdrop {
    animation:backdrop-in 260ms var(--ease-out) backwards
  }
}

@keyframes dialog-in {
  from {
    opacity:0;
    transform:translateY(12px) scale(.97)
  }
}

@keyframes backdrop-in {
  from {
    opacity:0
  }
}

/* Page changes: the old view fades out quickly and the new one rises in. The
   header is captured on its own and shows live, so it never flickers. It is
   named only during the change: a named element is a backdrop root, which
   would stop its glass blurring the page. */
html[data-view-turn=nav] .site-header[data-motion] {
  view-transition-name:site-header
}

html[data-view-turn=nav]::view-transition-old(root) {
  animation:view-out 150ms var(--ease-out) both
}

html[data-view-turn=nav]::view-transition-new(root) {
  animation:view-in 360ms var(--ease-out) both
}

::view-transition-old(site-header) {
  display:none
}

::view-transition-new(site-header) {
  animation:none
}

@keyframes view-out {
  to {
    opacity:0
  }
}

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

/* Theme changes spread from the toggle as a widening circle. */
html[data-view-turn=theme]::view-transition-old(root),html[data-view-turn=theme]::view-transition-new(root) {
  animation:none;
  mix-blend-mode:normal
}

html[data-view-turn=theme]::view-transition-new(root) {
  animation:theme-reveal 640ms var(--ease-out)
}

@keyframes theme-reveal {
  from {
    clip-path:circle(0 at var(--turn-x,50%) var(--turn-y,0))
  }
  to {
    clip-path:circle(150vmax at var(--turn-x,50%) var(--turn-y,0))
  }
}

/* The background orbs (motion.js): soft lights that rest where each page puts
   them, fade in on arrival, cross-fade on a page change and drift very slowly,
   each on its own cycle. They replace the static body::before light (hidden
   outright, so a cold load never shows it before the orbs fade in). Transform
   and opacity only, so they never repaint. */
body::before {
  display:none
}

.ambient {
  position:fixed;
  inset:0;
  z-index:-1;
  pointer-events:none;
  contain:strict
}

.ambient-orb {
  position:absolute;
  left:var(--x);
  top:var(--y);
  width:var(--w);
  height:var(--h);
  translate:-50% -50%;
  background:radial-gradient(closest-side,rgb(var(--c) / var(--a)),rgb(var(--c) / 0));
  opacity:0;
  transition:opacity 1.9s cubic-bezier(.4,0,.2,1);
  animation:orb-drift var(--drift) ease-in-out var(--delay) infinite alternate
}

.ambient-orb[data-shown] {
  opacity:1
}

@keyframes orb-drift {
  0% {
    transform:translate3d(0,0,0) scale(1)
  }
  50% {
    transform:translate3d(var(--dx),var(--dy),0) scale(1.05)
  }
  100% {
    transform:translate3d(calc(var(--dx) * -.6),calc(var(--dy) * .8),0) scale(.96)
  }
}
