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

html, body {
  width: 100%;
  height: 100%;
  background: #000;
  overflow: hidden;
  touch-action: none;
}

#globe-canvas {
  position: fixed;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  cursor: grab;
}

#globe-canvas.dragging {
  cursor: grabbing;
}

#pin-labels {
  position: fixed;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
}

/* Repositioned every frame via `transform` on this element only — kept
   free of any transition/scale so the per-frame update never touches
   text layout or interrupts the label's own pop transition below. */
.pin-label-anchor {
  position: absolute;
  left: 0;
  top: 0;
}

.pin-label {
  position: absolute;
  transform: translate(-50%, -140%) scale(0.7);
  padding: 6px 12px;
  background: rgba(8, 14, 28, 0.82);
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 999px;
  color: #eaf3ff;
  font-family: "Courier New", monospace;
  font-size: 13px;
  letter-spacing: 0.03em;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease, transform 0.25s ease;
  will-change: opacity, transform;
}

.pin-label.visible {
  opacity: 1;
  transform: translate(-50%, -160%) scale(1);
  /* only clickable while actually shown — a hidden label sits at its last
     known position and shouldn't be able to intercept clicks there */
  pointer-events: auto;
  cursor: pointer;
}

.hidden {
  display: none !important;
}

/* -- hamburger button -- */

#hamburger-btn {
  position: fixed;
  top: 20px;
  right: 20px;
  z-index: 20;
  width: 44px;
  height: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  background: rgba(8, 14, 28, 0.7);
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 10px;
  cursor: pointer;
}

#hamburger-btn span {
  display: block;
  width: 20px;
  height: 2px;
  background: #eaf3ff;
  border-radius: 1px;
}

/* -- nav menu -- */

#nav-menu {
  position: fixed;
  top: 72px;
  right: 20px;
  z-index: 20;
  background: rgba(8, 14, 28, 0.9);
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 12px;
  padding: 8px;
  min-width: 220px;
}

#nav-menu-list {
  list-style: none;
}

#nav-menu-list li button {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  color: #eaf3ff;
  font-family: system-ui, -apple-system, sans-serif;
  font-size: 14px;
  padding: 10px 12px;
  border-radius: 8px;
  cursor: pointer;
}

#nav-menu-list li button:hover {
  background: rgba(255, 255, 255, 0.1);
}

/* -- overlay card -- */

#card-backdrop {
  position: fixed;
  inset: 0;
  z-index: 30;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

#card {
  position: relative;
  width: 100%;
  max-width: 520px;
  max-height: min(640px, 85vh);
  overflow-y: auto;
  background: rgba(14, 20, 34, 0.97);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 16px;
  padding: 32px 28px 28px;
  color: #eaf3ff;
  font-family: system-ui, -apple-system, sans-serif;

  /* Closed state: a flat sliver pinned to the label it's popping out from
     (transform-origin x is set inline per open, in cards.js; origin-y is
     always the card's own top edge, so growth only ever extends downward —
     never upward — matching "the bottom opens"). This same closed state is
     also what closing animates back down to, via the transition below. */
  opacity: 0;
  transform: scale(0.2, 0.02);
  transform-origin: 50% 0%;
  transition: transform 0.22s ease-out, opacity 0.18s ease-out;
}

/* Opening plays as a distinct two-stage keyframe animation rather than a
   plain transition: first it pops out to a modest, title-bar-sized shape
   (mostly full width, still short), THEN the bottom drops open the rest of
   the way to reveal the body content — two visually distinct beats, not one
   smooth grow, but fast/fluid enough that it reads as one continuous motion
   rather than two separate steps. Closing doesn't need the same two beats;
   removing this class just lets the plain transition above shrink it back
   down. */
@keyframes card-pop-open {
  0% {
    transform: scale(0.2, 0.02);
    opacity: 0;
  }
  40% {
    transform: scale(1, 0.22);
    opacity: 1;
  }
  100% {
    transform: scale(1, 1);
    opacity: 1;
  }
}

#card.opening {
  animation: card-pop-open 0.32s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

#card-close {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.25);
  background: rgba(255, 255, 255, 0.05);
  color: #eaf3ff;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}

#card-close:hover {
  background: rgba(255, 255, 255, 0.15);
}

.card-title {
  font-family: "Courier New", monospace;
  font-size: 22px;
  letter-spacing: 0.02em;
  margin-bottom: 18px;
  padding-right: 30px;
}

.card-body p {
  font-size: 15px;
  line-height: 1.6;
  color: #d7e3f5;
  margin-bottom: 14px;
}

.card-download {
  display: inline-block;
  margin-top: 4px;
  padding: 12px 20px;
  background: #ea4335;
  color: #fff;
  text-decoration: none;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 600;
}

