/* =============================================================================
   Global stylesheet for the whole site. Defines theming (data-theme /
   data-bg-style), the vertical "leech bar" layout, popups, and the responsive /
   mobile rules.
   ============================================================================= */

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-display: swap;
}

body {
  font-family: "Lato", "NotoColorEmoji";
  display: flex;
  flex-direction: column;
  align-items: center;
  font-size: 1.4vh;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow: hidden;
  height: 100vh;
  margin: 0;
}

::selection {
  background-color: #1c2023;
}

/* Thin custom scrollbar across the board, matching the .vertical-bar (board
   entries) / #filter-details-mount style. Firefox uses the standard properties;
   WebKit/Blink uses the ::-webkit-scrollbar pseudo-elements. Per-element blocks
   below only remain where they need extra behaviour (e.g. .vertical-bar). */
* {
  scrollbar-width: thin;
  scrollbar-color: #888 transparent;
}

*::-webkit-scrollbar {
  width: 0.5vh;
  height: 0.5vh;
  background-color: transparent;
}

*::-webkit-scrollbar-track {
  display: none;
  background-color: transparent;
}

*::-webkit-scrollbar-thumb {
  background: #888;
}

*::-webkit-scrollbar-thumb:hover {
  background: #555;
}

.blur-effect {
  transition: 0.2s;
  filter: blur(0.5vh);
  pointer-events: none;
  user-select: none;
  -moz-user-select: none;
  -webkit-user-drag: none;
  -webkit-user-select: none;
  -ms-user-select: none;
}

:disabled {
  opacity: 0.4;
  /* !important so it beats the cursor: pointer on [class*="-btn"], which has equal
     specificity but wins on source order (it's defined later in the file). */
  cursor: not-allowed !important;
}

.invite-btn-mobile {
  display: none;
}

.footer-mobile {
  display: none;
}

[data-bg-style="artwork"] {
  body {
    opacity: 77%; /* Only valuable when a background image is set */
    background-size: cover;
    background-repeat: no-repeat;
    background-position: center;
  }
}

[data-bg-artwork="2-0-limsa"] {
  body {
    background-image: url("/assets/images/backgrounds/limsa.webp");
    background-blend-mode: overlay;
    background-color: #444 !important;
  }
}

[data-bg-artwork="2-0-gridania"] {
  body {
    background-image: url("/assets/images/backgrounds/gridania.webp");
    background-blend-mode: overlay;
    background-color: #444 !important;
  }
}

[data-bg-artwork="2-0-uldah"] {
  body {
    background-image: url("/assets/images/backgrounds/uldah.webp");
    background-blend-mode: overlay;
    background-color: #444 !important;
  }
}

[data-bg-artwork="5-0-scions"] {
  body {
    background-image: url("/assets/images/backgrounds/5-0-scions.webp");
  }
}

[data-bg-artwork="6-0-moon"] {
  body {
    background-image: url("/assets/images/backgrounds/6-0-moon.jpg");
  }
}

[data-bg-artwork="6-0-thavnair"] {
  body {
    background-image: url("/assets/images/backgrounds/6-0-thavnair.jpg");
  }
}

[data-bg-artwork="6-2-island-sanctuary"] {
  body {
    background-image: url("/assets/images/backgrounds/Endwalker-ConceptArt-IslandSanctuary.jpg");
    background-blend-mode: overlay;
    background-color: #444 !important;
  }
}

[data-bg-artwork="7-0-ocean"] {
  body {
    background-image: url("/assets/images/backgrounds/7-0-ocean.jpg");
    background-blend-mode: overlay;
    background-color: #444 !important;
  }
}

[data-bg-artwork="7-0-tural"] {
  body {
    background-image: url("/assets/images/backgrounds/7-0-tural.jpg");
  }
}

[data-bg-artwork="7-0-map"] {
  body {
    background-image: url("/assets/images/backgrounds/7-0-map.jpg");
    background-blend-mode: overlay;
    background-color: #444 !important;
  }
}

[data-bg-artwork="7-1"] {
  body {
    background-image: url("/assets/images/backgrounds/7-1.jpg");
  }
}

[data-bg-artwork="7-2"] {
  body {
    background-image: url("/assets/images/backgrounds/7-2.jpg");
  }
}

[data-bg-artwork="7-21"] {
  body {
    background-image: url("/assets/images/backgrounds/7-21.jpg");
    background-blend-mode: overlay;
  }
}

[data-bg-artwork="7-3"] {
  body {
    background-image: url("/assets/images/backgrounds/7-3.jpg");
    background-blend-mode: overlay;
    background-color: #444 !important;
  }
}

[data-bg-artwork="7-4"] {
  body {
    background-image: url("/assets/images/backgrounds/7-4.jpg");
    background-blend-mode: overlay;
    background-color: #444 !important;
  }
}

[data-bg-artwork="7-5"] {
  body {
    background-image: url("/assets/images/backgrounds/7-5.jpg");
    background-blend-mode: overlay;
    background-color: #444 !important;
  }
}

[data-bg-style="tomestone"] {
  body {
    opacity: 77%; /* Only valuable when a background image is set */
    background-blend-mode: overlay;
  }
}

/* Light theme */
[data-theme="light"] {
  body {
    color: #333;
    background-color: #444;
  }

  .maplink {
    color: #333;
  }

  .subtitle {
    color: #ddd;
  }

  .subtitle#alt-subtitle {
    color: #737373;
  }

  a {
    color: #333;
  }

  .session_status a {
    color: #ddd;
  }

  .entry,
  .avatar,
  .character-portrait img {
    border: 0.23vh solid #ddd;
    background-color: #f9f9f9;
    transition: 0.2s;
  }

  .lodestone-logo {
    filter: drop-shadow(0 0.3vh 0.5vh rgba(0, 0, 0, 0.5));
  }

  .sidebar-button {
    border: 0.23vh solid #333;
    background-color: #e0e0e0;
  }

  .sidebar {
    border-left: 0.23vh solid #333;
  }

  .mini-avatar {
    border: 0.13vh solid #ddd;
  }

  .linked:hover {
    border: 0.23vh solid #333;
    background-color: #e6e6e6;
    cursor: pointer;
    transition: 0.2s;
    transform: scale(1.01);
  }

  .vertical-bar {
    background-color: #e0e0e0;
  }

  .sticky-header {
    background: rgb(224, 224, 224);
    background: linear-gradient(180deg, rgba(224, 224, 224, 1) 95%, rgba(224, 224, 224, 1) 100%);
  }

  h2 {
    color: #333;
  }

  .rainbow {
    background-image: radial-gradient(circle, rgba(51, 51, 51, 1) 50%, rgba(255, 123, 241, 1) 100%);
  }

  .footer {
    color: #ddd;
  }

  .footer a {
    color: #ddd;
  }

  .logon,
  .popup,
  .print {
    background-color: #e0e0e0;
    border: 0.3vh solid #ddd;
  }

  input[type="text"],
  input[type="number"],
  input[type="password"],
  select {
    border: 0.15vh solid #ddd;
    background-color: #f9f9f9;
    color: black;
  }

  input[type="number"] {
    color-scheme: light;
  }

  input[type="checkbox"],
  input[type="range"] {
    accent-color: #333;
  }

  [class*="-btn"],
  .login-form button {
    background-image: linear-gradient(45deg, #333 0%, #545454 51%, #333 100%);
    background-size: 200% auto;
    color: #f9f9f9;
  }

  [class*="-btn"].active {
    box-shadow: inset 0px 0px 0.5vh white;
  }

  .login-form button:enabled:hover {
    box-shadow: 0px 0px 0.5vh black;
  }

  .close-button {
    color: #333;
  }
}

