/* ==========================================================================
   Landing / shell styles

   Covers the animated dark background, the title card, and buttons.
   Panel styling lives in panel.css.
   ========================================================================== */

/* TEMPORARY - warm palette trial ahead of the galaxy/sunset background.
   See docs/09-background.md. The blue values are kept in comments so the
   previous look can be restored in one edit. */
:root {
  --bg-base: #0a0503;              /* was #04050a */
  --title-fg: #f5ece4;             /* was #e8ecf5 */
  --title-glow: rgb(235 152 86 / 0.4);   /* was rgb(126 178 235 / .35) */

  --btn-fg: #efe2d6;                       /* was #dce3f0 */
  --btn-border: rgb(210 190 175 / 0.45);   /* was rgb(180 190 210 / .45) */
  --btn-bg-hover: rgb(235 160 96 / 0.12);  /* was rgb(126 178 235 / .12) */

  /* Display face for the title card only. Panel and body copy stay on the
     system stack - legibility there is non-negotiable. */
  --font-display: "Cinzel", Georgia, "Times New Roman", serif;
  --font-body: system-ui, -apple-system, "Segoe UI", sans-serif;
}

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

html {
  color-scheme: dark;
}

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--bg-base);
  color: var(--title-fg);
  font-family: var(--font-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* --------------------------------------------------------------------------
   Animated background

   Composited layers only. Each blob animates transform and opacity, both of
   which the compositor handles without repainting. Do NOT animate
   background-image or background-position here - neither can be composited
   and both will jank on mobile.

   This whole layer is a placeholder for background art. Swapping it out
   should not touch any other rule.
   -------------------------------------------------------------------------- */

.bg {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  background: var(--bg-base);
}

/* Opens on black, then the colour wells up. The fade lives on the container,
   not the blobs - putting it on the blobs would fight the drift keyframes for
   control of opacity, and the later animation in the list would win. */
@media (prefers-reduced-motion: no-preference) {
  @keyframes bg-in {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
  .js .bg {
    animation: bg-in 2600ms cubic-bezier(0.4, 0, 0.2, 1) both;
  }
}

/* No filter: blur() here, deliberately.

   A radial-gradient with a soft falloff is ALREADY soft - stacking a blur on
   top of it buys nothing and costs a great deal. Worse, a blurred layer that
   also animates scale() must re-rasterize the blur every frame, which on an
   element this size saturates the compositor and makes the whole page feel
   laggy, hover included.

   Rule for this layer: translate and opacity only. No scale, no filter. */
.bg__blob {
  position: absolute;
  width: 70vmax;
  height: 70vmax;
  border-radius: 50%;
  opacity: 0.55;
}

/* TEMPORARY warm trial. Deep ember at top, burnt orange low and warm, with a
   dull red as the third layer so it does not read as a single hue. Previous
   blues kept in comments. */
.bg__blob--1 {
  /* was rgb(28 40 78 / .85) */
  background: radial-gradient(circle closest-side, rgb(112 48 14 / 0.85), transparent);
  top: -22vmax;
  left: -16vmax;
}

.bg__blob--2 {
  /* was rgb(58 26 66 / .8) */
  background: radial-gradient(circle closest-side, rgb(138 66 20 / 0.8), transparent);
  bottom: -26vmax;
  right: -18vmax;
}

.bg__blob--3 {
  /* was rgb(16 52 62 / .65) */
  background: radial-gradient(circle closest-side, rgb(86 26 22 / 0.7), transparent);
  top: 14vmax;
  right: -24vmax;
}

@media (prefers-reduced-motion: no-preference) {
  /* translate3d only - promotes to its own layer and moves a cached texture.
     Opacity is composited too. Neither triggers repaint or re-raster. */
  @keyframes drift-a {
    0%, 100% { transform: translate3d(0, 0, 0); opacity: 0.5; }
    50%      { transform: translate3d(7vmax, 5vmax, 0); opacity: 0.66; }
  }
  @keyframes drift-b {
    0%, 100% { transform: translate3d(0, 0, 0); opacity: 0.42; }
    50%      { transform: translate3d(-8vmax, -6vmax, 0); opacity: 0.6; }
  }
  @keyframes drift-c {
    0%, 100% { transform: translate3d(0, 0, 0); opacity: 0.3; }
    50%      { transform: translate3d(-5vmax, 8vmax, 0); opacity: 0.48; }
  }

  /* Long cycles on purpose. This is ambience, not an effect to be noticed.

     The negative delay is set inline in <head> from wall-clock time, so the
     drift continues across page navigations instead of snapping back to the
     start of the cycle on every load. */
  .bg__blob--1 {
    animation: drift-a 34s ease-in-out infinite;
    animation-delay: var(--drift-delay-1, 0s);
  }
  .bg__blob--2 {
    animation: drift-b 41s ease-in-out infinite;
    animation-delay: var(--drift-delay-2, 0s);
  }
  .bg__blob--3 {
    animation: drift-c 29s ease-in-out infinite;
    animation-delay: var(--drift-delay-3, 0s);
  }
}

/* --------------------------------------------------------------------------
   Landing layout
   -------------------------------------------------------------------------- */

/* Body is the column; main takes the slack so the footer sits at the bottom
   on short pages and is pushed below the fold on long ones. */
body {
  display: flex;
  flex-direction: column;
}

.landing {
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2rem;
  padding: 2rem 1.25rem;
  padding-top: max(2rem, env(safe-area-inset-top));
}

/* Title card sits OUTSIDE the panel, like a game title screen. */
.title-card {
  text-align: center;
}

.title-card__name {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 11vw, 4.5rem);
  font-weight: 600;
  letter-spacing: 0.04em;
  line-height: 1.05;
  text-shadow: 0 0 32px var(--title-glow);
}

/* Outlined wordmark. text-shadow does not apply to an <img>, so the glow
   moves to drop-shadow. It is a static filter on a non-animating property,
   so it rasterises once - unlike a blur on a moving element. */
.title-card__name--art {
  line-height: 0;
}

.title-card__name--art img {
  display: block;
  width: min(78vw, 30rem);
  height: auto;
  filter: drop-shadow(0 0 22px var(--title-glow));
}

.title-card__sub {
  margin: 0.35rem 0 0;
  font-size: 0.85rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgb(158 166 184);
}

@media (prefers-reduced-motion: no-preference) {
  /* Fade only. No translate - the previous version bumped upward on entry,
     which fought the floating idle that follows it. */
  @keyframes fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
  }

  /* Slow vertical float, like the title is suspended. Transform only, so it
     never conflicts with the opacity fade above. */
  @keyframes title-float {
    0%, 100% { transform: translate3d(0, 0, 0); }
    50%      { transform: translate3d(0, -6px, 0); }
  }

  /* Float lives on the WRAPPER, entrance fades live on the children.

     Splitting them across elements is what lets the veil fade the wrapper
     without ever touching the children's animation. Re-applying an animation
     restarts it from zero, so any rule that removed and restored the fade
     would replay the whole entrance on the way back. */
  .js .title-card {
    animation: title-float 7s ease-in-out 1450ms infinite;
  }

  .js .title-card__name {
    animation: fade-in 1100ms ease-out 350ms both;
  }

  /* Subtitle trails the name rather than arriving with it. */
  .js .title-card__sub {
    animation: fade-in 900ms ease-out 950ms both;
  }

  /* Opening delay applies ONLY to the first-load panel, so the name is read
     before the choice appears. Scoped to .is-intro - JS strips that class
     after the first open, so swapped-in and reopened panels are immediate.
     Without this scope the rule matched every panel in .landing and made the
     options popup wait a second and a half. */
  .js .landing .panel.is-intro.is-opening,
  .js .landing .panel.is-intro.is-opening .panel__edge {
    animation-delay: 1500ms;
  }
}

