/* ==========================================================================
   sections.css - hero, games library, about
   ========================================================================== */

/* ==========================================================================
   hero
   ========================================================================== */

.hero {
  position: relative;
  overflow: hidden;
  background-color: var(--bg-deep);
}

.hero__stage {
  position: relative;
}

/* Each slide is a self-contained full-height panel, so adding a game means
   adding one <article class="slide"> with its own image - no nth-child CSS.

   Only the slide on screen is in the flow, so the hero never changes height
   mid-swipe. Everything else waits off-stage on the side it should come in
   from, and slideshow.js sets data-dir to say which side that is. No z-index
   is used: every slide is positioned, so document order decides who paints on
   top, and the incoming slide is always the one arriving on top of the
   outgoing one. */
.slide {
  position: absolute;
  inset: 0;
  isolation: isolate;
  display: flex;
  align-items: center;
  min-height: 640px;
  padding-top: calc(var(--nav-h) + var(--sp-7));
  padding-bottom: calc(var(--sp-8) + var(--sp-5));
  opacity: 0;
  visibility: hidden;
  transform: translateX(100%);
  pointer-events: none;
  transition: transform var(--t-swipe) var(--ease),
    opacity var(--t-swipe) var(--ease), visibility 0s;
}

/* waiting on the left, because the show is moving backwards */
.slideshow[data-dir="prev"] .slide:not(.is-active):not(.is-leaving) {
  transform: translateX(-100%);
}

/* the slide on screen, and the one arriving, are both fully visible - a push,
   not a cross-fade */
.slide.is-active,
.slide.is-entering,
.slide.is-leaving {
  opacity: 1;
  visibility: visible;
}

.slide.is-active {
  position: relative;
  transform: none;
  pointer-events: auto;
}

/* the slide being pushed out travels the opposite way to its replacement */
.slideshow[data-dir="next"] .slide.is-leaving {
  transform: translateX(-100%);
}

.slideshow[data-dir="prev"] .slide.is-leaving {
  transform: translateX(100%);
}

/* one frame with the animation off. This is how an off-stage slide is moved
   to the other side once the loop wraps, and how the incoming slide is parked
   on its entry line before it is allowed to move - both while nobody can see
   it, so nothing ever flickers back across the screen. */
.slide.is-jumping {
  transition: none;
}

@supports (min-height: 100svh) {
  .slide {
    min-height: max(640px, 100svh);
  }
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* The fortress is 266x182 hand-made pixel art stretched over a whole screen.
   Nearest-neighbour keeps every pixel a crisp square instead of smearing the
   whole thing into a blur. `cover` means it always reaches the edges, so there
   is never a letterbox bar. */
.hero__media--pixel {
  image-rendering: -moz-crisp-edges;
  image-rendering: pixelated;
}

/* legibility scrim: darkens under the navbar, behind the copy and blends
   the bottom edge into the next section */
.hero__veil {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(180deg, rgba(4, 7, 17, 0.85) 0%, rgba(4, 7, 17, 0) 26%),
    linear-gradient(
      100deg,
      rgba(4, 7, 17, 0.95) 0%,
      rgba(4, 7, 17, 0.88) 42%,
      rgba(4, 7, 17, 0.62) 100%
    ),
    linear-gradient(0deg, var(--bg-deep) 0%, rgba(4, 7, 17, 0) 40%);
}

/* The pixel-art slide *is* the artwork rather than a photo behind a headline,
   so the scrim is pulled right back: enough darkness on the left to carry the
   copy, and the fortress left almost untouched on the right. The bottom fade
   stays, so the hero still melts into the games section. */
.hero__veil--soft {
  background: linear-gradient(180deg, rgba(4, 7, 17, 0.8) 0%, rgba(4, 7, 17, 0) 22%),
    linear-gradient(
      100deg,
      rgba(4, 7, 17, 0.86) 0%,
      rgba(4, 7, 17, 0.5) 46%,
      rgba(4, 7, 17, 0.1) 100%
    ),
    linear-gradient(0deg, var(--bg-deep) 0%, rgba(4, 7, 17, 0) 34%);
}

.hero__grid {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
  gap: var(--sp-8);
}

.hero__copy {
  max-width: 44ch;
}

.hero__logo {
  width: clamp(240px, 46vw, 460px);
  height: auto;
  margin-bottom: var(--sp-5);
}

/* The TIRELESS mark is a long flat wordmark (5.3:1). The Curse Of Ashes mark
   is a stacked emblem (1.9:1), so matching widths would make it sit nearly
   three times as tall. It gets its own, narrower, column instead. */
.hero__logo--emblem {
  width: clamp(250px, 36vw, 420px);
}

.hero__subtitle {
  font-size: var(--fs-md);
  line-height: 1.6;
  color: var(--text-muted);
  margin-bottom: var(--sp-6);
}

.hero__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-6);
}

.hero__aside {
  width: 100%;
}

.hero__scroll {
  position: absolute;
  right: var(--gutter);
  bottom: var(--sp-5);
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-dim);
}

.hero__scroll:hover {
  color: var(--c-cyan);
}

.hero__scroll span {
  animation: nudge 2.4s var(--ease) infinite;
}

