/* Lockerbie RailOS v5.0.10 — subtle always-on public display motion */
:root{--mb-ambient-speed:9s;--mb-ambient-soft:16s}
html{background-attachment:fixed}
body.mb-public-ambient{position:relative;isolation:isolate}
body.mb-public-ambient::before{
  content:"";position:fixed;inset:-25%;z-index:-1;pointer-events:none;
  background:
    radial-gradient(circle at 20% 25%,rgba(0,191,255,.075),transparent 31%),
    radial-gradient(circle at 82% 70%,rgba(255,214,59,.045),transparent 29%);
  transform:translate3d(-1.2%,0,0) scale(1.03);
  animation:mbAmbientDrift 22s ease-in-out infinite alternate;
}
@keyframes mbAmbientDrift{
  0%{transform:translate3d(-1.2%,-.5%,0) scale(1.03);opacity:.64}
  50%{opacity:.92}
  100%{transform:translate3d(1.2%,.7%,0) scale(1.055);opacity:.7}
}

/* Common public information cards: a near-imperceptible breathing highlight. */
body.mb-public-ambient .card,
body.mb-public-ambient .sum,
body.mb-public-ambient .metric,
body.mb-public-ambient .hero,
body.mb-public-ambient .top-card,
body.mb-public-ambient .next-train,
body.mb-public-ambient .session-card,
body.mb-public-ambient .service:not(.platform-service){
  --mb-stagger:0s;
  animation:mbCardBreathe var(--mb-ambient-soft) ease-in-out infinite;
  animation-delay:var(--mb-stagger);
}
body.mb-public-ambient .card:nth-child(2n),body.mb-public-ambient .metric:nth-child(2n),body.mb-public-ambient .sum:nth-child(2n){--mb-stagger:-3s}
body.mb-public-ambient .card:nth-child(3n),body.mb-public-ambient .metric:nth-child(3n),body.mb-public-ambient .sum:nth-child(3n){--mb-stagger:-6s}
body.mb-public-ambient .card:nth-child(4n),body.mb-public-ambient .metric:nth-child(4n),body.mb-public-ambient .sum:nth-child(4n){--mb-stagger:-9s}
@keyframes mbCardBreathe{
  0%,100%{filter:brightness(1);box-shadow:0 0 0 rgba(0,190,255,0)}
  50%{filter:brightness(1.025);box-shadow:0 0 18px rgba(0,190,255,.055)}
}

/* Live/status elements should look active without flashing. */
body.mb-public-ambient .live,
body.mb-public-ambient .dot,
body.mb-public-ambient .status:not(select),
body.mb-public-ambient [class*="status-badge"]{
  animation:mbStatusPulse 3.6s ease-in-out infinite;
}
@keyframes mbStatusPulse{0%,100%{filter:brightness(.96)}50%{filter:brightness(1.15)}}

/* Progress / direction / distance bars get a slow travelling sheen. */
body.mb-public-ambient .track,
body.mb-public-ambient .bar,
body.mb-public-ambient .progress,
body.mb-public-ambient .meter,
body.mb-public-ambient [class*="progress"],
body.mb-public-ambient [class*="bar"]{
  position:relative;overflow:hidden;
}
body.mb-public-ambient .track::after,
body.mb-public-ambient .bar::after,
body.mb-public-ambient .progress::after,
body.mb-public-ambient .meter::after,
body.mb-public-ambient [class*="progress"]::after{
  content:"";position:absolute;inset:0 auto 0 -38%;width:32%;pointer-events:none;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.12),transparent);
  transform:skewX(-14deg);animation:mbSheen var(--mb-ambient-speed) linear infinite;
}
@keyframes mbSheen{0%,18%{left:-38%}72%,100%{left:118%}}

/* Public hub tiles and buttons get a tiny periodic edge lift. */
body.mb-public-ambient a.tile,
body.mb-public-ambient .button,
body.mb-public-ambient .btn{
  transition:transform .2s ease,filter .2s ease,box-shadow .2s ease;
  animation:mbEdgeWake 13s ease-in-out infinite;
}
@keyframes mbEdgeWake{0%,78%,100%{filter:brightness(1)}86%{filter:brightness(1.07)}}