/* ALTCHA captcha widget — follow the site's light/dark theme.
   The v3 widget has NO theme="dark" attribute for colors; its palette uses CSS
   light-dark(), which resolves from the element's `color-scheme`. So we map the
   site's data-theme (set on <html> by style_theme_switcher.js) onto color-scheme,
   and the widget's built-in dark/light values follow automatically. */
[data-theme="dark"] altcha-widget {
  color-scheme: dark;
}

[data-theme="light"] altcha-widget {
  color-scheme: light;
}

/* Widget sizing/spacing overrides (inherit into the light-DOM internals). */
altcha-widget {
  --altcha-checkbox-size: 3.5vh !important;
}

.altcha-checkbox-wrap {
  gap: 1.5rem !important;
}

/* Center the widget on the login page only. It keeps its natural ~320px box
   (the widget's own --altcha-max-width) and is centered via auto margins.
   Scoped to login's direct .logon > .login-form so the register page — where the
   form sits in a text-align:left column — keeps the widget left-aligned. */
.logon > .login-form altcha-widget {
  display: block;
  width: var(--altcha-max-width, 320px);
  max-width: 100%;
  margin-inline: auto;
}

/* Dark theme */
[data-theme="dark"] {
  body {
    color: #ddd;
    background-color: #222;
  }

  .maplink {
    color: #ddd;
  }

  .subtitle {
    color: #bbb;
  }

  a {
    color: #bbb;
  }

  .entry,
  .avatar,
  .character-portrait img {
    border: 0.23vh solid #555;
    background-color: #444;
    transition: 0.2s;
  }

  .sidebar-button {
    background-color: #333;
    border: 0.23vh solid #555;
  }

  .sidebar {
    border-left: 0.23vh solid #555;
  }

  .mini-avatar {
    border: 0.13vh solid #555;
  }

  .linked:hover {
    border: 0.23vh solid #ddd;
    background-color: #595959;
    cursor: pointer;
    transition: 0.2s;
    transform: scale(1.01);
  }

  .vertical-bar {
    background-color: #333;
  }

  .sticky-header {
    background: rgb(51, 51, 51);
    background: linear-gradient(180deg, rgba(51, 51, 51, 1) 90%, rgba(51, 51, 51, 0) 100%);
  }

  h2 {
    color: #ddd;
  }

  .rainbow {
    background-image: radial-gradient(
      circle,
      rgba(221, 221, 221, 1) 50%,
      rgba(255, 123, 241, 1) 100%
    );
  }

  .footer {
    color: #bbb;
  }

  .footer a {
    color: #bbb;
  }

  .logon,
  .popup,
  .print {
    background-color: #333;
    border: 0.23vh solid #555;
  }

  input[type="text"],
  input[type="number"],
  input[type="password"],
  select {
    border: 0.15vh solid #555;
    background-color: #444;
    color: white;
  }

  input[type="number"] {
    color-scheme: dark;
  }

  input[type="checkbox"],
  input[type="range"] {
    accent-color: #bbb;
  }

  input[type="checkbox"]:hover,
  input[type="checkbox"]:checked {
    border-color: #ddd;
  }

  .volume {
    filter: invert(100%);
  }

  [class*="-btn"],
  .login-form button {
    background-image: linear-gradient(45deg, #f9f9f9 0%, #b8b8b8 51%, #f9f9f9 100%);
    background-size: 200% auto;
    color: #333;
  }

  [class*="-btn"].active {
    box-shadow: inset 0px 0px 0.5vh black;
  }

  .login-form button:enabled:hover {
    box-shadow: 0px 0px 0.5vh white;
  }

  .close-button {
    color: #ddd;
  }
}

/* Logon */

.logo-login {
  max-width: 10vh;
  max-height: 10vh;
  width: auto;
  height: auto;
  transition: 0.2s;
}

.logon,
.popup,
.print {
  text-align: center;
  padding: 2vh;
  border-radius: 1vh;
  max-width: 43vh;
  width: 100%;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -400%);
  transition:
    opacity 500ms ease-in-out,
    transform 300ms cubic-bezier(1, 0, 0, 1);
  opacity: 0;
  pointer-events: none;
  user-select: none;
  -moz-user-select: none;
  -webkit-user-drag: none;
  -webkit-user-select: none;
  -ms-user-select: none;
}

.logon.active,
.popup.active {
  transform: translate(-50%, -50%);
  opacity: 1;
  pointer-events: auto;
  user-select: auto;
  -moz-user-select: auto;
  -webkit-user-drag: auto;
  -webkit-user-select: auto;
  -ms-user-select: auto;
}

.popup.active {
  z-index: 9999;
}

/* Page chrome hidden behind a HideContent "site closed" takeover (toggled by
   setPageHidden in popup.js). A class instead of an inline display so restoring
   visibility doesn't wipe inline styles like the register .logon's display:flex. */
.hidden-by-takeover {
  display: none !important;
}

.popup,
.print {
  text-align: left;
  box-shadow: 0 0 5vh 1vh grey;
}

.login-form {
  margin-top: 1vh;
}

input {
  transition: 0.2s;
}

input:hover {
  transition: 0.2s;
  transform: scale(1.01);
}

input:focus {
  background: white;
  transition: 0.2s;
  transform: scale(1.05);
}

input[type="text"],
input[type="number"],
input[type="password"],
input[type="range"],
select {
  font-size: 1.5vh;
  width: 100%;
  padding: 0.6vh;
  margin: 0.6vh 0;
  border-radius: 0.75vh;
}

/* WebKit hides the number spinners until the field is hovered/focused — force
   them always visible (Firefox already shows its own, non-styleable spinner). */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  opacity: 1;
}

.invitelink_box {
  width: 100%;
}

.condensed {
  padding: 0.25vh !important;
  margin: 0.25vh 0 !important;
}

.login-form button {
  transition: 0.2s;
  border: none;
  padding: 0.75vh 2vh;
  border-radius: 1vh;
  cursor: pointer;
  font-size: 1.75vh;
}

.login-form button:enabled:hover {
  transition: 0.2s;
  transform: scale(1.2) !important;
}

/* Normal page */

.blink {
  animation: blink 1s;
  animation-iteration-count: 3;
}

@keyframes blink {
  0% {
    transform: scale(1);
  }

  50% {
    transform: scale(1.05);
    background-color: #dc143c;
    background-image: none;
    color: white;
  }

  100% {
    transform: scale(1);
  }
}

.pulse {
  animation: pulse 2s infinite;
}

@keyframes pulse {
  0% {
    box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.7);
  }

  70% {
    box-shadow: 0 0 0 1vh rgba(255, 0, 0, 0);
  }

  100% {
    box-shadow: 0 0 0 0 rgba(255, 0, 0, 0);
  }
}

.blink.pulse {
  animation:
    blink 1s 3,
    pulse 2s infinite 3s; /* This is what an SS rank would actually use */
}

.blink-color {
  animation: blink-color 1s;
  animation-iteration-count: 3;
}

@keyframes blink-color {
  0%,
  100% {
    color: inherit;
    text-shadow: inherit;
  }

  50% {
    color: white;
    text-shadow: inherit;
  }
}

.rainbow {
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: move 60s linear infinite;
}

@keyframes move {
  to {
    background-position: 100vh;
  }
}

.gelatine {
  animation: gelatine 0.5s;
}