.card-download:hover {
  background: #d33a2c;
}

.card-linklist {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 12px;
}

.card-linklist a {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 16 / 9;
  background: linear-gradient(135deg, #1c66d4, #239b3e);
  border-radius: 8px;
  color: #fff;
  text-decoration: none;
  font-family: "Courier New", monospace;
  font-size: 13px;
  text-align: center;
  padding: 8px;
  overflow: hidden;
}

.card-linklist a:hover {
  filter: brightness(1.15);
}

.card-linklist-thumb {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 6px;
}

.card-linklist-label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  padding: 6px;
  background: rgba(0, 0, 0, 0.85);
  color: #fff;
  font-family: system-ui, -apple-system, sans-serif;
  font-size: 10px;
  line-height: 1.25;
  text-align: left;
  opacity: 0;
  transition: opacity 0.15s ease-out;
}

.card-linklist a:hover .card-linklist-label,
.card-linklist a:focus-visible .card-linklist-label {
  opacity: 1;
}

.card-spotify-embed {
  display: block;
  margin-top: 4px;
}

.card-contact-mailto {
  display: inline-block;
  margin-bottom: 20px;
  padding: 12px 20px;
  background: #ea4335;
  color: #fff;
  text-decoration: none;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 600;
}

.card-contact-mailto:hover {
  background: #d33a2c;
}

.card-socials {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.card-socials a {
  display: inline-block;
  padding: 8px 14px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 999px;
  color: #eaf3ff;
  text-decoration: none;
  font-size: 13px;
}

.card-socials a:hover {
  background: rgba(255, 255, 255, 0.18);
}

.card-footer-note {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  color: #8ba0bd;
  font-size: 13px;
  font-style: italic;
  text-align: center;
}

.card-image {
  display: block;
  width: 100%;
  max-height: 60vh;
  object-fit: contain;
  border-radius: 10px;
}

.card-image-caption {
  margin-top: 10px;
  color: #d7e3f5;
  font-size: 14px;
  text-align: center;
}

/* -- spaceship NPC: flat pixel sprite + dialogue bubble -- */

/* Travels in nose-first: starts translated up-and-right of its resting spot,
   rotated to -110deg so the sprite's top point (its "nose" at rest) faces
   along the actual direction of travel (down and to the left) instead of
   just sliding in sideways. Holds that heading for most of the flight, then
   only in the last stretch does it both finish closing the distance and
   rotate back to 0deg (nose up) — the "turn to point up" as it settles. No
   overshoot/bounce anywhere, just a straight decelerating glide to a stop. */
@keyframes npc-fly-in {
  0% {
    transform: translate(260px, -100px) rotate(-110deg);
    opacity: 0;
  }
  10% {
    opacity: 1;
  }
  70% {
    transform: translate(18px, -8px) rotate(-110deg);
  }
  100% {
    transform: translate(0, 0) rotate(0deg);
  }
}

#npc-container {
  position: fixed;
  /* meaningfully lower and further inward than the last pass — sitting
     closer to the globe itself rather than isolated up in the corner, and
     with enough headroom above for the dialogue bubble that it doesn't
     clip against the viewport top */
  top: 320px;
  right: 150px;
  z-index: 15;
  /* "both" fill mode matters here: it holds the 0% (hidden/offset) state
     during the delay too, not just after — without it the ship would sit
     fully visible at rest for that first half second, then jump to hidden
     right as the animation kicks in. NPC_ENTRANCE_DELAY_MS in
     npcDialogue.js is timed to match this delay + duration. */
  animation: npc-fly-in 2s ease-out 2.5s both;
}

#npc-ship {
  display: block;
  width: 32px;
  height: 32px;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  cursor: pointer;
  /* Facing direction while wandering (js/npcWander.js) — a CSS variable
     rather than JS setting transform directly, since this animation is
     already the sole owner of this element's transform (running
     continuously); JS instead just updates the variable this keyframe
     reads, so the two never fight over the property. Kept off the
     ship's #npc-container parent, which also holds the dialogue bubble —
     rotating that would turn the bubble's text sideways with it. */
  --ship-heading: 0deg;
  animation: npc-bob 2.2s ease-in-out infinite;
}

@keyframes npc-bob {
  0%, 100% {
    transform: translateY(0) rotate(var(--ship-heading));
  }
  50% {
    transform: translateY(-4px) rotate(var(--ship-heading));
  }
}