/* --- Fading the backdrop -------------------------------------------------
   Used both when an overlay opens over the landing and when leaving the page
   entirely. The background layer is never touched and keeps drifting.

   Asymmetric on purpose: fades OUT, snaps back IN.

   The transition is declared only on the .has-overlay / .is-exiting rules,
   never on the base. A transition is read from the state being moved toward,
   so entering those states animates, while leaving them reverts to a base
   with no transition and is therefore instant. Dismissing a popup should
   feel like the popup was lifted off, not like the page is fading up.

   Nothing here overrides an animation. An earlier version set
   `animation: none` on the title to defeat the intro fade's fill-mode, but
   restoring that rule re-applied the animation and replayed the entrance
   every time a popup was dismissed. Fading the parent sidesteps it: parent
   opacity multiplies down the subtree regardless of what the children's own
   animations are asserting.

   The overlay case is handled by the .landing > * rule in panel.css. This is
   the page-exit equivalent. */
.is-exiting .landing > * {
  opacity: 0;
  pointer-events: none;
  transition: opacity 220ms ease-out;
}

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0.6rem 1rem;
  border: 1px solid var(--btn-border);
  border-radius: 3px;
  background: transparent;
  color: var(--btn-fg);
  font: inherit;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 160ms ease, border-color 160ms ease;
}