@keyframes gelatine {
  from,
  to {
    transform: scale(1, 1);
  }

  25% {
    transform: scale(0.9, 1.1);
  }

  50% {
    transform: scale(1.1, 0.9);
  }

  75% {
    transform: scale(0.95, 1.05);
  }
}

.hithere {
  animation: hithere 1s ease;
}

@keyframes hithere {
  30% {
    transform: scale(1.2);
  }

  40%,
  60% {
    transform: rotate(-20deg) scale(1.2);
  }

  50% {
    transform: rotate(20deg) scale(1.2);
  }

  70% {
    transform: rotate(0deg) scale(1.2);
  }

  100% {
    transform: scale(1);
  }
}

.bounce {
  animation: bounce 2s ease;
}

@keyframes bounce {
  70% {
    transform: translateY(0%);
  }

  80% {
    transform: translateY(-15%);
  }

  90% {
    transform: translateY(0%);
  }

  95% {
    transform: translateY(-7%);
  }

  97% {
    transform: translateY(0%);
  }

  99% {
    transform: translateY(-3%);
  }

  100% {
    transform: translateY(0);
  }
}

.bounce2 {
  animation: bounce2 2s ease;
}

@keyframes bounce2 {
  0%,
  20%,
  50%,
  80%,
  100% {
    transform: translateY(0);
  }

  40% {
    transform: translateY(-30px);
  }

  60% {
    transform: translateY(-15px);
  }
}

.shake {
  animation: shake 2s ease;
}

@keyframes shake {
  0%,
  100% {
    transform: translateX(0);
  }

  10%,
  30%,
  50%,
  70%,
  90% {
    transform: translateX(-10px);
  }

  20%,
  40%,
  60%,
  80% {
    transform: translateX(10px);
  }
}

.swing {
  transform-origin: top center;
  animation: swing 2s ease;
}

@keyframes swing {
  20% {
    transform: rotate(15deg);
  }

  40% {
    transform: rotate(-10deg);
  }

  60% {
    transform: rotate(5deg);
  }

  80% {
    transform: rotate(-5deg);
  }

  100% {
    transform: rotate(0deg);
  }
}

.wobble {
  animation: wobble 2s ease;
}

@keyframes wobble {
  0% {
    transform: translateX(0%);
  }

  15% {
    transform: translateX(-25%) rotate(-5deg);
  }

  30% {
    transform: translateX(20%) rotate(3deg);
  }

  45% {
    transform: translateX(-15%) rotate(-3deg);
  }

  60% {
    transform: translateX(10%) rotate(2deg);
  }

  75% {
    transform: translateX(-5%) rotate(-1deg);
  }

  100% {
    transform: translateX(0%);
  }
}

/* Title Bar */
.title-bar {
  width: 100%;
  color: #fff;
  padding: 0.75vh;
  position: fixed;
  z-index: 1;
  transition:
    opacity 500ms ease-in-out,
    transform 300ms cubic-bezier(1, 0, 0, 1);
  transform: translate(0, -300%);
  opacity: 0;
}

.title-bar.active {
  transform: translate(0);
  opacity: 1;
}

.logo-title {
  display: flex;
  align-items: center;
  margin-left: 0.6%;
}

.logo {
  max-width: 7.25vh;
  max-height: 7.25vh;
  width: auto;
  height: auto;
  margin-right: 2vh;
  transition: 0.2s;
}

.logo:hover,
.logo-login:hover {
  transition: 0.2s;
  transform: scale(1.07) rotate(-7deg);
}

h1 {
  margin: 0;
}

.popup h1 {
  max-width: 90%;
}

.subtitle {
  font-size: 1.5vh;
}

.session_status {
  margin-top: -7.5vh;
  margin-right: 0.75vh;
  text-align: right;
  float: right;
}

.avatar {
  height: 5.5vh;
  width: 5.5vh;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  object-fit: cover;
}

.avatar:hover {
  border-width: 0.32vh;
  transition: 0.2s;
  transform: scale(1.12) rotate(7deg);
}

.mini-avatar {
  height: 2.2vh;
  width: 2.2vh;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  object-fit: cover;
  margin-right: 0.5vh;
  vertical-align: middle;
}

a:hover,
.session_status a:hover,
.maplink:hover {
  text-shadow: 0px 0.05em 0.05em #000000;
}

/* Content */
.content {
  display: flex;
  width: 99.5%;
  justify-content: space-around;
  margin-top: 9vh;
  transform: translate(0, 120%);
  transition:
    opacity 500ms ease-in-out,
    transform 300ms cubic-bezier(1, 0, 0, 1),
    margin-top 500ms ease-in-out;
  opacity: 0;
}

.content.active {
  transform: translate(0);
  opacity: 1;
}

/* Fullscreen immersive mode — toggled by shared_scripts/fullscreen_immersive.js
   after 5s of mouse idle while fullscreen. The title-bar/footer hide via their
   own .active fade; these rules animate the rest: the board slides up to fill the
   freed space and the volume icons fade out. Everything reverses on activity. */
body.fs-immersive .content {
  margin-top: 1.5vh;
}

body.fs-immersive .vertical-bar {
  max-height: 96.5vh;
}

/* !important beats the body.audio-locked .volume.sound dim rule so every volume
   icon is fully hidden in immersive mode. .volume's transition:0.2s animates it. */
body.fs-immersive .volume {
  opacity: 0 !important;
  pointer-events: none;
}

/* Slide the sidebar button fully off the right edge — far enough that its pulse
   box-shadow ring (spreads 1vh) also can't peek. It animates via the button's
   own `transition: right`. The JS collapses the sidebar itself in tandem. */
body.fs-immersive .sidebar-button {
  right: -20vh;
}

/* Hide the cursor while immersive (!important beats per-element cursor: pointer
   / grab rules). */
body.fs-immersive,
body.fs-immersive * {
  cursor: none !important;
}

/* Kill :hover effects on the board — a hidden cursor could otherwise sit on an
   entry and keep it hovered. The wake listeners live on `document`, so mouse
   movement still bubbles up and reverses immersive; this only gates the board. */
body.fs-immersive .content {
  pointer-events: none;
}

/* Mobile: drop the combined bar's header strip while immersive (the container is
   desktop-hidden, so this only takes effect on mobile). */
body.fs-immersive .mobile-data-container .sticky-header {
  display: none;
}

.placeholder-img {
  width: 18vh;
  pointer-events: none;
  user-select: none;
  -moz-user-select: none;
  -webkit-user-drag: none;
  -webkit-user-select: none;
  -ms-user-select: none;
}

.error_image {
  filter: grayscale(100%);
}

.entry {
  text-align: left;
  padding: 1.5vh;
  margin-top: 0.25vh;
  margin-bottom: 0.25vh;
  border-radius: 1vh;
  /* Fixed factor instead of `normal` so each text line is a deterministic height,
     not font-metric-dependent — keeps a tile from drifting a pixel as its relative
     timestamp text ("x minutes ago") changes. */
  line-height: 1.25;
  transition:
    width 2s,
    height 2s,
    0.2s;
  background-repeat: no-repeat;
  background-size: contain;
  background-position: right center;
  overflow: hidden;
  position: relative;
  z-index: 1;
}

.entry::after {
  position: absolute;
  content: "";
  height: 100%;
  width: 100%;
  top: 0;
  right: 0;
  /* Per-entry icon, set inline via --entry-bg in data_fetcher.js (timers override
     this with --timer-bg below). Replaces the per-entry injected <style> block. */
  background-image: var(--entry-bg);
  background-repeat: no-repeat;
  background-size: contain;
  background-position: right center;
  overflow: hidden;
  z-index: -1;
}