#npc-dialogue {
  position: absolute;
  right: 0;
  bottom: 100%;
  margin-bottom: 14px;
  /* wider than the first pass on purpose — this pixel font is quite wide
     per character, so a narrow box turned tall/thin with long lines and
     clipped against the top of the viewport; wider means fewer, longer
     lines instead */
  width: max-content;
  max-width: 300px;
  padding: 14px 16px;
  background: rgba(8, 14, 28, 0.92);
  border: 2px solid rgba(255, 255, 255, 0.3);
  border-radius: 4px;
  color: #eaf3ff;
  font-family: "Press Start 2P", "Courier New", monospace;
  font-size: 9px;
  line-height: 1.8;
  opacity: 0;
  pointer-events: none;
  transform: translateY(6px) scale(0.92);
  transition: opacity 0.2s ease, transform 0.2s ease;
}

#npc-dialogue::after {
  content: "";
  position: absolute;
  right: 10px;
  top: 100%;
  width: 0;
  height: 0;
  border-left: 7px solid transparent;
  border-right: 7px solid transparent;
  border-top: 7px solid rgba(8, 14, 28, 0.92);
}

/* Applied by js/npcWander.js right before each line types, based on the
   ship's live on-screen position — the ship (and this bubble, anchored to
   it) can now be anywhere on screen instead of a fixed corner, so the
   default "open up-and-left" placement isn't safe to assume anymore. */
#npc-dialogue.flip-right {
  right: auto;
  left: 0;
}

#npc-dialogue.flip-right::after {
  right: auto;
  left: 10px;
}

#npc-dialogue.flip-down {
  bottom: auto;
  top: 100%;
  margin-bottom: 0;
  margin-top: 14px;
}

#npc-dialogue.flip-down::after {
  top: auto;
  bottom: 100%;
  border-top: none;
  border-bottom: 7px solid rgba(8, 14, 28, 0.92);
}

#npc-dialogue.visible {
  opacity: 1;
  transform: translateY(0) scale(1);
}

/* -- hidden star easter egg: 8-bit word game -- */

#game-backdrop {
  position: fixed;
  inset: 0;
  z-index: 30;
  background: rgba(0, 0, 0, 0.65);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

#game {
  position: relative;
  width: 100%;
  max-width: 340px;
  max-height: 85vh;
  overflow-y: auto;
  background: rgba(14, 20, 34, 0.97);
  border: 2px solid rgba(255, 255, 255, 0.25);
  border-radius: 10px;
  padding: 36px 24px 28px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

#game-close {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.25);
  background: rgba(255, 255, 255, 0.05);
  color: #eaf3ff;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}

#game-close:hover {
  background: rgba(255, 255, 255, 0.15);
}

#game-title {
  margin: 0;
  font-family: "Press Start 2P", "Courier New", monospace;
  font-size: 18px;
  letter-spacing: 2px;
  color: #eaf3ff;
}

.game-row {
  display: flex;
  gap: 8px;
}

.game-tile {
  width: 46px;
  height: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid rgba(255, 255, 255, 0.25);
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.04);
  color: #eaf3ff;
  font-family: "Press Start 2P", "Courier New", monospace;
  font-size: 18px;
}

.game-tile--filled {
  border-color: rgba(255, 255, 255, 0.55);
}

.game-tile--correct {
  background: #239b3e;
  border-color: #239b3e;
}

.game-tile--present {
  background: #c9a227;
  border-color: #c9a227;
}

.game-tile--absent {
  background: #2a323f;
  border-color: #2a323f;
}

#game-message {
  margin-top: 10px;
  max-width: 260px;
  text-align: center;
  font-family: "Press Start 2P", "Courier New", monospace;
  font-size: 11px;
  line-height: 1.8;
  color: #eaf3ff;
}

/* -- mobile fallback (brief section 8): hidden on desktop, replaces the
   hamburger/pin/NPC chrome below the breakpoint with a frosted sheet that's
   the only way into the cards; the globe stays visible but frozen behind it -- */

#mobile-overlay {
  display: none;
}

@media (max-width: 767px) and (pointer: coarse) {
  #hamburger-btn,
  #nav-menu,
  #pin-labels,
  #npc-container {
    display: none;
  }

  #globe-canvas {
    cursor: default;
  }

  #mobile-overlay {
    display: flex;
    flex-direction: column;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 15;
    max-height: 68vh;
    padding: 20px 20px 28px;
    background: rgba(10, 16, 30, 0.55);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    border-top: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: 20px 20px 0 0;
    overflow-y: auto;
    touch-action: pan-y;
  }

  #mobile-message {
    margin-bottom: 16px;
    color: #d7e3f5;
    font-size: 13px;
    line-height: 1.5;
    text-align: center;
  }

  #mobile-menu-list {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 8px;
  }

  #mobile-menu-list button {
    display: block;
    width: 100%;
    text-align: left;
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid rgba(255, 255, 255, 0.18);
    color: #eaf3ff;
    font-family: system-ui, -apple-system, sans-serif;
    font-size: 15px;
    padding: 12px 14px;
    border-radius: 10px;
    cursor: pointer;
  }
}
