/*
 * Liquid-metal buttons.
 *
 * Every button-like control on the dashboard gets a metal rim and a solid core
 * with CSS alone. Three prominent CTAs additionally mount a real WebGL shader
 * (liquid-metal-shader.bundle.js), which draws the rim instead; if WebGL is
 * unavailable, those buttons simply keep this CSS design.
 *
 * Layout is untouched: padding, height, font size and width still come from
 * the existing rules, so every button stays as wide as its label. Only paint
 * and interaction states are defined here.
 *
 * The rim is a gradient border (a transparent 2px border over a border-box
 * gradient, with the core clipped to the padding box). No pseudo-elements are
 * used for the look, so existing ::before icons such as the topbar Discord
 * glyph keep working.
 *
 * Specificity: ":not(#lm)" matches every element and adds one id's worth of
 * specificity. This sheet loads last and has to win over the many class-based
 * page-specific button rules without resorting to !important.
 */

:root {
  --lm-purple: #7f00ff;
  --lm-ink: #fbf7ff;
  --lm-focus: #d7b3ff;
  /* Rims start and end on the same colour so the flowing animation loops
     without a visible seam. */
  --lm-rim: linear-gradient(115deg, #3a1d63 0%, #cfb8ff 14%, #5a2aa0 28%, #f6efff 42%, #6d33c4 56%, #b98cff 70%, #2a1545 86%, #3a1d63 100%);
  --lm-rim-quiet: linear-gradient(115deg, #2a1d3b 0%, #8e79ad 16%, #34224c 32%, #c9b8e2 48%, #3a2756 64%, #7d68a0 80%, #231733 92%, #2a1d3b 100%);
  /* Text sits on these, never on the moving metal. Contrast with --lm-ink:
     quiet core top #1d1330 is about 17:1; primary core top #9a2bff is about
     5.1:1 and only gets darker towards #5c00bb. */
  --lm-core: linear-gradient(180deg, #1d1330 0%, #0a0612 100%);
  --lm-core-primary: linear-gradient(180deg, #9a2bff 0%, #7f00ff 48%, #5c00bb 100%);
  --lm-shadow: 0 0 0 1px rgba(12, 5, 24, 0.65), 0 8px 22px -12px rgba(127, 0, 255, 0.45), 0 2px 5px rgba(0, 0, 0, 0.35);
  --lm-shadow-primary: 0 0 0 1px rgba(20, 4, 44, 0.7), 0 12px 30px -12px rgba(127, 0, 255, 0.75), 0 2px 6px rgba(0, 0, 0, 0.35);
  --lm-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Base: every button-like control, in its quiet (secondary) finish. */
:is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button):not(#lm) {
  position: relative;
  isolation: isolate;
  border: 2px solid transparent;
  border-radius: 999px;
  color: var(--lm-ink);
  background: var(--lm-core) padding-box, var(--lm-rim-quiet) border-box;
  background-size: 100% 100%, 300% 100%;
  background-position: 0 0, 0% 50%;
  box-shadow: var(--lm-shadow);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
  cursor: pointer;
  transition:
    transform 0.2s var(--lm-ease-spring),
    box-shadow 0.2s ease,
    filter 0.2s ease;
  /* Static while idle: declared, but paused until an interaction below runs
     it. A paused animation repaints nothing, and resuming continues from the
     same point instead of snapping the rim back to its start. */
  animation: lm-flow 9s linear infinite;
  animation-play-state: paused;
  -webkit-tap-highlight-color: transparent;
}

/* Primary finish: purple core, brighter chrome rim. */
:is(.button:not(.ghost):not(.quiet), .cta.primary, .mission-action.primary, .shop-buy-button:not(.secondary)):not(#lm) {
  color: #fff;
  background: var(--lm-core-primary) padding-box, var(--lm-rim) border-box;
  background-size: 100% 100%, 300% 100%;
  background-position: 0 0, 0% 50%;
  box-shadow: var(--lm-shadow-primary);
}

/* Hover. */
:is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button):hover:not(:disabled):not([aria-disabled="true"]):not([aria-busy="true"]):not(#lm) {
  transform: translateY(-1px);
  filter: brightness(1.1) saturate(1.05);
}

/* Pressed. */
:is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button):active:not(:disabled):not([aria-disabled="true"]):not([aria-busy="true"]):not(#lm) {
  transform: translateY(1px) scale(0.98);
  filter: brightness(0.96);
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.45), 0 0 0 1px rgba(12, 5, 24, 0.7);
  transition-duration: 0.08s;
}

/* Rim motion runs only while someone is interacting with the button.
   Hover is limited to devices that genuinely hover: on touch screens :hover
   sticks after a tap, which would leave a rim flowing indefinitely. */
@media (hover: hover) and (pointer: fine) {
  :is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button):hover:not(:disabled):not([aria-disabled="true"]):not(#lm) {
    animation-play-state: running;
  }
}