.entry.atrain-entry::after {
  background-size: 55%;
}

.entry.departed.atrain-entry::after {
  background-size: contain;
}

.entry.dead,
.entry.departed,
.entry.completed,
.entry.alertOver,
.entry.feteOver,
.entry.exp-excluded {
  opacity: 0.6;
  position: relative;
  overflow: hidden;
  transition:
    width 2s,
    height 2s,
    0.2s;
  pointer-events: none;
}

/* Image overlay effect, right-aligned */
.entry.dead::before,
.entry.departed::before,
.entry.completed::before,
.entry.alertOver::before,
.entry.feteOver::before {
  content: "";
  position: absolute;
  /* Centred via calc rather than translateY(-50%) so transform stays free for the
     stamp-in animation (see .entry.state-enter::before). */
  top: calc(50% - 6.25vh);
  right: 0%;
  width: 12.5vh;
  height: 12.5vh;
  background-size: contain;
  background-repeat: no-repeat;
  z-index: 2;
}

.entry.dead::before {
  background-image: url("/assets/images/dead.webp");
}

.entry.departed::before {
  background-image: url("/assets/images/departed.webp");
}

.entry.completed::before {
  background-image: url("/assets/images/completed.webp");
}

.entry.alertOver::before {
  background-image: url("/assets/images/redalert_resolved.webp");
  top: calc(50% - 2.325vh); /* equivalent of the old top:50% + translateY(-15%) of 15.5vh */
  width: 15.5vh;
  height: 15.5vh;
}

.entry.feteOver::before {
  background-image: url("/assets/images/fete_done.webp");
}

/* Animate the terminal look in on a genuine alive→terminal transition: the tile
   dims (entryStateFade) and its overlay slams in like a rubber stamp (entryStamp).
   JS adds .state-enter on the freshly-swapped node and clears it when the dim ends;
   the gate lives in JS so neither replays when a long-dead tile's node is recreated
   by an unrelated tick (e.g. the time-ago refresh). fill-mode: both holds each
   first frame from the moment the class lands, so nothing flashes before it starts.
   The stamp's scale is kept modest on purpose — .entry is overflow:hidden, so a big
   scale would just be clipped to the tile instead of dropping in from above. */
@keyframes entryStateFade {
  from {
    opacity: 1;
  }
  to {
    opacity: 0.6;
  }
}

@keyframes entryStamp {
  from {
    opacity: 0;
    transform: scale(3.5) rotate(-25deg);
  }
  to {
    opacity: 1;
    transform: scale(1) rotate(0deg);
  }
}

.entry.state-enter {
  animation: entryStateFade 0.4s ease both;
}

.entry.state-enter::before {
  animation: entryStamp 0.4s cubic-bezier(0.6, 0.04, 0.98, 0.335) both;
}

div.entry p {
  width: calc(65% - (0.2 * (100% - 25vh)));
  max-width: 65%;
}

div.entry.compact p {
  width: 75%;
} /* Used on Achievement page */

div.entry.nolimit p {
  width: 100%;
  max-width: max-content;
}

a.is-entry,
a.is-entry:hover,
a.is-entry:active {
  text-decoration: none;
  color: inherit;
  text-shadow: none;
}

.reward-icon {
  max-height: 1.4em;
  height: auto;
  vertical-align: middle;
  margin-left: -0.1vh;
}

.reward-container {
  text-align: center;
  vertical-align: middle;
  padding-top: 0.2vh;
  padding-bottom: 0.2vh;
  padding-left: 0.4vh;
  padding-right: 0.4vh;
}

.reward-container:first-child {
  padding-left: 0;
}

.reward-container:last-child {
  padding-right: 0;
}

.reward-container-container {
  margin-top: 0.5vh;
}

.otw-container {
  display: flex;
  align-items: center;
  margin-top: 1vh;
}

.member-text {
  padding-left: 0.2vh;
}

/* Vertical Bar */
.vertical-bar {
  position: relative;
  width: 24.2%;
  max-height: 87.5vh;
  padding: 1vh;
  padding-top: 0;
  border-radius: 1vh;
  /* Firefox squares the corners when scrollbar-gutter reserves a gutter on the
     same element (border-radius clip is dropped). clip-path rounds the box via
     a separate mechanism FF honours regardless, so gutter + radius both work. */
  clip-path: inset(0 round 1vh);
  overflow: hidden;
  scrollbar-gutter: stable both-edges;
  /* Animate the fullscreen-immersive expansion (max-height -> 96.5vh). */
  transition: max-height 500ms ease-in-out;
}

.vertical-bar:hover {
  overflow-y: scroll;
}

.vertical-bar::-webkit-scrollbar {
  width: 0.5vh;
  position: absolute;
  background-color: transparent;
  overflow: hidden;
}

.vertical-bar::-webkit-scrollbar-track {
  display: none;
  background-color: transparent;
}

.vertical-bar::-webkit-scrollbar-thumb {
  background: #888;
}

.vertical-bar::-webkit-scrollbar-thumb:hover {
  background: #555;
}

.mobile-data-container {
  display: none;
}

/* Scroll-to-top button — appended by shared_scripts/scroll_to_top.js to bars
   marked .scroll-top-enabled. Sticks to the bottom-center of its own bar and
   scrolls it back to the top. Reuses .misc-btn for theming; stays hidden until
   the bar is scrolled down (.visible toggled in the script). */
.misc-btn.scroll-top-btn {
  position: sticky;
  bottom: 1vh;
  z-index: 4;
  display: block;
  width: fit-content;
  /* A sticky element stays in flow, so its box would add blank space at the end
     of the bar. Pull it up by its own height (line-height 2.2vh + 0.5vh*2 vertical
     padding = 3.2vh) so it overlays the content instead of reserving space. */
  margin-top: -3.2vh;
  margin-left: auto !important;
  margin-right: auto !important;
  font-size: 2.2vh !important;
  line-height: 1;
  opacity: 0;
  pointer-events: none;
  transition:
    opacity 0.2s,
    transform 0.2s,
    background-position 0.2s;
}

.scroll-top-btn.visible {
  opacity: 0.85;
  pointer-events: auto;
}

.sticky-header {
  padding-top: 1.25vh;
  padding-bottom: 1.25vh;
  padding-right: 2vh;
  padding-left: 2vh;
  margin-left: -2vh;
  margin-right: -2vh;
  top: 0;
  position: sticky;
  z-index: 3;
}

.lodestone-logo {
  width: 25vh;
  margin-top: 0.5vh;
  margin-bottom: 1.5vh;
}

.vertical-bar h2 {
  font-size: 2.6vh;
}

/* Admin panel: stack each section full-width inside the single wide #admin-bar
   (mirrors the stats page's one-bar layout, so the tables get the horizontal
   room a narrow flex column can't give them). */
.admin-section {
  margin-bottom: 1vh;
}

/* Create form + live list sit side by side, sharing the bar width evenly. */
.admin-row {
  display: flex;
  gap: 1.5vh;
  align-items: flex-start;
  margin-top: 1vh;
}

.admin-row .admin-section {
  flex: 1 1 0;
  min-width: 0;
}

.admin-section table#allUsers,
.admin-section table#allPopups {
  width: 100%;
}