@keyframes nudge {
  0%,
  70%,
  100% {
    transform: translateY(0);
  }
  35% {
    transform: translateY(4px);
  }
}

.slide-dots {
  position: absolute;
  left: 50%;
  bottom: var(--sp-5);
  transform: translateX(-50%);
  display: flex;
  gap: var(--sp-3);
}

.slide-dots[hidden] {
  display: none;
}

/* The dots are also the slideshow's progress readout: each one is a track with
   a cyan fill that runs for exactly as long as data-autoplay, so the slide
   changes at the moment the bar lands. The duration comes from --slide-ms,
   which the slideshow sets from data-autoplay. */
.slide-dot {
  position: relative;
  overflow: hidden;
  width: 40px;
  height: 5px;
  padding: 0;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--line-strong);
  cursor: pointer;
  transition: background-color var(--t-fast) var(--ease);
}

.slide-dot:hover {
  background: var(--text-dim);
}

/* the active track is a touch brighter than the idle ones - the fill is what
   says how far along it is */
.slide-dot[aria-current="true"] {
  background: color-mix(in srgb, var(--text) 26%, transparent);
}

.slide-dot__fill {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--c-cyan);
  transform: scaleX(0);
  transform-origin: left center;
}

.slide-dot.is-timing .slide-dot__fill {
  animation: slide-progress var(--slide-ms, 10000ms) linear forwards;
}

/* hovering the hero, or leaving the tab, freezes the bar where it is */
.slideshow.is-paused .slide-dot.is-timing .slide-dot__fill {
  animation-play-state: paused;
}

@keyframes slide-progress {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

/* ==========================================================================
   games
   One horizontal rail of cards. The row slides sideways instead of stacking
   - mouse drag (js/dragscroll.js), trackpad, touch, or arrow keys once the
   rail has focus. Cards are a fixed width so the rail stays predictable.
   ========================================================================== */

.games {
  background-color: var(--bg-page);
}

.games__grid {
  display: flex;
  gap: var(--sp-5);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--gutter);
  /* full-bleed: the rail reaches the page edges while the first card stays
     lined up with the container; the top padding leaves room for the hover
     lift, which an overflow box would otherwise clip */
  margin-inline: calc(-1 * var(--gutter));
  padding: 10px var(--gutter) var(--sp-5);
  -webkit-overflow-scrolling: touch;
  cursor: grab;
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
}

.games__grid.is-dragging {
  cursor: grabbing;
  scroll-snap-type: none;
  user-select: none;
}

.games__grid::-webkit-scrollbar {
  height: 6px;
}

.games__grid::-webkit-scrollbar-track {
  background: transparent;
}

.games__grid::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: var(--r-pill);
}

.games__grid:focus-visible {
  outline-offset: 6px;
}

.game-card {
  position: relative;
  display: block;
  flex: 0 0 min(86%, 540px);
  scroll-snap-align: start;
  overflow: hidden;
  border-radius: var(--r-lg);
  background-color: var(--bg-panel);
  color: var(--text);
  transition: background-color var(--t-mid) var(--ease);
}

.game-card:hover {
  background-color: #0f1a2e;
}

.game-card__media {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  -webkit-user-drag: none;
}

.game-card__body {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-5);
}

.game-card__title {
  font-size: var(--fs-md);
  margin-bottom: var(--sp-1);
}

.game-card__text {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.game-card__cta {
  flex-shrink: 0;
  align-self: center;
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--text-dim);
  white-space: nowrap;
  transition: color var(--t-fast) var(--ease);
}

.game-card:hover .game-card__cta {
  color: var(--c-cyan);
}

.game-card__cta::after {
  content: " \2192";
}

/* the upcoming title is muted on purpose */
.game-card--teaser .game-card__media {
  filter: grayscale(0.55) brightness(0.62);
}

.game-card--teaser:hover .game-card__media {
  filter: grayscale(0.2) brightness(0.8);
}

.game-card--teaser {
  cursor: default;
}

.game-card--teaser:hover {
  background-color: var(--bg-panel);
}

/* announced but not linked anywhere yet - it just sits there */
.game-card--static {
  cursor: default;
}

.game-card--static:hover {
  background-color: var(--bg-panel);
}

/* ==========================================================================
   about
   ========================================================================== */

.about {
  background-color: var(--bg-panel);
}

.about__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-6);
}

.about__body p + p {
  margin-top: var(--sp-4);
}

.about__body {
  max-width: 62ch;
  color: var(--text-muted);
}

.about__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-6);
  margin-top: var(--sp-7);
  padding-top: var(--sp-5);
}

.about__links li {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.about__links dt,
.about__links .label {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-dim);
}

.about__cta {
  margin-top: var(--sp-6);
}

.about__links a {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
}

.about__links a:hover {
  color: var(--c-cyan);
}

/* ==========================================================================
   reveal (opt-in, used on two blocks only - never on everything)
   ========================================================================== */

.js [data-reveal] {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity var(--t-slow) var(--ease),
    transform var(--t-slow) var(--ease);
}

.js [data-reveal].is-visible {
  opacity: 1;
  transform: none;
}