:is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button):is(:focus-visible, :active):not(:disabled):not([aria-disabled="true"]):not(#lm) {
  animation-play-state: running;
}

/* Keyboard focus: a real, visible outline, never removed. It is not clipped
   because nothing here sets overflow on the button. #d7b3ff against the
   dashboard's near-black background is well above 3:1. */
:is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button):focus-visible:not(#lm) {
  outline: 2px solid var(--lm-focus);
  outline-offset: 3px;
}

/* Disabled. Still readable (text is about 10:1 on this core); it just stops
   moving and stops responding. Listed after the primary rule so it wins at
   equal specificity. */
:is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button):is(:disabled, [aria-disabled="true"]):not([aria-busy="true"]):not(#lm) {
  cursor: not-allowed;
  color: #cbbde0;
  background: linear-gradient(180deg, #1b1624, #120e19) padding-box, linear-gradient(115deg, #3b3447, #5a5168, #3b3447) border-box;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5);
  text-shadow: none;
  opacity: 1;
  filter: none;
  transform: none;
  animation: none;
}

/* Loading: ordinary form posts (aria-busy, set by liquid-metal.js) and the
   settings panels, whose own script marks the form while it saves. The form
   selector outranks the disabled rule, because guild.js disables the button
   while saving and this state should show instead. */
:is(form[data-saving="true"], form.saving) :is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button)[type="submit"]:not(#lm),
:is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button):is([aria-busy="true"], [data-lm-loading="true"]):not(#lm) {
  cursor: progress;
  /* No pointer-events: none here: it would hide the progress cursor, since the
     pointer would never be over the element. liquid-metal.js stops repeat
     clicks on a loading button instead. */
  padding-inline-start: 2.3em;
  color: #fff;
  filter: none;
  transform: none;
  /* The name is restated because a saving settings button is also disabled,
     and the disabled rule removes the animation. */
  animation-name: lm-flow;
  animation-duration: 2.4s;
  animation-play-state: running;
}

:is(form[data-saving="true"], form.saving) :is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button)[type="submit"]:not(#lm)::after,
:is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button):is([aria-busy="true"], [data-lm-loading="true"]):not(#lm)::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 0.85em;
  width: 0.95em;
  height: 0.95em;
  margin-top: -0.475em;
  box-sizing: border-box;
  border: 2px solid rgba(255, 255, 255, 0.35);
  border-top-color: #fff;
  border-radius: 50%;
  animation: lm-spin 0.7s linear infinite;
}

/* State cursors. The site's custom cursor rule in styles.css sets the pointer
   on every button with !important, which would otherwise show a pointer over
   a disabled or saving control. These are the only !important declarations in
   this sheet, limited to the cursor of liquid-metal controls in those states;
   the global cursor system is left alone. Loading is listed after disabled at
   equal or higher specificity, so a saving button that is also disabled shows
   progress. */
:is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button):is(:disabled, [aria-disabled="true"]):not(#lm) {
  cursor: not-allowed !important;
}

:is(form[data-saving="true"], form.saving) :is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button)[type="submit"]:not(#lm),
:is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button):is([aria-busy="true"], [data-lm-loading="true"]):not(#lm) {
  cursor: progress !important;
}

/* Press ripple. The layer is clipped to the core, so the ripple never spills
   over the rim, and it sits under the label. */
.lm-ripple-layer {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  border-radius: inherit;
  pointer-events: none;
}