.volume {
  position: relative;
  float: right;
  margin-top: -3.3vh;
  transition: 0.2s;
  max-width: 3.75vh;
  max-height: 3.75vh;
  width: auto;
  height: auto;
  opacity: 0.7;
  border-radius: 1vh;
}

.volume:hover {
  transition: 0.2s;
  transform: scale(1.2);
  cursor: pointer;
  border: 0.2vh solid black;
}

.volume.sound {
  display: none;
  pointer-events: none;
}

/* Notification audio restored as unmuted from a previous visit, but not yet
   unlocked by a user interaction (browsers block scripted playback until then).
   Dim the armed sound icons so it reads as "on, but waiting for interaction".
   .volume's transition:0.20s makes this fade in/out. */
body.audio-locked .volume.sound {
  opacity: 0.2;
}

.topmenu {
  filter: invert(100%);
  position: relative;
  float: right;
  transition: 0.2s;
  max-width: 2.2em;
  max-height: 2.2em;
  width: auto;
  height: auto;
  padding: 0.2vh;
  border-radius: 1vh;
  overflow: visible;
}

.topmenu:hover {
  transition: 0.2s;
  transform: scale(1.2);
  cursor: pointer;
  border: 0.2vh solid black;
}

/* Active-filter indicator dot on the filter toolbar icon */
#filterLink {
  position: relative;
}

#filterLink.has-active-filters::after {
  content: "";
  position: absolute;
  top: 0.5vh;
  right: -3vh;
  width: 0.8vh;
  height: 0.8vh;
  border-radius: 50%;
  background-color: #dc143c;
  box-shadow: 0 0 0.4vh #dc143c;
}

/* Filter popup goes wide: the three feeds sit side by side instead of stacking
   into one tall column. Height is capped so it never runs off the viewport. */
#popup-filter,
#popup-settings,
#popup-accountSettings {
  max-width: min(115vh, 94vw);
  width: 100%; /* fill to max-width (mobile would otherwise shrink to the single
                  wrapped column); cards still spread into columns on desktop */
  max-height: 86vh;
  overflow-y: auto;
}

/* Settings & Account Settings arrange their section cards in the same wrapping flex
   row the filter popup uses for its feed columns (.filter-columns / .filter-feed). */
#popup-settings .settings-container,
#popup-accountSettings .settings-container {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5vh;
  align-items: stretch;
}

#popup-settings .settings-container > .entry,
#popup-accountSettings .settings-container > .entry {
  flex: 1 1 30vh;
  min-width: min(28vh, 100%);
  /* match .filter-feed: don't tween width/height when columns stretch to equal
     height, and let content (native select dropdowns etc.) escape the card. */
  transition: none;
  overflow: visible;
}

/* Pin the card heading to the top edge, like .filter-feed h2. */
#popup-settings .settings-container > .entry > h2,
#popup-accountSettings .settings-container > .entry > h2 {
  margin-top: 0;
}

.filter-columns {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5vh;
  /* stretch = all columns share the tallest one's height. Content stays top-aligned
     because each .filter-feed is block-flow (not a centered flex container). */
  align-items: stretch;
}

.filter-feed {
  flex: 1 1 30vh;
  min-width: min(28vh, 100%);
  /* override .entry's width/height 2s transition so equal-height stretch doesn't
     animate when the expansion lists fill in after the popup opens. */
  transition: none;
}

.filter-feed h2 {
  margin-top: 0;
}

/* Filter cards opt out of .entry's overflow:hidden (board entries keep it). */
.filter-feed,
#filter-details-mount .entry {
  overflow: visible;
}

/* Nudge the first card clear of the mask's top fade so it isn't dimmed at rest. */
#filter-details-mount > .entry:first-child {
  margin-top: 2vh;
}

/* Nudge the last card clear of the mask's bottom fade so it isn't dimmed at rest. */
#filter-details-mount > .entry:last-child {
  margin-bottom: 2vh;
}


/* Filter popup: per-feed expansion / mark / FATE checkbox lists */
.filter-expansions,
.filter-categories,
.filter-details {
  display: flex;
  flex-direction: column;
  gap: 0.3vh;
  margin-top: 0.4vh;
}

/* Action buttons (exclude-specific / reset) stacked with the same spacing the
   datacenter / server buttons use in .dc-row. */
.filter-actions {
  display: flex;
  flex-direction: column;
  gap: 0.4vh;
  margin-top: 0.4vh;
}

/* Muted caveat under the "already collected" toggle in the FATE sub-popup. */
.filter-note {
  display: block;
  margin-top: 0.5vh;
  opacity: 0.6;
}

/* The detail list lives in its own sub-popup, so give it more height. A mask
   fades the content in/out at the top and bottom edges of the scroll viewport
   (opaque in the middle, transparent for the first/last 1vh) so rows dissolve
   as they scroll past either end instead of hard-cutting. */
.filter-details {
  max-height: 62vh;
  overflow-y: auto;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 1vh, black calc(100% - 1vh), transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, black 1vh, black calc(100% - 1vh), transparent 100%);
}

.filter-check {
  display: flex;
  align-items: center;
  gap: 0.6vh;
  cursor: pointer;
}

.filter-check input[type="checkbox"] {
  flex: 0 0 auto;
}

/* Sub-popup checkboxes sit inside .entry cards (block flow), so the .filter-details
   flex `gap` doesn't reach them — match the expansion list's row spacing. */
#filter-details-mount .filter-check {
  margin-bottom: 0.3vh;
}

.filter-group-title {
  margin: 0.6vh 0 0.1vh;
  font-weight: bold;
  opacity: 0.75;
}
/* Little secondary header inside a card (e.g. planet name in the red-alert list). */
.filter-subgroup-title {
  margin: 0.5vh 0 0.1vh;
  font-weight: bold;
  font-size: 0.9em;
  opacity: 0.55;
}

/* Lay a run of adjacent buttons out as a wrapping flex row with a consistent gap
   (instead of bare inline buttons that only get margin-right and cramp vertically
   when they wrap). .dc-row is the datacenter/server group in the filter menu;
   .btn-row is the generic version reused for the settings menu's button groups. */
.dc-row,
.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4vh;
}

#logoutLink .topmenu:hover {
  filter: brightness(0) saturate(100%) invert(49%) sepia(84%) saturate(3548%) hue-rotate(334deg)
    brightness(96%) contrast(110%);
}

.drag-handle {
  display: none;
  font-size: 1vh;
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: 0.25vh;
  opacity: 0.4;
}

.drag-handle:hover {
  cursor: grab;
  opacity: 0.75;
}

.drag-handle:active {
  cursor: grabbing;
}

.vertical-bar:hover .drag-handle {
  display: block;
}

.sidebar {
  width: 25%;
  padding-left: 1.5vh;
  right: 0;
  border-radius: 0;
  /* Cancel the .vertical-bar Firefox rounding fix — the sidebar is flush to the
     screen edge and must stay square (border-radius: 0 above isn't enough since
     the clip-path rounds independently). */
  clip-path: none;
  position: absolute;
  z-index: 5;
  height: 100vh;
  max-height: 100vh;
  transition: transform 500ms cubic-bezier(1, 0, 0, 1);
}

.sidebar.collapsed {
  transform: translateX(100%);
  transition: transform 500ms cubic-bezier(1, 0, 0, 1);
}

/* Mirrored ET/LT/ST clock in the timer sidebar's sticky header */
.sidebar-clock {
  margin: 0.5vh auto 0;
  font-size: 1.8vh;
}

/* Gap between the two timer groups in the sidebar */
.timer-spacer {
  height: 1vh;
}