/* Train/platform boards: preserve layout, animate only accents. */
body.mb-public-ambient .next-name,
body.mb-public-ambient .destination,
body.mb-public-ambient .train-name{
  animation:mbTextGlow 7s ease-in-out infinite;
}
@keyframes mbTextGlow{0%,100%{text-shadow:0 0 0 transparent}50%{text-shadow:0 0 9px rgba(255,255,255,.09)}}

/* Accessibility: users requesting reduced motion get a calm static board. */
@media (prefers-reduced-motion:reduce){
  body.mb-public-ambient::before,
  body.mb-public-ambient .card,body.mb-public-ambient .sum,body.mb-public-ambient .metric,
  body.mb-public-ambient .hero,body.mb-public-ambient .top-card,body.mb-public-ambient .next-train,
  body.mb-public-ambient .session-card,body.mb-public-ambient .service:not(.platform-service),
  body.mb-public-ambient .live,body.mb-public-ambient .dot,body.mb-public-ambient .status:not(select),
  body.mb-public-ambient [class*="status-badge"],body.mb-public-ambient .track::after,
  body.mb-public-ambient .bar::after,body.mb-public-ambient .progress::after,body.mb-public-ambient .meter::after,
  body.mb-public-ambient [class*="progress"]::after,body.mb-public-ambient a.tile,
  body.mb-public-ambient .button,body.mb-public-ambient .btn,body.mb-public-ambient .next-name,
  body.mb-public-ambient .destination,body.mb-public-ambient .train-name{animation:none!important}
}

/* Lockerbie RailOS v5.0.37 — contextual public-page animations */
.mb-value-change{animation:mbValueChange .62s cubic-bezier(.2,.8,.2,1)!important}
@keyframes mbValueChange{0%{transform:translateY(0);filter:brightness(1)}35%{transform:translateY(-3px) scale(1.035);filter:brightness(1.28)}100%{transform:translateY(0);filter:brightness(1)}}

/* Now Running: changed services arrive like a live information-board update. */
.mb-running-change{animation:mbRunningChange .58s cubic-bezier(.2,.82,.2,1)!important}
@keyframes mbRunningChange{0%{opacity:.35;transform:translateY(8px)}55%{opacity:1;transform:translateY(-2px);text-shadow:0 0 16px rgba(100,181,255,.26)}100%{transform:translateY(0);text-shadow:none}}
#recent .row.mb-row-enter{animation:mbRowEnter .46s ease both}
@keyframes mbRowEnter{from{opacity:0;transform:translateX(-12px)}to{opacity:1;transform:none}}

/* Achievement Hall: badges unlock/reveal with a restrained presentation. */
#hall .pill.mb-achievement-reveal,.badge.mb-achievement-reveal{animation:mbAchievementReveal .7s cubic-bezier(.18,.82,.2,1) both}
@keyframes mbAchievementReveal{0%{opacity:0;transform:translateY(8px) scale(.92);filter:brightness(.8)}65%{opacity:1;transform:translateY(-2px) scale(1.035);filter:brightness(1.18)}100%{opacity:1;transform:none;filter:none}}

/* Leaderboard: rank changes are visible but don't continuously flash. */
#rows tr{transition:transform .35s ease,background-color .5s ease,box-shadow .5s ease}
#rows tr.mb-rank-up{animation:mbRankUp .95s ease both}
#rows tr.mb-rank-down{animation:mbRankDown .85s ease both}
#rows tr.mb-rank-new{animation:mbRankNew .62s ease both}
@keyframes mbRankUp{0%{transform:translateY(10px);background:rgba(97,227,164,.16)}48%{transform:translateY(-3px);background:rgba(97,227,164,.09)}100%{transform:none;background:transparent}}
@keyframes mbRankDown{0%{transform:translateY(-8px);background:rgba(255,159,104,.12)}100%{transform:none;background:transparent}}
@keyframes mbRankNew{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}

/* Profile counters get a tiny finish pulse after their count-up completes. */
.mb-counter-finish{animation:mbCounterFinish .5s ease!important}
@keyframes mbCounterFinish{45%{transform:scale(1.045);filter:brightness(1.22)}100%{transform:none;filter:none}}

@media (prefers-reduced-motion:reduce){
  .mb-value-change,.mb-running-change,#recent .row.mb-row-enter,
  #hall .pill.mb-achievement-reveal,.badge.mb-achievement-reveal,
  #rows tr.mb-rank-up,#rows tr.mb-rank-down,#rows tr.mb-rank-new,.mb-counter-finish{animation:none!important}
}