.btn:hover,
.btn:focus-visible {
  background: var(--btn-bg-hover);
  border-color: var(--panel-accent);
}

.btn__hint {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.72rem;
  color: rgb(158 166 184);
}

.btn--stack {
  flex-direction: column;
  gap: 0;
  padding-block: 0.5rem;
}

:where(a, button, input, [tabindex]):focus-visible {
  outline: 2px solid var(--panel-accent);
  outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   Forms - used by the game options panel
   -------------------------------------------------------------------------- */

.field {
  margin-bottom: 1.1rem;
}

.field__label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 0.4rem;
  font-size: 0.9rem;
}

.field__value {
  color: var(--panel-accent);
  font-variant-numeric: tabular-nums;
}

.field input[type="range"] {
  width: 100%;
  min-height: 44px;
  accent-color: var(--panel-accent);
}

.field--check {
  display: flex;
  gap: 0.6rem;
  align-items: flex-start;
  font-size: 0.9rem;
}

.field--check input {
  margin-top: 0.35rem;
  width: 18px;
  height: 18px;
  accent-color: var(--panel-accent);
}

.muted {
  color: var(--panel-fg-muted);
}

.small {
  font-size: 0.85rem;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */

.site-footer {
  flex: 0 0 auto;
  width: 100%;
  max-width: 52rem;
  margin: 0 auto;
  padding: 0 1.25rem 1.5rem;
  padding-bottom: max(1.5rem, env(safe-area-inset-bottom));
  font-size: 0.8rem;
  color: var(--panel-fg-muted, rgb(158 166 184));
}

/* Ornamental divider - a hairline that fades at both ends with a diamond at
   the centre. Reads as part of the fantasy panel language rather than a
   default <hr>. Built from borders and a rotated square, so no image. */
.site-footer__rule {
  position: relative;
  height: 1px;
  margin-bottom: 1rem;
  background: linear-gradient(
    to right,
    transparent,
    rgb(180 190 210 / 0.28) 22%,
    rgb(180 190 210 / 0.28) 78%,
    transparent
  );
}

.site-footer__diamond {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 6px;
  height: 6px;
  translate: -50% -50%;
  rotate: 45deg;
  background: var(--bg-base, #04050a);
  border: 1px solid rgb(180 190 210 / 0.45);
}

.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1rem;
}

.site-footer__line {
  margin: 0;
}

.site-footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

.site-footer__link {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  padding-bottom: 1px;
  transition: color 140ms ease, border-color 140ms ease;
}

.site-footer__link:hover,
.site-footer__link:focus-visible {
  color: var(--title-fg);
  border-bottom-color: var(--panel-accent, rgb(126 178 235));
}

/* Icon links get a real touch target rather than a 20px hit area, and no
   underline - the border would sit awkwardly under a glyph. */
.site-footer__link--icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border-bottom: 0;
  border-radius: 3px;
}

.site-footer__link--icon:hover,
.site-footer__link--icon:focus-visible {
  border-bottom: 0;
  background: rgb(255 255 255 / 0.06);
}

.site-footer__icon {
  width: 20px;
  height: 20px;
}

/* Pull the icon row's padding back so it optically aligns with the text on
   the other side of the footer. */
.site-footer__links:has(.site-footer__link--icon) {
  gap: 0.25rem;
  margin-right: -0.5rem;
}

/* Centre everything when the row would otherwise wrap awkwardly. */
@media (max-width: 30rem) {
  .site-footer__inner {
    flex-direction: column;
    text-align: center;
  }
}

/* The footer belongs to the page, so it fades with everything else behind an
   overlay and on exit. */
.has-overlay .site-footer,
.is-exiting .site-footer {
  opacity: 0;
  pointer-events: none;
  transition: opacity 240ms ease-out;
}