/* Per-timer ::after background image (url set inline via --timer-bg in timers.js) */
.entry.timer::after {
  background-image: var(--timer-bg);
}

/* Cover background: render an .entry's ::after image full-bleed and faded at every
   screen size (the treatment .entry::after otherwise only gets on small screens).
   Image itself is set elsewhere — via --timer-bg for timers, or an injected
   #id::after rule for the filter popup's per-expansion cards. Used by the Housing
   timer and the exclude-marks/FATEs popup. */
.entry.cover-bg::after {
  left: 0;
  right: auto;
  opacity: 0.2;
  background-size: cover;
  background-position: center;
}

/* "Show map instead of image" mode (data_fetcher.js adds .map-bg + sets --cover-bg
   on active entries, and appends the .entry-mapimg overlay once the map has loaded).
   The entry's own icon is demoted to a faded full-bleed cover here on ::before.
   Active entries don't otherwise use ::before (the dead/departed stamp lives on a
   terminal tile's ::before, and .map-bg is never applied to those), so there's no
   clash. On narrow screens this ::before is hidden (media queries below) — the map
   overlay covers the tile there — so the icon ("normal" background) only ever shows
   on wide screens. */
.entry.map-bg::before {
  content: "";
  position: absolute;
  height: 100%;
  width: 100%;
  top: 0;
  left: 0;
  background-image: var(--cover-bg);
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
  opacity: 0.2;
  z-index: -1;
}

/* The map image itself, on its own overlay child (rather than ::after) so it can be
   cloned into a ghost and faded out when the mark dies — the same approach the otw
   container uses. Mirrors what the icon's ::after normally does: right-aligned and
   contained on wide screens, full-bleed faded cover on narrow (media queries below).
   Sits behind the tile's text (z-index:-1) but above the faded ::before icon. */
.entry-mapimg {
  position: absolute;
  inset: 0;
  background-image: var(--map-bg);
  background-repeat: no-repeat;
  background-size: contain;
  background-position: right center;
  z-index: -1;
}

/* Fade the map overlay in when it first appears (image loaded) and out when it
   leaves (mark died / setting switched off). Each keyframe pins only the hidden end;
   the visible end is the overlay's own opacity — 1 on wide, 0.2 on narrow — so the
   tween lands on the right value at either screen size. JS toggles these classes and
   clears them on animationend (see animateMapTransition). */
@keyframes mapFadeIn {
  from {
    opacity: 0;
  }
}

@keyframes mapFadeOut {
  to {
    opacity: 0;
  }
}

.entry-mapimg.map-enter {
  animation: mapFadeIn 0.4s ease both;
}

.entry-mapimg.map-leave {
  animation: mapFadeOut 0.4s ease both;
}

.sidebar-button {
  line-height: 3.5vh;
  padding: 2vh;
  border-radius: 2vh;
  font-size: 3.5vh;
  top: 45%;
  position: absolute;
  right: 24.2%;
  transform: scaleX(-1);
  padding-left: 2.75vh;
  padding-right: 0.2vh;
  margin-right: -1.2vh;
  transition: right 500ms cubic-bezier(1, 0, 0, 1);
  user-select: none;
  -moz-user-select: none;
  -webkit-user-drag: none;
  -webkit-user-select: none;
  -ms-user-select: none;
}

.sidebar-button.collapsed {
  margin-right: -2vh;
  padding-right: 2vh;
  padding-left: 0.1vh;
  margin-left: 0vh;
  transform: scaleX(1);
  right: 0;
  transition: right 500ms cubic-bezier(1, 0, 0, 1);
}

/* The shared .blink keyframes animate `transform: scale(...)`, which clobbers the
   sidebar button's scaleX flip — un-mirroring the arrow and shifting/overflowing
   it (only the uncollapsed scaleX(-1) state is visibly affected). Re-run blink
   with keyframes that fold the flip into each state's transform. */
.sidebar-button.blink {
  animation: blink-sidebar 1s 3;
}

.sidebar-button.blink.pulse {
  animation:
    blink-sidebar 1s 3,
    pulse 2s infinite 3s;
}

.sidebar-button.collapsed.blink {
  animation: blink-sidebar-collapsed 1s 3;
}

.sidebar-button.collapsed.blink.pulse {
  animation:
    blink-sidebar-collapsed 1s 3,
    pulse 2s infinite 3s;
}

@keyframes blink-sidebar {
  0%,
  100% {
    transform: scaleX(-1) scale(1);
  }

  50% {
    transform: scaleX(-1) scale(1.05);
    background-color: #dc143c;
    background-image: none;
    color: white;
  }
}

@keyframes blink-sidebar-collapsed {
  0%,
  100% {
    transform: scaleX(1) scale(1);
  }

  50% {
    transform: scaleX(1) scale(1.05);
    background-color: #dc143c;
    background-image: none;
    color: white;
  }
}

/* Small Screen ish */
@media only screen and (max-aspect-ratio: 16/8.7) and (min-aspect-ratio: 3.3/3) {
  /* If changed also apply to: Excessively narrow Mobile */

  div.entry p {
    width: 100%;
    max-width: 100%;
  }

  .entry::after {
    position: absolute;
    content: "";
    height: 100%;
    width: 100%;
    top: 0;
    left: 0;
    opacity: 0.2;
    background-repeat: no-repeat;
    background-size: cover;
    background-position: center;
    overflow: hidden;
    z-index: -1;
  }

  .entry.atrain-entry::after {
    background-size: contain;
  }

  /* Map mode: the map overlay becomes the full-bleed faded cover here, so hide the
     icon ::before — the "normal" background only shows on wide screens. */
  .entry.map-bg::before {
    display: none;
  }

  .entry-mapimg {
    background-size: cover;
    background-position: center;
    opacity: 0.2;
  }

  .character-portrait {
    display: none;
  }

  .character-text {
    margin-left: 0 !important;
  }

  /* Portrait is hidden at this aspect ratio, so show the character's banner
	   (faded via the .entry::after rule above) behind the name/info box. */
  .char-info::after {
    background-image: var(--char-banner);
  }
}

/* Mobile */
@media only screen and (max-aspect-ratio: 3.3/3) {
  .entry {
    font-size: 1.75vh;
  }

  .footer {
    display: none;
  }

  .sidebar-button {
    display: none;
  }

  .content {
    margin-top: 0.4vh;
  }

  .vertical-bar {
    display: none;
    width: 99%;
    /* Bound the scroll container to the *dynamic* viewport (dvh), not vh:
  		   vh on mobile is the toolbar-hidden height, so a vh-bounded bar runs
  		   past the visible area and its bottom can't be scrolled into view. */
    min-height: calc(100dvh - 1vh);
    max-height: calc(100dvh - 1vh);
    padding: 0.5vh;
    padding-top: 0;
  }

  .mobile-data-container {
    display: block;
    /* Extra scroll room so the last content clears the absolutely-positioned
		   mobile footer that overlays the bottom of the viewport. */
    padding-bottom: 6vh;
  }

  /* The character entry boxes are relocated into the mobile bar; drop the
	   desktop margin so they span the full width. (!important because the base
	   .character-text rule is defined later in the sheet.) */
  .character-text {
    margin: 0 !important;
  }

  /* Portrait isn't shown on mobile, so fade the banner behind the name box. */
  .char-info::after {
    background-image: var(--char-banner);
    background-size: cover;
    background-position: center;
    opacity: 0.2;
  }

  .entry,
  .logo-title {
    padding: 1.75vh;
  }

  .session_status {
    padding: 1.75vh;
    margin-right: 0vh;
    margin-top: -10.75vh;
  }

  .sticky-header {
    padding-top: 12vh;
  }

  .invite-btn,
  a.invite-btn,
  .daily-card-btn {
    display: none !important;
  }

  .footer-mobile {
    position: absolute;
    transform: translate(0, 300%);
    transition:
      opacity 750ms ease-in-out,
      transform 500ms cubic-bezier(1, 0, 0, 1);
    opacity: 0;
    display: block;
    bottom: 0.5%;
    width: 95%;
  }

  .footer-mobile.active {
    transform: translate(0);
    opacity: 1;
  }

  .footer-mobile .vertical-bar {
    text-align: center;
    max-width: 99%;
    min-height: 2em;
    max-height: 2em;
    height: 2em;
    display: block;
  }

  .footer-mobile .vertical-bar p {
    vertical-align: middle;
    padding: 0.4em;
  }

  .invite-btn-mobile {
    display: block;
    position: absolute;
    bottom: 150%;
    right: 0;
  }

  [data-theme="light"] {
    .subtitle {
      color: #737373;
    }

    .title-bar {
      color: #333;
    }

    .session_status a {
      color: #737373;
    }

    .avatar {
      border: 0.23vh solid #555;
      background-color: #444;
      transition: 0.2s;
    }

    .topmenu {
      filter: none;
    }
  }
}