.lm-ripple {
  position: absolute;
  left: var(--lm-ripple-x);
  top: var(--lm-ripple-y);
  width: var(--lm-ripple-size);
  height: var(--lm-ripple-size);
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(circle, rgba(233, 214, 255, 0.42) 0%, rgba(233, 214, 255, 0.16) 35%, rgba(233, 214, 255, 0) 65%);
  transform: translate(-50%, -50%) scale(0);
  animation: lm-ripple 0.6s cubic-bezier(0.2, 0.7, 0.3, 1) forwards;
}

/* Real shader. Once mounted, the canvas draws the rim, so the CSS rim stops.
   Two ids of specificity to outrank the primary finish above. */
.lm-has-shader:not(#lm):not(#lm-shader) {
  background: #12081f padding-box, #12081f border-box;
  animation: none;
}

.lm-has-shader:is(:disabled, [aria-disabled="true"]):not(#lm):not(#lm-shader) {
  background: #120e19;
}

/* Covers the border box, so the canvas shows through the transparent border
   as the rim. */
.lm-shader {
  position: absolute;
  inset: -2px;
  z-index: -2;
  display: block;
  overflow: hidden;
  border-radius: inherit;
  pointer-events: none;
}

.lm-shader canvas {
  border-radius: inherit;
}

/* The solid core the label sits on, above the canvas. */
.lm-shader-core {
  position: absolute;
  inset: 0;
  z-index: -1;
  display: block;
  border-radius: inherit;
  pointer-events: none;
  background: var(--lm-core-primary);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18), inset 0 -1px 1px rgba(0, 0, 0, 0.35);
}

.lm-has-shader:is(:disabled, [aria-disabled="true"]) .lm-shader-core {
  background: linear-gradient(180deg, #1b1624, #120e19);
  box-shadow: none;
}

@keyframes lm-flow {
  /* 150% of a 300%-wide image moves exactly one tile, so the loop is seamless. */
  to { background-position: 0 0, 150% 50%; }
}

@keyframes lm-spin {
  to { transform: rotate(360deg); }
}

@keyframes lm-ripple {
  0% { opacity: 1; transform: translate(-50%, -50%) scale(0); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1); }
}

@keyframes lm-pulse {
  50% { opacity: 0.35; }
}

@media (prefers-reduced-motion: reduce) {
  :is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button):not(#lm) {
    animation: none;
    transition: none;
  }

  :is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button):hover:not(:disabled):not([aria-disabled="true"]):not([aria-busy="true"]):not(#lm),
  :is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button):active:not(:disabled):not([aria-disabled="true"]):not([aria-busy="true"]):not(#lm) {
    transform: none;
  }

  /* Loading restores the rim animation for everyone else; not here. Same
     selectors as the loading rule, so this later rule wins. */
  :is(form[data-saving="true"], form.saving) :is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button)[type="submit"]:not(#lm),
  :is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button):is([aria-busy="true"], [data-lm-loading="true"]):not(#lm) {
    animation: none;
  }

  /* Loading still needs a visible signal; a slow fade instead of a spin. */
  :is(form[data-saving="true"], form.saving) :is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button)[type="submit"]:not(#lm)::after,
  :is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button):is([aria-busy="true"], [data-lm-loading="true"]):not(#lm)::after {
    border-color: #fff;
    animation: lm-pulse 1.6s ease-in-out infinite;
  }

  .lm-ripple-layer {
    display: none;
  }
}

/* Windows high contrast and similar: hand the colours back to the system. */
@media (forced-colors: active) {
  :is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button):not(#lm) {
    border-color: ButtonText;
    background: ButtonFace;
    color: ButtonText;
    box-shadow: none;
    text-shadow: none;
    animation: none;
  }

  :is(form[data-saving="true"], form.saving) :is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button)[type="submit"]:not(#lm),
  :is(.button, .cta, .mission-action, .shop-buy-button, .orbit-button):is([aria-busy="true"], [data-lm-loading="true"]):not(#lm) {
    animation: none;
  }

  .lm-shader,
  .lm-shader-core,
  .lm-ripple-layer {
    display: none;
  }
}