/* Excessively narrow Mobile */
@media only screen and (max-aspect-ratio: 9/10) {
  /* If changed also apply to: Small Screen ish */

  .logo-title {
    display: flex;
    justify-content: center;
  }

  .session_status {
    margin-top: -3vh;
    float: none;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .session_status p {
    text-align: center;
  }

  .sticky-header {
    padding-top: 17vh;
  }

  div.entry p {
    width: 100%;
    max-width: 100%;
  }

  .entry::after {
    position: absolute;
    content: "";
    height: 100%;
    width: 100%;
    top: 0;
    left: 0;
    opacity: 0.2;
    background-repeat: no-repeat;
    background-size: cover;
    background-position: center;
    overflow: hidden;
    z-index: -1;
  }

  .entry.atrain-entry::after {
    background-size: contain;
  }

  /* Map mode: the map overlay becomes the full-bleed faded cover here, so hide the
     icon ::before — the "normal" background only shows on wide screens. */
  .entry.map-bg::before {
    display: none;
  }

  .entry-mapimg {
    background-size: cover;
    background-position: center;
    opacity: 0.2;
  }
}

/* Real fucking narrow Mobile */
@media only screen and (max-aspect-ratio: 20/43) {
  .session_status_text {
    display: none;
  }
}

/* Settings Menu */

.close-button {
  transition: 0.2s;
  position: absolute;
  right: 2vh;
  top: 2vh;
  font-size: 3.5vh;
  text-decoration: none;
  display: inline-block;
  line-height: 1;
  transform-origin: center;
}

.close-button:hover {
  rotate: 90deg;
  transition: 0.2s;
  transform: scale(1.1);
  text-shadow: none;
}

/* Every site button shares a "-btn" class (.misc-btn, .theme-btn, .dc-btn,
   .invite-btn, …), so one [class*="-btn"] rule styles them all — and any future
   -btn button automatically. It deliberately excludes .canvas-legend-item (a
   legend toggle <button>), .sidebar-button (a div) and .close-button (an anchor),
   none of which carry "-btn". .login-form buttons keep their own larger geometry. */
[class*="-btn"] {
  font-family:
    "Lato",
    "NotoColorEmoji",
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    Roboto,
    Helvetica,
    Arial,
    sans-serif,
    "Apple Color Emoji",
    "Segoe UI Emoji",
    "Segoe UI Symbol";
  transition: 0.2s;
  border: none;
  padding: 0.5vh 1vh !important;
  border-radius: 1vh !important;
  cursor: pointer;
  font-size: 1.5vh !important;
  vertical-align: middle;
  text-decoration: none; /* anchor-buttons (.invite-btn, the daily-card link) keep no underline */
}

/* A right margin only makes sense between buttons packed together, so it's scoped to the
   grouped buttons of the filter and settings popups rather than sitting on every -btn. */
#popup-filter [class*="-btn"],
#popup-settings [class*="-btn"] {
  margin-right: 0.5vh !important;
}

/* Daily-summary link stands apart from the stats-controls selects. */
.daily-card-btn {
  margin-left: 5vh;
}

.invite-btn {
  margin-right: 2.5vh !important;
}

/* Anchors styled as buttons (the .invite-btn "Live" link, the daily-card link, …) need
   inline-block so transform:scale applies to them outside a flex row, and their hover
   can't ride the rule below — :enabled matches form controls only, never anchors — so
   they get the same scale via their own a[class*="-btn"]:hover selector. */
a[class*="-btn"] {
  display: inline-block;
}

[class*="-btn"]:enabled:hover,
a[class*="-btn"]:hover,
.login-form button:enabled:hover {
  transition: 0.2s;
  transform: scale(1.1);
  background-position: right center;
}

[class*="-btn"].active {
  transition: 0.2s;
  border: 0.2vh solid black;
}

.checkbox-container {
  display: flex;
  align-items: center;
  gap: 1vh;
}

input[type="checkbox"] {
  transition: 0.2s;
  width: 2vh;
  height: 2vh;
  cursor: pointer;
  border: none;
  border-radius: 1vh;
}

input[type="checkbox"]:hover {
  transition: 0.2s;
  border: 0.5vh solid black;
}

input[type="checkbox"]:checked {
  transition: 0.2s;
  border: 0.5vh solid black;
}

/* Footer */
.footer {
  margin-top: 0.5vh;
  font-size: 1.5vh;
  width: 98.6%;
  transition:
    opacity 750ms ease-in-out,
    transform 500ms cubic-bezier(1, 0, 0, 1);
  transform: translate(0, 300%);
  opacity: 0;
}

.footer.active {
  transform: translate(0);
  opacity: 1;
}

/* Character page specific */

.character-container {
  display: flex;
  align-items: center;
  justify-content: flex-start;
}

.character-content {
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  width: 100%;
}

.character-text {
  font-size: 1.8vh;
  margin-left: 1.5vh; /* Space between the text and the portrait */
  flex: 1;
}

.character-portrait img {
  height: 77vh;
  max-width: 45vh;
  object-fit: cover;
  flex-shrink: 0;
  border-radius: 1vh;
}

/* Statistics page specific */

.stats-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1.5vh;
  margin: 1.5vh 1vh 0.5vh 1vh;
}

.stats-header h1 { white-space: nowrap; }

.stats-controls {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75vh;
}

.stats-controls label { margin-left: 1.5vh; }

.stats-controls select { width: auto; }

/* Include the self-hosted NotoColorEmoji so the EU flag (🇪🇺) renders in the
   datacenter selector — the OS emoji font doesn't draw regional-indicator flags. */
.stats-controls select {
  font-family: "Lato", "NotoColorEmoji", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}

/* Sections start hidden so they don't flash before the charts are populated;
   charts.js reveals the selected one(s) once the data has loaded. */
.charts-section {
  display: none;
  margin-top: 3vh;
}

.chart-row {
  display: flex;
  gap: 1.5vh;
  align-items: stretch;
  height: 32vh;
}

.chart-cell {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* The DC distribution chart only ever has two bars — give it half the width. */
.dc-cell {
  flex: 0.5 1 0;
}

.chart-cell h3, .line-row h3 {
  font-size: 1.7vh;
  margin: 0 0 2vh 0;
  text-align: center;
}

.canvas-box {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
}

/* Pie and line charts swap their canvas-baked legend for HTML (see ensureHtmlLegend
   in charts.js) so it scrolls / wraps and stays consistent across charts instead of
   overflowing the canvas. Only those boxes get .has-html-legend; bar charts keep the
   canvas as the box's sole child. .legend-right (pies) sits the legend beside the
   plot, .legend-top (the line chart) above it. */
.canvas-box.has-html-legend {
  display: flex;
}

/* Pie: legend beside the plot, the pair centred as a group so the square pie sits
   right next to the legend instead of floating in a stretched-wide canvas. */
.canvas-box.legend-right {
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 1.5vh;
}

/* Line: legend stacked on top of the plot (mirrors the old top-positioned legend). */
.canvas-box.legend-top {
  flex-direction: column;
  align-items: stretch;
}

.canvas-plot {
  position: relative;
  min-width: 0;
  min-height: 0;
}

/* Take the canvas out of flow so its (Chart.js-driven) size can't feed back into
   the flex / aspect-ratio layout. Otherwise the responsive canvas and its flex
   parent chase each other on resize and settle differently than a fresh load at
   the same size. The .canvas-plot stays determinate; Chart.js sizes the canvas to
   it. */
.canvas-plot canvas {
  position: absolute;
  top: 0;
  left: 0;
}

/* Keep a pie's plot square (sized by the cell height) so a wide cell doesn't open a
   gap between the pie and its legend; it shrinks to fit if the cell is narrow. */
.legend-right .canvas-plot {
  flex: 0 1 auto;
  height: 100%;
  aspect-ratio: 1 / 1;
  max-width: 100%;
}

/* A line's plot just fills the width and the height left below the legend. */
.legend-top .canvas-plot {
  flex: 1 1 0;
  width: 100%;
}

.canvas-legend {
  overflow: auto;
  display: flex;
  justify-content: center;
  align-content: flex-start;
  gap: 0.25vh 1.4vh;
  padding: 0 0.5vh;
  font-size: 1.5vh;
}

/* Pie: fill a column down the cell height, then — if there's width to spare — wrap
   into a second column instead of scrolling one tall column. The definite height is
   what tells the flexbox where to break a column. */
.legend-right .canvas-legend {
  flex: 0 1 auto;
  flex-flow: column wrap;
  height: 100%;
  max-width: 66%;
}

/* Line: a centred horizontal row that wraps onto more lines as needed, capped so it
   never crowds out the plot. */
.legend-top .canvas-legend {
  flex: 0 0 auto;
  flex-flow: row wrap;
  width: 100%;
  max-height: 35%;
}

.canvas-legend-item {
  display: flex;
  align-items: center;
  gap: 0.5em;
  margin: 0;
  padding: 0;
  background: none;
  border: none;
  color: inherit;
  font: inherit;
  line-height: 1.2;
  text-align: left;
  cursor: pointer;
}

/* A toggled-off slice reads as struck-through and dimmed, like the canvas legend. */
.canvas-legend-item.hidden { opacity: 0.45; text-decoration: line-through; }

/* The colour swatch stays wider than it is tall. */
.canvas-legend-swatch {
  flex: 0 0 auto;
  width: 2.2em;
  height: 0.85em;
  border: 1px solid;
  border-radius: 2px;
}

/* Line charts draw a line segment instead of a filled box; its dash style mirrors
   the dataset (solid group / dashed Mean / dotted History), set inline in JS. */
.canvas-legend-swatch--line {
  height: 0;
  border: 0;
  border-top-width: 0.4vh;
  border-top-style: solid;
  border-radius: 0;
}

.canvas-legend-label {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.line-row {
  display: flex;
  flex-direction: row;
  gap: 1.5vh;
  height: 44vh;
  margin-top: 1vh;
}

.line-chart {
  flex: 6.5 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.line-compare {
  flex: auto 0 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  /* Centered when the breakdown fits; "safe" falls back to top-aligned (instead of
     clipping the top) and the box scrolls when a full per-server list overflows. */
  justify-content: safe center;
  overflow-y: auto;
  text-align: left;
}

.line-compare h3 {
  font-size: 1.8vh;
  margin: 0 0 1.5vh 0;
  text-align: center;
}

/* Space the second comparison block (vs Historical Average) from the first. */
.line-compare h3:not(:first-child) { margin-top: 2.5vh; }

.line-compare .cmp-table {
  width: auto;
  margin: 0 auto;
  border-collapse: collapse;
}

.line-compare .cmp-table td { padding: 0.15vh 0.5vh; white-space: nowrap; }
.line-compare .cmp-table td:first-child { padding-left: 0; }
/* Counts / deltas align right; the group label stays left. */
.line-compare .cmp-table td:not(:first-child) { text-align: right; }

/* Column-header row above the breakdown. */
.line-compare .cmp-head { font-size: 1.3vh; opacity: 0.65; }

/* Separate the total row from the per-group breakdown below it. */
.line-compare .cmp-main td { padding-bottom: 0.6vh; }

.line-compare .cmp-main { font-size: 2vh; }

.line-compare .cmp-sub { font-size: 1.6vh; }

/* Title + grouping selector row above the activity-over-time chart. */
.line-chart-header {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1vh;
  margin: 0 0 1vh 0;
}

.line-chart-header h3 { margin: 0; }

.line-group-select {
  width: auto;
  font-size: 1.4vh;
  margin: 0;
}

/* Mobile: charts.js moves #stats-content into the mobile bar (#stats-mobile-bar);
   stack the header and give every chart its own full-width row. */
@media only screen and (max-aspect-ratio: 3.3/3) {
  .stats-header {
    flex-direction: column;
    align-items: stretch;
    margin: 1.5vh 1vh;
  }

  .stats-header h1 { display: none; }

  .stats-controls {
    flex-direction: column;
    align-items: stretch;
    gap: 1vh;
  }

  .stats-controls label { margin-left: 0; }
  .stats-controls select { width: 100%; }

  .charts-section { margin-top: 2vh; }

  .chart-row {
    flex-direction: column;
    height: auto;
    gap: 2vh;
  }

  .chart-cell {
    flex: none;
    height: 45vh;
  }

  .dc-cell { height: 38vh; }

  .line-row {
    flex-direction: column;
    height: auto;
    gap: 2vh;
  }

  .line-chart { flex: none; height: 50vh; }
  .line-compare { flex: none; height: auto; }

  .chart-cell h3, .line-row h3 { font-size: 2vh; }
  .canvas-legend { font-size: 1.1vh; }
  /* Pie legend: single scrolling column on mobile — no wrapping into extra columns
     (the line legend keeps its wrapping top row). */
  .legend-right .canvas-legend { flex-wrap: nowrap; }
}

/* Print Blocker */

DIV.print {
  display: none !important;
  visibility: hidden !important;
}

@media only print {
  DIV.title-bar,
  DIV.content,
  DIV.footer,
  DIV.footer-mobile,
  DIV.popup {
    display: none !important;
    visibility: hidden !important;
  }

  DIV.print {
    display: block !important;
    visibility: visible !important;
    transform: translate(-50%, -50%);
    opacity: 1;
    pointer-events: auto;
    user-select: auto;
    -moz-user-select: auto;
    -webkit-user-drag: auto;
    -webkit-user-select: auto;
    -ms-user-select: auto;
  }
}
