/* Landing page 2.0 (draft). Deliberately its own stylesheet, not an edit to
 * the live site's styles.css: this page is still being built section by
 * section behind the admin gate, and touching the shared stylesheet would
 * change the public site immediately, before anything here is ready. Colors
 * below are copied from styles.css's :root (not referenced) for the same
 * reason - no coupling to a file this page must never modify.
 */

@font-face {
  font-family: 'Candy Beans';
  src: url('/assets/nlp-font-candy-beans.ttf') format('truetype');
  font-weight: 400;
  font-display: swap;
}

:root {
  --c-pink: #e5307d;
  --c-pink-dark: #c81f68;
  /* Halfway between --c-pink and --c-pink-2 - --c-pink-2 alone read as too
   * pale/washed out for the burger. */
  --c-pink-soft: #f0629f;
  --c-pink-2: #fa94c1;
  --c-pink-light: #f7b2d0;
  --c-ink: #1c1c1c;
  --c-body: #575757;
  --c-border: #e2e2e2;

  /* Design frame is 1440px wide with the content capped at 1312px, i.e. a
   * 64px gutter on each side (1440 - 1312 = 128, split two ways). The
   * padding scales down from that with viewport width via the vw term below
   * (64 / 1440 = 4.444vw) so the gutter shrinks smoothly on tablets and
   * phones instead of jumping between fixed breakpoint values, then floors
   * at 20px so it never collapses to nothing on a narrow phone. Screens
   * wider than 1440px don't get a bigger gutter - the max-width below simply
   * stops growing and margin:auto absorbs the rest as empty space outside.
   */
  --nlp-container-max: 1312px;
  --nlp-gutter: clamp(20px, 4.444vw, 64px);

  /* Distance from the viewport edge to where the content column's own edge
   * actually is - the same math .nlp-container's centering does, exposed as
   * a variable for full-bleed elements (like the slider's credit pill) that
   * need to line up with it despite not being inside a .nlp-container
   * themselves. 0 once the viewport is narrower than the content max-width
   * (nothing to center against any more), growing as the window widens
   * past it. */
  --nlp-edge: calc((100vw - min(100vw, var(--nlp-container-max))) / 2 + var(--nlp-gutter));
}

/* ── Reset ────────────────────────────────────────────────────────────────── */
/* This page links no other stylesheet (deliberately, see the file header),
 * so nothing else resets the browser's default 8px body margin. Without
 * this the header's border-bottom (and every full-bleed section after it)
 * sits inset from the viewport edge by that margin instead of running edge
 * to edge. */

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

html,
body {
  margin: 0;
  padding: 0;
}

body {
  font-family: 'Manrope', sans-serif;
  color: var(--c-ink);
  -webkit-font-smoothing: antialiased;
}

.nlp-container {
  max-width: var(--nlp-container-max);
  margin-inline: auto;
  padding-inline: var(--nlp-gutter);
}

@media (max-width: 880px) {
  /* Redefining the variable itself, not overriding .nlp-container directly,
   * so everything derived from it (the burger's position, --nlp-edge for
   * the slider credit) stays in sync instead of drifting out of alignment
   * with the container's own padding. */
  :root {
    --nlp-gutter: 10px;
  }
}

/* ── Header ───────────────────────────────────────────────────────────────── */

.nlp-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: #fff;
  border-bottom: 1px solid var(--c-border);
}

.nlp-header__inner {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: 16px;
}

.nlp-logo {
  display: block;
  flex-shrink: 0;
  line-height: 0;
}

.nlp-logo img {
  height: 40px;
  width: auto;
  display: block;
}

/* Everything but the logo and the burger. On desktop this simply fills the
 * rest of the row - .nlp-menu sits at its left edge (right after the logo)
 * and .nlp-header__right at its right edge, because the drawer itself is
 * space-between and flex:1. On mobile it becomes the flyout - see the media
 * query below, where display stays as-is and opacity/visibility/transform
 * do the showing and hiding, so open/close is an animation, not a snap. */
.nlp-drawer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex: 1;
  min-width: 0;
  margin-left: 40px;
}

.nlp-header__right {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
}

/* ── Menu (desktop: inline row) ──────────────────────────────────────────── */

.nlp-menu {
  display: flex;
  align-items: center;
  gap: 32px;
}

.nlp-menu__link {
  font-family: 'Manrope', sans-serif;
  font-size: 15px;
  font-weight: 600;
  color: var(--c-ink);
  text-decoration: none;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.nlp-menu__link:hover {
  color: var(--c-pink);
}

.nlp-menu__caret {
  width: 10px;
  height: 10px;
  /* Same easing and duration as the live site's .c-menu__caret. */
  transition: transform 0.24s cubic-bezier(0.65, 0, 0.35, 1);
}

.nlp-menu__item--dropdown[data-open="true"] .nlp-menu__caret {
  transform: rotate(180deg);
}

.nlp-menu__item--dropdown {
  position: relative;
}

.nlp-menu__dropdown {
  position: absolute;
  /* +28px, not +16px: "Tools" sits vertically centered inside a taller
   * padded header (the tallest thing in that row is the 44px language
   * switch, not this text), so 100% of the text's own small box lands
   * before the header's actual bottom edge - the popover was overlapping
   * the header's border-bottom by ~10px. This clears it with a visible gap
   * to spare, tuned against the header's current padding/content height -
   * revisit if either changes materially. */
  top: calc(100% + 28px);
  left: 0;
  min-width: 240px;
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: 16px;
  box-shadow: 0 8px 20px rgba(28, 28, 28, 0.07), 0 2px 5px rgba(28, 28, 28, 0.04);
  padding: 10px;
  /* Closed state: invisible and non-interactive rather than display:none, so
   * opacity/transform can transition instead of snapping. visibility follows
   * opacity with a delay on the way out only, so it still blocks tabbing/
   * hover while hidden but doesn't hold up the fade-in when opening. */
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(-6px) scale(0.98);
  transform-origin: top left;
  transition: opacity 0.18s ease, transform 0.22s cubic-bezier(0.65, 0, 0.35, 1),
    visibility 0s linear 0.18s;
}

/* Small rotated square forming a triangular pointer, visually bridging the
 * gap back up to the "Tools" trigger instead of the card just floating
 * disconnected below it. Border on two sides only, matching the card's own
 * border where the two shapes meet. */
.nlp-menu__dropdown::before {
  content: '';
  position: absolute;
  top: -7px;
  left: 22px;
  width: 14px;
  height: 14px;
  background: #fff;
  border-left: 1px solid var(--c-border);
  border-top: 1px solid var(--c-border);
  border-radius: 3px 0 0 0;
  transform: rotate(45deg);
}

.nlp-menu__dropdown-inner {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.nlp-menu__item--dropdown[data-open="true"] .nlp-menu__dropdown {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translateY(0) scale(1);
  transition: opacity 0.18s ease, transform 0.22s cubic-bezier(0.65, 0, 0.35, 1);
}

.nlp-menu__dropdown a {
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-decoration: none;
  padding: 10px 12px;
  border-radius: 10px;
  transition: background-color 0.15s ease, transform 0.15s ease;
}

.nlp-menu__dropdown a:hover {
  background: #fdf1f6;
  transform: translateX(2px);
}

.nlp-menu__dropdown-label {
  font-family: 'Manrope', sans-serif;
  font-size: 14px;
  font-weight: 700;
  color: var(--c-ink);
}

.nlp-menu__dropdown-note {
  font-family: 'Manrope', sans-serif;
  font-size: 12px;
  font-weight: 500;
  color: var(--c-body);
}

.nlp-menu__dropdown a:hover .nlp-menu__dropdown-label {
  color: var(--c-pink);
}

/* ── Right side: language toggle + CTA ───────────────────────────────────── */

/* Two stacked cards, ID and EN. Whichever is active sits on top (pink,
 * checkmark badge); clicking swaps which one that is, animating the
 * newly-active card up-front and the other one back-behind. */
.nlp-lang {
  position: relative;
  width: 46px;
  height: 44px;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  flex-shrink: 0;
}

.nlp-lang__card {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 30px;
  height: 36px;
  margin: -18px 0 0 -15px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: 'Manrope', sans-serif;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.02em;
  transition: transform 0.32s cubic-bezier(0.34, 1.56, 0.64, 1), background-color 0.2s ease,
    color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}

.nlp-lang__check {
  position: absolute;
  top: -5px;
  right: -5px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--c-pink);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transform: scale(0.4);
  transition: opacity 0.18s ease, transform 0.18s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.nlp-lang__check svg {
  width: 8px;
  height: 8px;
}

/* Active card: front, upright, pink, checkmark visible. */
.nlp-lang[data-lang="id"] .nlp-lang__card--id,
.nlp-lang[data-lang="en"] .nlp-lang__card--en {
  transform: translate(-5px, -3px) rotate(-6deg);
  background: var(--c-pink);
  color: #fff;
  z-index: 2;
  box-shadow: 0 8px 16px rgba(229, 48, 125, 0.35);
}

.nlp-lang[data-lang="id"] .nlp-lang__card--id .nlp-lang__check,
.nlp-lang[data-lang="en"] .nlp-lang__card--en .nlp-lang__check {
  opacity: 1;
  transform: scale(1);
}

/* Inactive card: behind, smaller, peeking out bottom-right. */
.nlp-lang[data-lang="id"] .nlp-lang__card--en,
.nlp-lang[data-lang="en"] .nlp-lang__card--id {
  transform: translate(5px, 3px) rotate(6deg) scale(0.86);
  background: #fff;
  color: var(--c-pink);
  border: 1.5px solid var(--c-pink-light);
  z-index: 1;
  opacity: 0.85;
}

.nlp-cta {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  overflow: hidden;
  background: var(--c-pink);
  color: #fff;
  font-family: 'Manrope', sans-serif;
  font-size: 14px;
  font-weight: 700;
  text-decoration: none;
  padding: 12px 20px;
  border-radius: 999px;
  white-space: nowrap;
  flex-shrink: 0;
}

/* Same wipe as the live site's .c-btn__link: a black panel grows from the
 * left over the pink background on hover, text/icon stay on top of it. */
.nlp-cta::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  z-index: 0;
  width: 0;
  height: 100%;
  background-color: #000000;
  transition: width 0.4s cubic-bezier(0.87, 0, 0.13, 1);
}

.nlp-cta:hover::before {
  width: 100%;
  transition: width 0.6s cubic-bezier(0.87, 0, 0.13, 1);
}

.nlp-cta__text,
.nlp-cta__chevron {
  position: relative;
  z-index: 1;
}

.nlp-cta svg {
  width: 14px;
  height: 14px;
}

/* ── Burger (hidden on desktop) ───────────────────────────────────────────── */

.nlp-burger {
  display: none;
  width: 36px;
  height: 36px;
  border: none;
  background: none;
  cursor: pointer;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  flex-shrink: 0;
  z-index: 1;
  /* The whole icon spins a quarter turn as it opens, on the same springy
   * overshoot easing the language cards use - a plain morph-to-X is the one
   * animation every hamburger button already does, this gives it a motion
   * signature instead of just a shape change. */
  transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.nlp-header[data-menu-open="true"] .nlp-burger {
  transform: rotate(90deg);
}

/* A thin line with rounded corners still reads as a rigid rectangle, but
 * 5px tall was too far the other way - three bold slabs next to the small
 * wordmark logo. 4px keeps the rounding visible without that much weight;
 * longer and tighter-stacked so it reads as one compact icon rather than
 * three separate pills with daylight between them. Lighter pink than the
 * brand color - the full-saturation pink read as a bit heavy this small. */
.nlp-burger span {
  display: block;
  width: 24px;
  height: 3px;
  background: var(--c-pink-soft);
  border-radius: 999px;
  transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.15s ease;
}

/* Full-viewport scrim behind the drawer. Its own element (not something
 * .nlp-drawer does with a pseudo-element) because it has to sit visually
 * between the plain header bar and the drawer panel, at a different
 * position (covers edge-to-edge, the drawer doesn't) and it needs its own
 * click handler to close the drawer. Hidden outside the mobile breakpoint -
 * the desktop drawer is inline content, not an overlay. */
.nlp-backdrop {
  display: none;
}

/* Morphs into an X while the drawer is open. 7px = one bar's height (3px)
 * plus the gap (4px) - the distance from the top or bottom bar's center to
 * the middle bar's center, so the two rotate into exactly the same line. */
.nlp-header[data-menu-open="true"] .nlp-burger span:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}

.nlp-header[data-menu-open="true"] .nlp-burger span:nth-child(2) {
  opacity: 0;
}

.nlp-header[data-menu-open="true"] .nlp-burger span:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

/* ── Tablet / phone ───────────────────────────────────────────────────────── */
/* Below this width only the logo and the burger show in the bar itself -
 * the drawer (nav links, Tools, language switch, and the Discord CTA) lives
 * in one flyout underneath it, opened by the burger. */

@media (max-width: 880px) {
  /* Logo becomes the only element left in the row's normal flex flow (the
   * burger and the drawer both go to position:fixed below), so centering
   * the row centers the logo regardless of the burger sitting off to its
   * fixed corner. */
  .nlp-header__inner {
    justify-content: center;
  }

  .nlp-burger {
    display: flex;
    position: fixed;
    top: 18px;
    right: var(--nlp-gutter);
    /* Above the backdrop and the drawer, both fixed-position siblings within
     * this same stacking context, so it stays visible and clickable as the
     * close control the whole time the drawer is open. */
    z-index: 3;
  }

  .nlp-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 1;
    background: rgba(28, 28, 28, 0.4);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.28s ease, visibility 0s linear 0.28s;
  }

  .nlp-header[data-menu-open="true"] .nlp-backdrop {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.28s ease;
  }

  /* Full-height panel pinned to the right edge, sliding in from off-screen
   * rather than dropping down - a full-height drawer reads as "the site's
   * next to this", a short dropdown reads as "a menu popped up". Width caps
   * at 380px so it doesn't stretch absurdly wide on a small tablet; on a
   * phone it's most of the screen. */
  .nlp-drawer {
    margin-left: 0;
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: auto;
    width: min(85vw, 380px);
    height: 100vh;
    height: 100dvh;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    /* One of the live site's hero fan-art crops, darkened underneath its own
     * tint rather than with a separate overlay element - one layer, one
     * thing to keep in sync with the "darker" ask instead of two. Which
     * image is a CSS variable, not a hardcoded url() - new-landing-page.js
     * sets it to a random pick from the pool on every load, and this rule
     * doesn't need to know how many there are. Falls back to #1 if JS
     * hasn't run yet (or is disabled), so there's never a blank drawer. */
    background-image: linear-gradient(180deg, rgba(18, 8, 15, 0.88), rgba(18, 8, 15, 0.82)),
      var(--nlp-drawer-bg, url('/assets/nlp-drawer-bg-1.webp'));
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    box-shadow: -20px 0 40px rgba(28, 28, 28, 0.16);
    padding: 96px 24px 24px;
    overflow-y: auto;
    z-index: 2;
    /* Closed: pushed fully off-screen to the right. transform is the only
     * thing that changes on toggle, so this is a slide, not a fade. */
    opacity: 1;
    visibility: hidden;
    pointer-events: none;
    transform: translateX(100%);
    transition: transform 0.32s cubic-bezier(0.65, 0, 0.35, 1), visibility 0s linear 0.32s;
  }

  .nlp-header[data-menu-open="true"] .nlp-drawer {
    visibility: visible;
    pointer-events: auto;
    transform: translateX(0);
    transition: transform 0.32s cubic-bezier(0.65, 0, 0.35, 1);
  }

  .nlp-menu {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }

  /* Light text over the dark art background - swaps out the desktop's dark
   * ink color, which would be unreadable here. No divider lines between
   * items: they read as clutter over an image, spacing alone separates them. */
  .nlp-menu__link {
    width: 100%;
    padding: 14px 0;
    justify-content: space-between;
    color: #fff;
  }

  .nlp-menu__link:hover {
    color: var(--c-pink-light);
  }

  .nlp-menu__item--dropdown {
    display: flex;
    flex-direction: column;
  }

  /* Same 1fr/0fr grid trick as the live site's .c-menu__subwrap: the row
   * animates to its real height instead of a hardcoded max-height, so it
   * still works cleanly if a fourth tool link is added later. Overrides the
   * desktop popover positioning/fade above - this is an inline accordion
   * row here, not a floating panel. */
  .nlp-menu__dropdown {
    position: static;
    /* Resets the desktop popover card look (white background, rounded
     * corners, fixed min-width) - this is an inline accordion row on a dark
     * background here, not a floating white card. Leaving any of these in
     * place is what showed up as a solid white box in testing. */
    background: none;
    border-radius: 0;
    min-width: 0;
    box-shadow: none;
    border: none;
    padding: 0 0 0 12px;
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: none;
    display: grid;
    grid-template-rows: 1fr;
    transition: grid-template-rows 0.26s cubic-bezier(0.65, 0, 0.35, 1), opacity 0.2s ease;
  }

  /* The popover pointer only makes sense floating below a trigger - this is
   * an inline row, there's nothing to point back up at. */
  .nlp-menu__dropdown::before {
    display: none;
  }

  .nlp-menu__item--dropdown[data-open="false"] .nlp-menu__dropdown {
    grid-template-rows: 0fr;
    opacity: 0;
  }

  /* Re-declares transform/transition against the desktop open-state rule
   * above (same selector, higher specificity than the base mobile rule) so
   * it doesn't win here and swap back to the fade/scale transition, which
   * would skip the grid-row animation on open. */
  .nlp-menu__item--dropdown[data-open="true"] .nlp-menu__dropdown {
    transform: none;
    transition: grid-template-rows 0.26s cubic-bezier(0.65, 0, 0.35, 1), opacity 0.2s ease;
  }

  /* No padding-bottom here: trailing space on the box the grid row is
   * actually collapsing is exactly what left a visible sliver while
   * "closed" - the row's own gap plus the next link's padding already give
   * enough breathing room without it. */
  .nlp-menu__dropdown-inner {
    min-height: 0;
    overflow: hidden;
  }

  /* Desktop's popover version of these is a white card with a dark label,
   * a gray note, and a light pink hover chip - all three assume a light
   * background this one doesn't have. The label/note spans carry their own
   * explicit colors (for the desktop card), so overriding the <a> itself
   * wouldn't reach them - each needs its own override here. */
  .nlp-menu__dropdown-label {
    color: #fff;
  }

  .nlp-menu__dropdown-note {
    color: rgba(255, 255, 255, 0.6);
  }

  .nlp-menu__dropdown a:hover {
    background: rgba(255, 255, 255, 0.12);
  }

  .nlp-menu__dropdown a:hover .nlp-menu__dropdown-label {
    color: #fff;
  }

  /* Language switch + CTA move to the bottom of the flyout as one row,
   * visually separated from the nav links above by breathing room - a
   * divider line here would compete with the art rather than organize it. */
  .nlp-header__right {
    margin-top: 20px;
    padding-top: 20px;
  }

  .nlp-cta {
    flex: 1;
    justify-content: center;
  }
}

@media (max-width: 420px) {
  .nlp-logo img {
    height: 32px;
  }

  /* Header is 8px shorter here (smaller logo), so the fixed burger's offset
   * re-centers with it. */
  .nlp-burger {
    top: 14px;
  }
}

/* ── Hero / banner slider ─────────────────────────────────────────────────── */
/* Carried over from the live site's .l-hero/.c-slider: same 15 fan-art
 * pieces, same crossfade-and-shuffle behavior (new-landing-page.js), same
 * credit pills. Only the scroll-down indicator differs - see .nlp-scroll-cue
 * below. Full-bleed on purpose: no .nlp-container here, the live banner
 * runs edge to edge too. */

.nlp-hero {
  background-color: var(--c-pink-light);
}

.nlp-slider {
  position: relative;
  width: 100%;
  /* Matching the art's own ratio shows it whole at every width - no crop,
   * no dependence on viewport height. Capped below a certain width the
   * height would otherwise keep growing past on a very large/high-res
   * monitor (at 2.36:1, a 3840px-wide screen would ask for a ~1630px-tall
   * banner) - object-fit:contain on the slides handles the resulting ratio
   * mismatch by pillarboxing rather than cropping or stretching. */
  aspect-ratio: 1897 / 803;
  height: auto;
  max-height: 900px;
  overflow: hidden;
  background: var(--c-pink-light);
  /* The slider handles horizontal drags itself; vertical scroll still needs
   * to reach the browser so the page can be scrolled from on top of it. */
  touch-action: pan-y;
  user-select: none;
}

.nlp-slider__slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1.6s cubic-bezier(0.165, 0.84, 0.44, 1);
  /* Every slide is stacked at inset:0, and opacity:0 doesn't stop hit
   * testing - without this, all fifteen credit links pile up on the same
   * spot and whichever is last in the DOM eats every click. */
  pointer-events: none;
}

.nlp-slider__slide.is-active {
  opacity: 1;
  pointer-events: auto;
}

.nlp-slider__slide picture {
  display: block;
  position: absolute;
  inset: 0;
}

.nlp-slider__slide img {
  display: block;
  /* Without this the browser starts its own image-drag on mousedown, the
   * pointerup never reaches the slider, and the swipe silently does nothing. */
  -webkit-user-drag: none;
  user-select: none;
  /* Events pass through to the slider, which listens for the swipe. The
   * credit link sits above this and keeps its own pointer events. */
  pointer-events: none;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Below the slider's max-height cap, the box matches the art's own ratio
   * exactly and this never actually crops anything. Past the cap on a very
   * wide monitor the box becomes proportionally wider than the art (not
   * taller), so cover crops the sides rather than the top/bottom - centered
   * is the safe default for that direction. */
  object-position: center;
}

/* Only one credit is correct at a time - desktop and mobile show different
 * pieces by different artists, so the link has to switch with the image. */
.nlp-slider__credit--mobile {
  display: none;
}

.nlp-slider__credit {
  position: absolute;
  z-index: 3;
  /* The slider is full-bleed, but the credit shouldn't drift out toward the
   * raw viewport edge on a wide screen - it lines up with the same content
   * edge the header's own nav/CTA sit against instead. */
  right: var(--nlp-edge);
  bottom: 13px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.38);
  padding: 5px 12px;
  font-family: 'Manrope', sans-serif;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: #fff;
  text-decoration: none;
  transition: background 0.2s ease;
}

.nlp-slider__credit:hover,
.nlp-slider__credit:focus-visible {
  background: rgba(0, 0, 0, 0.62);
}

/* New scroll-down indicator (nlp-scroll-cue.svg) replaces the live site's
 * chevron-in-a-ring icon. The artwork already bakes in its own soft glow
 * (the translucent outer circle), so this only needs to bounce it - no
 * separate pulsing ::before ring layered on top like the original had. */
.nlp-scroll-cue {
  position: absolute;
  left: 50%;
  bottom: 32px;
  z-index: 4;
  width: 56px;
  height: 56px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  transform: translateX(-50%);
  animation: nlp-scroll-cue-bounce 2.2s ease-in-out infinite;
  transition: transform 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
}

.nlp-scroll-cue:hover {
  animation-play-state: paused;
  transform: translateX(-50%) scale(1.1);
}

.nlp-scroll-cue img {
  display: block;
  width: 100%;
  height: 100%;
}

@keyframes nlp-scroll-cue-bounce {
  0%, 100% { transform: translateX(-50%) translateY(0); }
  50% { transform: translateX(-50%) translateY(10px); }
}

/* Mobile art is 1:1, so a square box shows it whole with no crop at all. */
@media (max-width: 768px) {
  .nlp-slider {
    height: auto;
    aspect-ratio: 1 / 1;
  }

  .nlp-slider__credit--desktop {
    display: none;
  }

  .nlp-slider__credit--mobile {
    display: block;
  }
}

@media (prefers-reduced-motion: reduce) {
  .nlp-scroll-cue {
    animation: none;
  }
}

/* ── Wordmark marquee ─────────────────────────────────────────────────────── */
/* Only .nlp-marquee__group's first copy exists in the HTML - see the
 * comment there. new-landing-page.js clones it until the track covers at
 * least 2x the section's width, so -50% always loops seamlessly and never
 * runs out partway across a wide screen. */

.nlp-marquee {
  width: 100%;
  overflow: hidden;
  background: #fff;
  padding-block: 28px;
}

.nlp-marquee__track {
  display: flex;
  width: max-content;
  animation: nlp-marquee-scroll 22s linear infinite;
}

.nlp-marquee__group {
  display: flex;
  align-items: center;
  gap: 48px;
  padding-right: 48px;
  flex: none;
}

.nlp-marquee__group img {
  height: 52px;
  width: auto;
  flex: none;
  display: block;
}

@keyframes nlp-marquee-scroll {
  0% { transform: translate3d(0, 0, 0); }
  100% { transform: translate3d(-50%, 0, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .nlp-marquee__track {
    animation: none;
  }
}

/* ── Scroll reveal ────────────────────────────────────────────────────────── */
/* Same fade-up-once mechanism as the live site's .js-reveal/.is-active,
 * driven by an IntersectionObserver in new-landing-page.js. */

.nlp-reveal {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 1.1s cubic-bezier(0.165, 0.84, 0.44, 1), transform 1.1s cubic-bezier(0.165, 0.84, 0.44, 1);
}

.nlp-reveal.is-active {
  opacity: 1;
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  .nlp-reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* Circle-expand variant: the panel's background (gradient + dots) grows out
 * from a point at its center rather than fading up like .nlp-reveal. Its
 * .nlp-reveal children stay invisible through this whole phase - their own
 * transitions haven't started yet, only their delay is ticking (see the
 * staircase delays below) - so nothing shows inside the growing circle
 * until it has finished, and only then do they begin sliding up. Same
 * observer/is-active toggle as .nlp-reveal, just a different property.
 *
 * circle()'s percentage is NOT relative to width/height directly - per the
 * CSS Shapes spec it resolves against sqrt(w^2+h^2)/sqrt(2), which for this
 * panel's aspect ratio is well past what's needed to actually cover the
 * farthest corner. Measured empirically against the live page (checking
 * whether a point near the corner is still hit-testable through the clip):
 * corners are already fully covered by ~65-70%. An earlier version of this
 * used 150-160% as the "finished" value on the assumption that 100% could
 * undershoot a wide box - it's the opposite problem, and it meant the
 * entire final easing beat was invisible (already fully covering the panel
 * long before that beat even started), which read as the circle
 * "teleporting" to full partway through beat 3 and then doing nothing.
 * 76% is the real finished value here, with a small margin over the
 * measured threshold. */
.nlp-reveal-circle {
  clip-path: circle(0% at 50% 50%);
}

.nlp-reveal-circle.is-active {
  animation: nlp-panel-expand 2.2s forwards;
}

@keyframes nlp-panel-expand {
  /* Nothing, fast: pops from nothing to a visible 30px-diameter dot (a
   * fixed length, not a percentage of the panel) quickly. */
  0% {
    clip-path: circle(0px at 50% 50%);
    animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
  }
  /* One pause, not two: an earlier version held here twice (a slow creep to
   * a second, barely-bigger radius before the real expansion below), which
   * read as the circle hesitating twice while still tiny. Reusing the same
   * ease-out curve for both this pop-in and the expansion below, rather
   * than giving the middle step its own distinct slow-creep curve, leaves
   * exactly one seam between them - one curve ending near-zero velocity,
   * the next starting fresh - which is the one deliberate pause this beat
   * is for. Removing the middle step entirely, not just retiming it, is
   * what actually gets to one pause instead of two. */
  15% {
    clip-path: circle(15px at 50% 50%);
    animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
  }
  /* Big, then a natural slowdown to rest: one continuous ease-out curve
   * from here all the way to 100%, not two curves stitched together. Two
   * separate segments (one ease-out ending near-zero velocity, handing off
   * to a second curve that starts with its own non-zero velocity) is
   * exactly what produced the awkward hitch around 90-95% in an earlier
   * version - the motion nearly stopped, then visibly re-accelerated, then
   * decelerated again. A single cubic-bezier across the whole arc has no
   * seam to hitch at: it just gets slower and slower until it reaches 0
   * velocity right at 100%. Mixing a length (px) start with a percentage
   * end is fine - the browser interpolates length-percentage values as a
   * calc() of both, confirmed by inspecting the live computed value
   * mid-transition (it briefly reports something like
   * "circle(calc(41% + 3px) at 50% 50%)"). */
  100% {
    clip-path: circle(76% at 50% 50%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .nlp-reveal-circle.is-active {
    animation: none;
    clip-path: circle(76% at 50% 50%);
  }
}

/* ── About section ────────────────────────────────────────────────────────── */

.nlp-about {
  padding-block: clamp(48px, 6vw, 96px);
}

/* The gradient/dots/content all live inside this rounded card - the section
 * itself has no background of its own, so the page's white shows around it
 * on every side rather than the gradient running edge to edge. */
.nlp-about__panel {
  position: relative;
  overflow: hidden;
  border-radius: 32px;
  background: linear-gradient(135deg, #cfe4f7 0%, #dad2ec 45%, #f6c8de 100%);
  padding: clamp(28px, 4vw, 56px);
}

/* Uniform dot pattern, revealed only near the two corners by the mask -
 * approximates a halftone fade without needing per-dot size variation. */
.nlp-about__dots {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.9) 1.5px, transparent 1.6px);
  background-size: 16px 16px;
  -webkit-mask-image: radial-gradient(circle at 0% 0%, #000 0%, transparent 42%),
    radial-gradient(circle at 100% 100%, #000 0%, transparent 42%);
  mask-image: radial-gradient(circle at 0% 0%, #000 0%, transparent 42%),
    radial-gradient(circle at 100% 100%, #000 0%, transparent 42%);
}

.nlp-about__inner {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  align-items: center;
  gap: 56px;
}

/* Staircase: every element in here is already .nlp-reveal, triggered by the
 * same viewport-entry moment as the panel's circle (they're all in the same
 * small section, so in practice one IntersectionObserver firing covers all
 * of them within a frame or two of each other). What makes this a staircase
 * rather than everything sliding up at once is transition-delay alone.
 *
 * The 1.0s base is much earlier than nlp-panel-expand's own 2.2s duration,
 * and deliberately so: circle()'s percentage keeps growing well past the
 * point where it's already covering every corner of the panel (see the
 * corner-coverage note on nlp-panel-expand above), so all that remaining
 * growth happens entirely outside the visible box - invisible. Carried over
 * proportionally from the previous 1.8s-of-3.4s base (itself measured
 * directly against the live, playing animation - polling every frame for
 * the first moment all four corners are actually hit-testable through the
 * clip) onto nlp-panel-expand's new duration and single-pause keyframe
 * shape: worth a quick eyeball against the live page after any further
 * retiming, rather than assuming the proportion holds exactly. Each
 * following element is offset another 0.08s past the last, unchanged -
 * close enough together to read as one continuous staircase, not six
 * isolated beats. */
.nlp-about__label.nlp-reveal { transition-delay: 1.0s; }
.nlp-about__logo.nlp-reveal { transition-delay: 1.08s; }
.nlp-about__lead.nlp-reveal { transition-delay: 1.12s; }
.nlp-about__lead--2.nlp-reveal { transition-delay: 1.14s; }
.nlp-about__checklist.nlp-reveal { transition-delay: 1.16s; }
.nlp-about__question.nlp-reveal { transition-delay: 1.24s; }
.nlp-about .nlp-cta.nlp-reveal { transition-delay: 1.32s; }
.nlp-about__card.nlp-reveal { transition-delay: 1.4s; }

@media (prefers-reduced-motion: reduce) {
  .nlp-about__label.nlp-reveal,
  .nlp-about__logo.nlp-reveal,
  .nlp-about__lead.nlp-reveal,
  .nlp-about__checklist.nlp-reveal,
  .nlp-about__question.nlp-reveal,
  .nlp-about .nlp-cta.nlp-reveal,
  .nlp-about__card.nlp-reveal {
    transition-delay: 0s;
  }
}

.nlp-about__content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 20px;
}

.nlp-about__label {
  margin: 0;
  color: var(--c-pink);
  font-family: 'Manrope', sans-serif;
  font-size: 14px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.nlp-about__logo {
  width: 262px;
  height: auto;
  display: block;
}

.nlp-about__checklist {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  max-width: 420px;
}

.nlp-about__check {
  background: rgba(255, 255, 255, 0.75);
  border-radius: 0;
  padding: 4px 16px;
  font-family: 'Manrope', sans-serif;
  font-size: 16px;
  font-weight: 600;
  color: #666666;
}

.nlp-about__question {
  margin: 0;
  font-family: 'Manrope', sans-serif;
  font-size: 16px;
  font-weight: 600;
  color: #2b2b2b;
}

.nlp-cta--large {
  padding: 16px 28px;
  font-size: 16px;
}

.nlp-cta--large svg {
  width: 16px;
  height: 16px;
}

/* ── About: preview card ──────────────────────────────────────────────────── */

/* The card is a single flattened image (nlp-about-card.png) rather than a
 * DOM reconstruction - the illustration inside it (custom character art,
 * grid background) isn't something CSS can recreate from scratch, and once
 * the whole thing is one image there's no reason to keep the rest of the
 * layout live either. */
.nlp-about__card {
  display: block;
  width: 100%;
  max-width: 535px;
  height: auto;
  border-radius: 24px;
  box-shadow: 0 40px 90px rgba(229, 48, 125, 0.28), 0 12px 32px rgba(28, 28, 28, 0.10);
}

@media (max-width: 880px) {
  .nlp-about__panel {
    padding: 30px 15px;
    border-radius: 24px;
  }

  /* Image before the text column on mobile - order alone reorders the grid
   * item visually, the DOM (and reveal-on-scroll timing) stays as written.
   * margin-inline:auto centers it: its own max-width (535px) is narrower
   * than the panel on plenty of "mobile" widths (a phone in landscape, a
   * small tablet), and a block element doesn't center itself just because
   * its container does - it sits flush left with the rest of the width
   * empty beside it unless told to. */
  .nlp-about__card {
    order: -1;
    border-radius: 12px;
    margin-inline: auto;
  }

  .nlp-about__inner {
    grid-template-columns: 1fr;
    gap: 40px;
  }

  .nlp-about__content {
    align-items: center;
    text-align: center;
  }

  .nlp-about__check,
  .nlp-about__question {
    font-size: 14px;
  }
}

@media (max-width: 420px) {
  .nlp-about__logo {
    width: 190px;
  }
}

/* ── Features ─────────────────────────────────────────────────────────────── */

.nlp-features {
  padding-block: clamp(48px, 6vw, 96px);
}

.nlp-features__title {
  margin: 0 0 48px;
  text-align: center;
  font-family: 'Manrope', sans-serif;
  font-size: clamp(26px, 3.4vw, 36px);
  font-weight: 800;
  color: var(--c-ink);
}

.nlp-features__grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 24px;
}

.nlp-feature {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;
  padding: 28px 24px;
  border: 1px solid var(--c-border);
  border-radius: 20px;
}

/* Pops in with a little spin as its card reveals, rather than looping
 * forever - .nlp-feature is already .nlp-reveal (see the staggered
 * per-card transition-delay above), so this rides the same is-active
 * toggle instead of needing its own trigger. Same springy overshoot easing
 * used for the language-card swap and the burger spin elsewhere on this
 * page, so it lands with a little bounce rather than a flat stop. */
.nlp-feature__icon {
  width: 64px;
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 16px;
  background: #F7B2D0;
  opacity: 0;
  transform: scale(0.4) rotate(-30deg);
  transition: opacity 0.4s ease, transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
  transition-delay: 0.15s;
}

.nlp-feature.is-active .nlp-feature__icon {
  opacity: 1;
  transform: scale(1) rotate(0deg);
}

@media (prefers-reduced-motion: reduce) {
  .nlp-feature__icon {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

.nlp-feature__icon img {
  width: 36px;
  height: 36px;
  display: block;
}

.nlp-feature__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Every channel pill (both hashtags and the "N+" count badge) shares the
 * same gradient - no more distinguishing colors per tag. */
/* Wipes in left to right rather than sliding - the pill itself never
 * moves, a clip-path rectangle grows from 0 width (masking everything)
 * to full width (masking nothing), so it reads as the pill's own content
 * being revealed rather than something arriving from off to the side.
 * Rides the same .nlp-feature.is-active trigger as the icon's pop-in
 * above, just later, and each tag in a card is staggered a bit further
 * than the last so they wipe in one after another, not all at once. */
.nlp-tag {
  display: inline-flex;
  align-items: center;
  padding: 5px 12px;
  border-radius: 999px;
  background: linear-gradient(90deg, #B2E0F7 0%, #F7B2D0 100%);
  color: var(--c-ink);
  font-family: 'Manrope', sans-serif;
  font-size: 12px;
  font-weight: 700;
  clip-path: inset(0 100% 0 0);
  transition: clip-path 0.5s cubic-bezier(0.65, 0, 0.35, 1);
}

.nlp-feature.is-active .nlp-tag {
  clip-path: inset(0 0 0 0);
}

.nlp-feature__tags .nlp-tag:nth-child(1) { transition-delay: 0.3s; }
.nlp-feature__tags .nlp-tag:nth-child(2) { transition-delay: 0.42s; }
.nlp-feature__tags .nlp-tag:nth-child(3) { transition-delay: 0.54s; }

@media (prefers-reduced-motion: reduce) {
  .nlp-tag {
    clip-path: none;
    transition: none;
  }
}

.nlp-feature__title {
  margin: 0;
  font-family: 'Manrope', sans-serif;
  font-size: 20px;
  font-weight: 800;
  color: var(--c-ink);
}

.nlp-feature__text {
  margin: 0;
  font-family: 'Manrope', sans-serif;
  font-size: 14px;
  line-height: 1.6;
  color: var(--c-body);
}

/* Staggered fade-up per card, same .nlp-reveal every other section uses -
 * just offset a little further apart per card so they read left-to-right
 * rather than popping in together. */
.nlp-feature:nth-child(1) { transition-delay: 0s; }
.nlp-feature:nth-child(2) { transition-delay: 0.1s; }
.nlp-feature:nth-child(3) { transition-delay: 0.2s; }
.nlp-feature:nth-child(4) { transition-delay: 0.3s; }

@media (max-width: 1024px) {
  .nlp-features__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 560px) {
  .nlp-features__grid {
    grid-template-columns: 1fr;
  }
}

/* ── Aktivitas Lainnya (scrollable rows) ──────────────────────────────────── */
/* Same layout as the live site's .l-nobar rows (copy + picture, alternating
 * sides), restyled for this page. Each row fades up with .nlp-reveal. */
.nlp-activities {
  background: #fff7fb;
  padding-block: clamp(48px, 6vw, 96px);
  overflow: hidden;
}

.nlp-activities__label {
  margin: 0 0 12px;
  color: var(--c-pink);
  font-family: 'Manrope', sans-serif;
  font-size: 14px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.nlp-act-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  align-items: center;
  gap: clamp(24px, 5vw, 72px);
}

.nlp-act-row + .nlp-act-row { margin-top: clamp(56px, 7vw, 104px); }

.nlp-act-row--reverse .nlp-act-row__copy { order: 2; }

.nlp-act-row__title {
  margin: 0 0 16px;
  font-family: 'Manrope', sans-serif;
  font-size: clamp(28px, 3.6vw, 40px);
  font-weight: 800;
  line-height: 1.2;
  color: var(--c-pink);
}

.nlp-act-row__text {
  margin: 0;
  max-width: 46ch;
  font-family: 'Manrope', sans-serif;
  font-size: 15px;
  line-height: 1.7;
  color: var(--c-body);
}

/* Single screenshot (not a stack, so none of the absolute stack geometry) */
.nlp-act-row__shot--single .nlp-stack__card {
  position: static;
  width: 100%;
  transform: none;
  cursor: zoom-in;
  display: block;
  border-radius: 24px;
  overflow: hidden;
  box-shadow: 0 18px 40px rgba(74, 59, 87, 0.16);
}
.nlp-act-row__shot--single img { display: block; width: 100%; height: auto; }

/* Card stack (live site's .c-stage): every card sits in the same spot, the
 * slot (set by new-landing-page.js) decides how far it fans out behind the
 * front one. Purely horizontal + scale, so the screenshots stay straight on. */
.nlp-stack {
  position: relative;
  aspect-ratio: 4 / 3;
}

.nlp-stack__card {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 62%;
  display: block;
  border-radius: 18px;
  overflow: hidden;
  background: #fff;
  box-shadow: 0 18px 40px rgba(74, 59, 87, 0.18);
  transform: translateX(-50%);
  transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1), filter 0.4s ease;
  cursor: pointer;
}

.nlp-stack__card img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: top; }

/* Offsets are worked out so the fanned group always spans the column edge
 * to edge, whatever the card count (back cards are scaled 0.86):
 *   3 cards, 62% wide: back cards +-37.6% of a card width
 *   2 cards, 70% wide: front shifted left, back shifted right, so the front
 *     card's left edge and the back card's right edge sit on the column edges
 *     (otherwise the pair leans right and leaves a gap on the left). */
.nlp-stack__card[data-slot="0"] { z-index: 3; transform: translateX(-50%); cursor: zoom-in; }
.nlp-stack__card[data-slot="1"] { z-index: 2; transform: translateX(calc(-50% + 37.6%)) scale(0.86); filter: brightness(0.92); }
.nlp-stack__card[data-slot="2"] { z-index: 1; transform: translateX(calc(-50% - 37.6%)) scale(0.86); filter: brightness(0.92); }

.nlp-stack[data-count="2"] .nlp-stack__card { width: 70%; }
.nlp-stack[data-count="2"] .nlp-stack__card[data-slot="0"] { transform: translateX(calc(-50% - 21.4%)); }
.nlp-stack[data-count="2"] .nlp-stack__card[data-slot="1"] { transform: translateX(calc(-50% + 28.4%)) scale(0.86); }

.nlp-stack__card:not([data-slot="0"]):hover { filter: none; }

@media (max-width: 880px) {
  .nlp-act-row { grid-template-columns: 1fr; gap: 24px; }
  .nlp-act-row--reverse .nlp-act-row__copy { order: 0; }
  .nlp-stack__card { width: 66%; }
  .nlp-stack__card[data-slot="1"] { transform: translateX(calc(-50% + 32.8%)) scale(0.86); }
  .nlp-stack__card[data-slot="2"] { transform: translateX(calc(-50% - 32.8%)) scale(0.86); }
}

@media (prefers-reduced-motion: reduce) {
  .nlp-stack__card { transition: none; }
}

/* ── Fan Corner ───────────────────────────────────────────────────────────
 * Ported from the live site's .l-fanart-wall / .c-scroll-wall: four columns,
 * each an independently-timed vertical marquee (odd columns scroll up, even
 * ones scroll down), looping via translateY(-50%) on a track whose content
 * new-landing-page.js has cloned to exactly double length - see that file's
 * fillLoopingTrack() for why a single clone isn't enough at every viewport
 * height. The only real difference from the live section is the background:
 * the heart-bloom SVG here instead of a flat wash colour. */

.nlp-fanart {
  position: relative;
  background-color: #fff;
  background-image: url('/assets/nlp-fanart-bg.svg');
  background-repeat: no-repeat;
  background-position: center center;
  background-size: contain;
  padding-block: clamp(48px, 6vw, 96px);
  overflow: hidden;
}

.nlp-fanart__label {
  margin: 0 0 12px;
  text-align: center;
  color: var(--c-pink);
  font-family: 'Manrope', sans-serif;
  font-size: 14px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.nlp-fanart__title {
  margin: 0 0 48px;
  text-align: center;
  font-family: 'Manrope', sans-serif;
  font-size: clamp(26px, 3.4vw, 36px);
  font-weight: 800;
  color: var(--c-ink);
}

.nlp-fanart__wall {
  position: relative;
  display: flex;
  gap: 15px;
  height: 640px;
  overflow: hidden;
  border-radius: 20px;
}

.nlp-fanart__col {
  flex: 1;
  min-width: 0;
  overflow: hidden;
}

.nlp-fanart__track {
  display: flex;
  flex-direction: column;
  width: 100%;
  animation-name: nlp_fanart_loop;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

.nlp-fanart__group {
  display: flex;
  flex-direction: column;
  width: 100%;
  gap: 15px;
  padding-bottom: 15px;
}

.nlp-fanart__col:nth-child(odd) .nlp-fanart__track { animation-direction: normal; }
.nlp-fanart__col:nth-child(even) .nlp-fanart__track { animation-direction: reverse; }

.nlp-fanart__col:nth-child(1) .nlp-fanart__track { animation-duration: 38s; }
.nlp-fanart__col:nth-child(2) .nlp-fanart__track { animation-duration: 44s; }
.nlp-fanart__col:nth-child(3) .nlp-fanart__track { animation-duration: 40s; }
.nlp-fanart__col:nth-child(4) .nlp-fanart__track { animation-duration: 46s; }

@keyframes nlp_fanart_loop {
  0%   { transform: translateY(0); }
  100% { transform: translateY(-50%); }
}

.nlp-fanart__item {
  position: relative;
  margin: 0;
  border-radius: 14px;
  overflow: hidden;
  background: #ffffff;
  flex: none;
}

.nlp-fanart__item img {
  display: block;
  width: 100%;
  height: auto;
}

.nlp-fanart__item figcaption {
  position: absolute;
  right: 0.6rem;
  bottom: 0.55rem;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.4);
  transition: background 0.2s ease;
}

.nlp-fanart__credit {
  display: block;
  padding: 0.25rem 0.65rem;
  color: #ffffff;
  font-family: 'Manrope', sans-serif;
  font-size: 0.68rem;
  font-weight: 700;
  text-decoration: none;
}

.nlp-fanart__item figcaption:hover,
.nlp-fanart__item figcaption:focus-within {
  background: rgba(0, 0, 0, 0.65);
}

@media (max-width: 880px) {
  .nlp-fanart__wall {
    height: 560px;
  }

  .nlp-fanart__col:nth-child(4) {
    display: none;
  }
}

@media (max-width: 560px) {
  .nlp-fanart__wall {
    height: 460px;
  }

  .nlp-fanart__col:nth-child(3) {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .nlp-fanart__track {
    animation: none;
  }
}

/* PhotoSwipe's own caption UI element, registered by new-landing-page.js -
 * ported as-is from the live site's .pswp__fanart-caption. */
.pswp__fanart-caption {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.55);
  font-family: 'Manrope', sans-serif;
  font-size: 0.85rem;
  font-weight: 700;
  transition: background 0.2s ease;
}

.pswp__fanart-caption a {
  display: block;
  padding: 0.4rem 1rem;
  color: #ffffff;
  text-decoration: none;
}

.pswp__fanart-caption:hover,
.pswp__fanart-caption:focus-within {
  background: rgba(0, 0, 0, 0.78);
}

/* ── Gacha TCG CTA ────────────────────────────────────────────────────────
 * .nlp-tcg__panel's background is one image (see the HTML's own comment on
 * this section for why), stretched with background-size: 100% 100% rather
 * than cover - it was authored at exactly --nlp-container-max's width
 * (1312x840) so a stretch fit never crops the halftone dots in its corners
 * the way cover would at an off-ratio viewport. overflow stays visible so
 * the fanned cards can bleed past the panel's bottom edge onto the section's
 * plain white background, same as the reference design. */

.nlp-tcg {
  background: #ffffff;
  padding-block: clamp(48px, 6vw, 96px);
}

/* The background image lives on ::before, not as a background-image on the
 * panel itself, specifically so the iris reveal below (same technique as
 * .nlp-about__panel's .nlp-reveal-circle, "Tentang Kami" - see that rule's
 * own comment for where nlp-panel-expand's timing/easing came from) clips
 * only the artwork, not the copy text or cards sitting on top of it. Those
 * already have their own independent reveals (nlp-reveal fade-ups, the
 * card fan's own shuffle) - clipping them to the same growing circle would
 * hide content that's meant to be readable immediately, not popped in with
 * the background. */
.nlp-tcg__panel {
  position: relative;
  /* Hard clip, not just smaller offsets below - a guarantee the cards never
     spill past the artwork's own edge at any viewport width, not just the
     ones this was tuned against. */
  overflow: hidden;
  border-radius: 32px;
  padding: clamp(48px, 7vw, 88px) 24px 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.nlp-tcg__panel::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: url('/assets/nlp-tcg-bg.png');
  background-repeat: no-repeat;
  background-size: 100% 100%;
  clip-path: circle(0% at 50% 50%);
  z-index: 0;
}

.nlp-tcg__panel.is-active::before {
  animation: nlp-panel-expand 2.2s forwards;
}

@media (prefers-reduced-motion: reduce) {
  .nlp-tcg__panel.is-active::before {
    animation: none;
    clip-path: circle(76% at 50% 50%);
  }
}

.nlp-tcg__copy,
.nlp-tcg__fan {
  position: relative;
  z-index: 1;
}

.nlp-tcg__copy {
  max-width: 640px;
}

.nlp-tcg__label {
  margin: 0 0 12px;
  color: var(--c-pink);
  font-family: 'Manrope', sans-serif;
  font-size: 14px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.nlp-tcg__title {
  margin: 0 0 20px;
  font-family: 'Manrope', sans-serif;
  font-size: clamp(28px, 4vw, 40px);
  font-weight: 800;
  line-height: 1.25;
  color: var(--c-ink);
}

.nlp-tcg__text {
  margin: 0 0 32px;
  font-family: 'Manrope', sans-serif;
  font-size: 15px;
  line-height: 1.7;
  color: var(--c-body);
}

.nlp-tcg__highlight {
  color: var(--c-pink);
  font-weight: 800;
}

/* The fan itself, rebuilt around the standard technique for this exact
 * effect (a stacked-card fan-out - see LESSONS.md for the research this
 * replaced a hand-rolled `left`-percentage version with): every card is
 * anchored at the SAME fixed point (top: 0; left: 50%, never animated) and
 * ALL of the fan-out motion - spread, rotation, lift - lives in one single
 * `transform` declaration per card. Only one property is ever transitioned,
 * so there's no cross-property coordination to get wrong, and
 * `transform-origin: bottom center` makes each card pivot from its own
 * bottom edge like a real card being fanned out from a hand, instead of
 * sliding sideways from its center.
 *
 * The horizontal reach uses `vw` inside calc() (not a fixed px value)
 * specifically so the two outer cards keep crossing the panel's edge -
 * getting cut by its overflow: hidden, the "sticking out" look this was
 * built for - across viewport widths, not just the one it happened to be
 * tuned at. */
.nlp-tcg__fan {
  position: relative;
  height: clamp(240px, 26vw, 370px);
  margin-top: clamp(44px, 5.6vw, 68px);
  /* Plain px, not vw - fixed spacing between card centers regardless of
     viewport width, tuned against .nlp-tcg__card's own width (clamp(150px,
     17vw, 230px)) below so adjacent cards overlap instead of gapping. */
  --tcg-spread-inner: 190px;
  --tcg-spread-outer: 390px;
}

.nlp-tcg__card {
  position: absolute;
  top: 0;
  left: 50%;
  display: block;
  width: clamp(180px, 20vw, 270px);
  height: auto;
  transform-origin: bottom center;
  border-radius: 7px;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.18);
  transition: transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
}

.nlp-tcg__card:nth-child(1) { transform: translateX(-50%) rotate(-3deg); z-index: 1; }
.nlp-tcg__card:nth-child(2) { transform: translateX(-50%) rotate(2deg); z-index: 2; }
.nlp-tcg__card:nth-child(3) { transform: translateX(-50%) rotate(-1deg); z-index: 3; }
.nlp-tcg__card:nth-child(4) { transform: translateX(-50%) rotate(2deg); z-index: 2; }
.nlp-tcg__card:nth-child(5) { transform: translateX(-50%) rotate(-3deg); z-index: 1; }

.nlp-tcg__fan.is-active .nlp-tcg__card:nth-child(1) {
  transform: translateX(calc(-50% - var(--tcg-spread-outer))) rotate(-10deg) translateY(50px) translateY(var(--tcg-lift, 0px));
  transition-delay: 0s;
}

.nlp-tcg__fan.is-active .nlp-tcg__card:nth-child(2) {
  transform: translateX(calc(-50% - var(--tcg-spread-inner))) rotate(-5deg) translateY(8px) translateY(var(--tcg-lift, 0px));
  transition-delay: 0.06s;
}

.nlp-tcg__fan.is-active .nlp-tcg__card:nth-child(3) {
  transform: translateX(-50%) rotate(0deg) translateY(6px) scale(1.05) translateY(var(--tcg-lift, 0px));
  transition-delay: 0.12s;
}

.nlp-tcg__fan.is-active .nlp-tcg__card:nth-child(4) {
  transform: translateX(calc(-50% + var(--tcg-spread-inner))) rotate(5deg) translateY(8px) translateY(var(--tcg-lift, 0px));
  transition-delay: 0.18s;
}

.nlp-tcg__fan.is-active .nlp-tcg__card:nth-child(5) {
  transform: translateX(calc(-50% + var(--tcg-spread-outer))) rotate(10deg) translateY(50px) translateY(var(--tcg-lift, 0px));
  transition-delay: 0.24s;
}

/* The desktop spread/size above is all fixed px, tuned against a wide panel -
 * on a phone-width panel (the container's own gutter drops to 10px at
 * ≤880px, see :root's --nlp-gutter) that same 390px outer spread pushes the
 * two outer cards' centers past the panel's edge by MORE than a card's own
 * width, so overflow: hidden was clipping them down to a barely-visible
 * sliver instead of the intended "sticking out a little" look. Redefining
 * the two custom properties (not overriding every transform rule again) is
 * enough - every rule above already reads them. */
@media (max-width: 560px) {
  .nlp-tcg__fan {
    height: clamp(150px, 40vw, 210px);
    margin-top: clamp(24px, 6vw, 40px);
    --tcg-spread-inner: 60px;
    --tcg-spread-outer: 122px;
  }

  .nlp-tcg__card {
    width: clamp(105px, 30vw, 150px);
  }

  .nlp-tcg__fan.is-active .nlp-tcg__card:nth-child(1) {
    transform: translateX(calc(-50% - var(--tcg-spread-outer))) rotate(-10deg) translateY(28px) translateY(var(--tcg-lift, 0px));
  }

  .nlp-tcg__fan.is-active .nlp-tcg__card:nth-child(5) {
    transform: translateX(calc(-50% + var(--tcg-spread-outer))) rotate(10deg) translateY(28px) translateY(var(--tcg-lift, 0px));
  }
}

@media (prefers-reduced-motion: reduce) {
  .nlp-tcg__card {
    transition: none;
  }

  .nlp-tcg__card:nth-child(1) { transform: translateX(calc(-50% - var(--tcg-spread-outer))) rotate(-10deg) translateY(50px); }
  .nlp-tcg__card:nth-child(2) { transform: translateX(calc(-50% - var(--tcg-spread-inner))) rotate(-5deg) translateY(8px); }
  .nlp-tcg__card:nth-child(3) { transform: translateX(-50%) rotate(0deg) translateY(6px) scale(1.05); }
  .nlp-tcg__card:nth-child(4) { transform: translateX(calc(-50% + var(--tcg-spread-inner))) rotate(5deg) translateY(8px); }
  .nlp-tcg__card:nth-child(5) { transform: translateX(calc(-50% + var(--tcg-spread-outer))) rotate(10deg) translateY(50px); }
}

/* ── Fan Translation ──────────────────────────────────────────────────────
 * Ported from the live site's .l-fantl / .c-ytc: same 5 real videos,
 * credits, and thumbnails, same click-to-play behaviour (new-landing-page.js
 * only ever creates the YouTube iframe on click, never in the markup - see
 * that file's own comment on why). The layout is a deliberate departure
 * from the live site's plain 3-across row, though:
 *
 * .nlp-fantl__swiper is NOT inside .nlp-container in the HTML - it's a
 * direct child of the <section>, which has no width constraint of its own,
 * so it runs the full viewport width edge to edge. Only the head (eyebrow/
 * title/nav) and foot (CTA button) stay inside a .nlp-container above and
 * below it.
 *
 * The carousel itself is Swiper (self-hosted nlp-swiper-bundle.min.{css,js},
 * MIT - new-landing-page.js configures it), not a hand-rolled scroll-snap
 * track: a hand-rolled version landed janky on the resize/grow transition
 * and had no clean way to loop past the last video back to the first.
 * centeredSlides + every slide sharing ONE fixed width (~70% of the
 * viewport - see .nlp-fantl__item below) is what actually gets "3 on
 * screen, sides sliced by the edge" - not a coverflow/scale effect, which
 * was tried first and took several rounds of tuning without landing right;
 * see that JS config's own comment. The classes below only style the slide
 * CONTENT; the sizing/positioning/looping is entirely Swiper's. */
.nlp-fantl {
  background: #ffffff;
  padding-block: clamp(48px, 6vw, 96px);
  overflow: hidden;
}

.nlp-fantl__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 32px;
}

.nlp-fantl__label {
  margin: 0 0 12px;
  color: var(--c-pink);
  font-family: 'Manrope', sans-serif;
  font-size: 14px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.nlp-fantl__title {
  margin: 0 0 12px;
  font-family: 'Manrope', sans-serif;
  font-size: clamp(26px, 3.4vw, 36px);
  font-weight: 800;
  color: var(--c-ink);
}

.nlp-fantl__text {
  margin: 0;
  max-width: 46ch;
  font-family: 'Manrope', sans-serif;
  font-size: 15px;
  line-height: 1.7;
  color: var(--c-body);
}

.nlp-fantl__nav {
  display: flex;
  gap: 12px;
  flex-shrink: 0;
  padding-bottom: 4px;
}

.nlp-fantl__arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 50%;
  border: 1.5px solid var(--c-border);
  background: #fff;
  color: var(--c-ink);
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s, color 0.2s;
}

.nlp-fantl__arrow svg {
  width: 8px;
  height: 12px;
}

.nlp-fantl__arrow:hover {
  background: var(--c-pink);
  border-color: var(--c-pink);
  color: #fff;
}

.nlp-fantl__swiper {
  padding-block: 24px;
}

/* No width rule here on purpose - Swiper computes each slide's width itself
   from the JS config's slidesPerView: 2.2 (container width / 2.2, applied
   as an inline style, which would win over a CSS width rule here anyway).
   That's what guarantees only 3 are ever on screen: the active slide takes
   ~45% of the viewport, leaving room on each side for the neighbour's edge
   to peek in - not enough for a whole 4th/5th slide, just a slice of it.
   The "center is bigger" look comes from .swiper-slide-active below
   instead, applied via transform (not a width/layout change) - it has to
   key off Swiper's own .swiper-slide-active class, which it adds to
   whichever slide is currently centered automatically. */
.nlp-fantl__card {
  height: 100%;
  text-align: center;
  /* transform, not width - scaling up the ACTIVE card only changes how it
     paints, not its layout box, so Swiper's own slide positions (computed
     from each slide's fixed width) never need to be recalculated for this.
     Animating width directly was the janky version - see LESSONS.md. */
  transform: scale(0.82);
  opacity: 0.55;
  transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.4s ease;
}

.swiper-slide-active .nlp-fantl__card {
  transform: scale(1);
  opacity: 1;
}

.nlp-fantl__play {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  padding: 0;
  border: 0;
  border-radius: 0;
  overflow: hidden;
  background: #000;
  cursor: pointer;
  /* Only the centered card is playable - a side card sits there half cut
     off by the screen edge, so a click on it should bring it to center
     (Swiper's own slideToClickedSlide, set in the JS config) rather than
     start playing a video the person can't even see in full yet. */
  pointer-events: none;
}

.swiper-slide-active .nlp-fantl__play {
  pointer-events: auto;
}

.nlp-fantl__play:focus-visible {
  outline: 3px solid var(--c-pink);
  outline-offset: 4px;
}

.nlp-fantl__thumb {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Zoomed in from the resting state on purpose, not just on hover - crops
     out roughly the outer 10px of the source image so the thumbnail never
     shows the thin border some of these fan-made thumbnails have baked in. */
  transform: scale(1.1);
  transition: transform 0.4s ease, opacity 0.3s ease;
}

.nlp-fantl__play:hover .nlp-fantl__thumb {
  transform: scale(1.16);
  opacity: 0.85;
}

.nlp-fantl__badge {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  padding-left: 4px;
  border-radius: 50%;
  background: var(--c-pink);
  color: #fff;
  transition: transform 0.3s ease;
}

.nlp-fantl__play:hover .nlp-fantl__badge {
  transform: translate(-50%, -50%) scale(1.1);
}

/* Swapped in for .nlp-fantl__play when a card is clicked. */
.nlp-fantl__frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: 0;
  overflow: hidden;
  background: #000;
}

.nlp-fantl__frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.nlp-fantl__meta {
  padding: 14px 2px 0;
}

.nlp-fantl__name {
  margin: 0 0 4px;
  font-family: 'Manrope', sans-serif;
  font-size: 16px;
  font-weight: 800;
  color: var(--c-ink);
}

.nlp-fantl__sub {
  margin: 0;
  font-family: 'Manrope', sans-serif;
  font-size: 13px;
  color: var(--c-body);
}

.nlp-fantl__foot {
  display: flex;
  justify-content: center;
  margin-top: 32px;
}

@media (max-width: 560px) {
  .nlp-fantl__head {
    flex-direction: column;
    align-items: flex-start;
  }

  .nlp-fantl__nav {
    display: none;
  }
}

/* ── Yumeshipper (Section 5b) ─────────────────────────────────────────────── */
/* Full-width #FFF7FB band. The art is one fixed-ratio stage with every piece
 * placed in % of it, so the whole composition scales together. Entrance is
 * gated on .is-active from revealNearBottom() (new-landing-page.js): cards
 * wipe in top to bottom, then the characters, then the bubble. The heartbeat
 * runs on its own, always. Composition measured off the design reference. */

.nlp-yume {
  position: relative;
  background: #fff7fb;
  padding-block: clamp(48px, 6vw, 96px);
  overflow: hidden;
}

.nlp-yume__inner {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  align-items: center;
  gap: clamp(24px, 5vw, 80px);
}

.nlp-yume__stage {
  position: relative;
  width: 100%;
  max-width: 620px;
  aspect-ratio: 5 / 3;
}

.nlp-yume__stage > * {
  position: absolute;
  display: block;
}

/* Heartbeat: faint full line + a bright segment that keeps running left to
 * right. pathLength="100" on both paths, so the dash numbers are % of the line. */
.nlp-yume__beat {
  /* spikes sit fully left of Ruka's card (card starts at 17%), only the
   * flat tail runs behind it */
  left: -24%;
  top: 27%;
  width: 40%;
  height: auto;
  overflow: visible;
}

.nlp-yume__beat path {
  fill: none;
  stroke-width: 6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Pen stroke: pathLength="100" so the dash numbers are % of the line.
 * Written left to right, held, erased left to right, then written again.
 * Duration is set by new-landing-page.js from the line's on-screen length,
 * so the pen moves at the same speed on every screen width. */
.nlp-yume__beat-pulse {
  stroke: #fcbfd9;
  stroke-dasharray: 100 100;
  stroke-dashoffset: 100;
  animation: nlp-yume-pen var(--nlp-beat-time, 5s) cubic-bezier(0.45, 0, 0.55, 1) infinite;
}

/* write and erase get the same share of the cycle (40% each) so the pen
 * moves at the same speed both ways; 15% hold when drawn, 5% blank. */
@keyframes nlp-yume-pen {
  0% { stroke-dashoffset: 100; }
  40% { stroke-dashoffset: 0; }
  55% { stroke-dashoffset: 0; }
  95%, 100% { stroke-dashoffset: -100; }
}

/* Diagonal cards: wipe in from the top edge down. */
.nlp-yume__card {
  bottom: 0;
  width: 48%;
  height: auto;
  clip-path: inset(0 0 100% 0);
  transition: clip-path 0.85s cubic-bezier(0.65, 0, 0.35, 1);
}

.nlp-yume__card--ruka { left: 14.75%; }
.nlp-yume__card--azuki { left: 53.25%; transition-delay: 0.15s; }

/* Characters: fade up once both cards are in. */
.nlp-yume__char {
  bottom: 4px;
  width: auto;
  opacity: 0;
  transform: translateY(6%);
  transition: opacity 0.8s cubic-bezier(0.165, 0.84, 0.44, 1), transform 0.8s cubic-bezier(0.165, 0.84, 0.44, 1);
}

.nlp-yume__char--ruka { left: 27%; height: 99%; transition-delay: 0.8s; }
.nlp-yume__char--azuki { left: 62%; height: 88%; transition-delay: 0.95s; }

/* Azuki's heart bubble: pops after she's in, then floats. */
.nlp-yume__bubble {
  /* follows Azuki: same offset from her as in the reference */
  left: 90%;
  top: 17%;
  width: 12.5%;
  height: auto;
  opacity: 0;
  transform: scale(0);
  transform-origin: 15% 100%;
  transition: opacity 0.2s ease 1.5s, transform 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) 1.5s;
}

.nlp-yume__stage.is-active .nlp-yume__card { clip-path: inset(0 0 0 0); }
.nlp-yume__stage.is-active .nlp-yume__char { opacity: 1; transform: none; }
.nlp-yume__stage.is-active .nlp-yume__bubble {
  opacity: 1;
  transform: scale(1);
  animation: nlp-yume-float 2.8s ease-in-out 2.1s infinite;
}

@keyframes nlp-yume-float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-8%); }
}

.nlp-yume__copy {
  max-width: 520px;
}

.nlp-yume__label {
  margin: 0 0 12px;
  color: var(--c-pink);
  font-family: 'Manrope', sans-serif;
  font-size: 14px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.nlp-yume__title {
  margin: 0 0 20px;
  font-family: 'Manrope', sans-serif;
  font-size: clamp(28px, 4vw, 40px);
  font-weight: 800;
  line-height: 1.25;
  color: var(--c-ink);
}

.nlp-yume__text {
  margin: 0;
  font-family: 'Manrope', sans-serif;
  font-size: 15px;
  line-height: 1.7;
  color: var(--c-body);
}

@media (max-width: 880px) {
  .nlp-yume__inner { grid-template-columns: 1fr; }
  .nlp-yume__stage { max-width: 520px; margin-inline: auto; }
  .nlp-yume__copy { max-width: none; }
  /* stage sits near the screen edge here, so keep the spike on screen */
  .nlp-yume__beat { left: -10%; width: 36%; }
}

@media (prefers-reduced-motion: reduce) {
  .nlp-yume__beat-pulse { animation: none; stroke-dasharray: none; stroke-dashoffset: 0; }
  .nlp-yume__card { clip-path: none; transition: none; }
  .nlp-yume__char,
  .nlp-yume__bubble { opacity: 1; transform: none; transition: none; }
  .nlp-yume__stage.is-active .nlp-yume__bubble { animation: none; }
}

/* ── Team (Babu server) ───────────────────────────────────────────────────── */
/* Roster comes from the nightly team-data.js (see the section's HTML comment).
 * Big ringed avatars, name, then an X link when the member has one. */

.nlp-team {
  background: #fff;
  padding-block: clamp(56px, 7vw, 112px);
  text-align: center;
}

.nlp-team[hidden] { display: none; }

.nlp-team__label {
  margin: 0 0 12px;
  color: var(--c-pink);
  font-family: 'Manrope', sans-serif;
  font-size: 14px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.nlp-team__title {
  /* same as the other centered section titles (features, fanart, fantl) */
  margin: 0 0 48px;
  font-family: 'Manrope', sans-serif;
  font-size: clamp(26px, 3.4vw, 36px);
  font-weight: 800;
  color: var(--c-ink);
}

.nlp-team__grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 32px clamp(24px, 4vw, 56px);
  margin: 0;
  padding: 0;
  list-style: none;
}

.nlp-team__member {
  width: 124px;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.nlp-team__avatar {
  display: block;
  width: 112px;
  height: 112px;
  border-radius: 50%;
  object-fit: cover;
  border: 3px solid var(--c-pink-light);
  background: #fff7fb;
}

.nlp-team__name {
  margin: 18px 0 0;
  font-family: 'Manrope', sans-serif;
  font-size: 18px;
  font-weight: 600;
  color: var(--c-ink);
  overflow-wrap: anywhere;
}

.nlp-team__x {
  display: inline-flex;
  margin-top: 14px;
  color: var(--c-pink);
  transition: transform 0.2s ease, color 0.2s ease;
}

.nlp-team__x:hover { transform: translateY(-2px); color: var(--c-pink-dark); }
.nlp-team__x svg { display: block; width: 26px; height: 26px; }

/* Stagger the cards' fade-up so the row fills in left to right. */
.nlp-team__member.nlp-reveal { transition-delay: calc(var(--i, 0) * 0.08s); }

@media (max-width: 560px) {
  .nlp-team__grid { gap: 28px 20px; }
  .nlp-team__member { width: 96px; }
  .nlp-team__avatar { width: 84px; height: 84px; }
  .nlp-team__name { font-size: 17px; margin-top: 12px; }
}

/* ── Footer ───────────────────────────────────────────────────────────────── */

.nlp-footer {
  background: #2b2b2b;
  color: #fff;
  padding-block: clamp(40px, 4.5vw, 56px) clamp(56px, 8vw, 120px);
  font-family: 'Manrope', sans-serif;
}

.nlp-footer__inner {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1.3fr) auto;
  gap: 40px clamp(32px, 5vw, 80px);
  align-items: start;
}

.nlp-footer__logo {
  display: block;
  width: 104px;
  height: auto;
}

.nlp-footer__about {
  margin: 20px 0 0;
  font-size: 13px;
  line-height: 1.75;
  color: #fff;
}

.nlp-footer__links {
  display: flex;
  gap: clamp(40px, 7vw, 96px);
}

.nlp-footer__col {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  align-content: start;
  gap: 14px;
}

.nlp-footer__col a {
  color: #fff;
  font-size: 13px;
  /* same weight as the disclaimer text (.nlp-footer__about, body default) */
  font-weight: 400;
  text-decoration: none;
}

.nlp-footer__col a:hover { color: var(--c-pink-light); }

.nlp-footer__social {
  display: grid;
  gap: 12px;
  min-width: 216px;
}

.nlp-footer__heading {
  margin: 0 0 4px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.nlp-footer__pill {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 26px 10px 24px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 999px;
  color: #fff;
  text-decoration: none;
  transition: border-color 0.2s ease, background-color 0.2s ease;
}

.nlp-footer__pill:hover { border-color: var(--c-pink-light); background: rgba(255, 255, 255, 0.04); }

.nlp-footer__icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
}

.nlp-footer__icon svg { width: 100%; height: 100%; display: block; }
.nlp-footer__icon--tiktok { color: var(--c-pink); }
.nlp-footer__icon--ig { background: var(--c-pink); border-radius: 7px; }
.nlp-footer__icon--ig svg { width: 80%; height: 80%; }

.nlp-footer__pill-body { display: flex; flex-direction: column; line-height: 1.3; }
.nlp-footer__pill-name { font-size: 15px; font-weight: 700; }
.nlp-footer__pill-handle { font-size: 12.5px; color: rgba(255, 255, 255, 0.85); }

@media (max-width: 880px) {
  .nlp-footer__inner { grid-template-columns: 1fr 1fr; }
  .nlp-footer__brand { grid-column: 1 / -1; }
  .nlp-footer__social { min-width: 0; }
}

@media (max-width: 560px) {
  .nlp-footer__inner { grid-template-columns: 1fr; gap: 32px; }
  .nlp-footer__brand { text-align: center; }
  .nlp-footer__logo { margin-inline: auto; }
  /* social pills: always 2 per row here, extra accounts wrap to new rows */
  .nlp-footer__social { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .nlp-footer__heading { grid-column: 1 / -1; }
  .nlp-footer__pill { gap: 8px; padding: 9px 12px; min-width: 0; }
  .nlp-footer__icon { width: 22px; height: 22px; }
  .nlp-footer__pill-body { min-width: 0; }
  .nlp-footer__pill-name { font-size: 14px; }
  .nlp-footer__pill-handle { font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* page links: one centered row that wraps, instead of two short columns
   * (the two <ul>s dissolve with display: contents so their items share it) */
  .nlp-footer__links { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px 24px; }
  .nlp-footer__col { display: contents; }
  .nlp-footer__heading { text-align: center; }
}

@media (max-width: 380px) {
  .nlp-footer__pill { gap: 6px; padding: 8px 9px; }
  .nlp-footer__icon { width: 20px; height: 20px; }
  .nlp-footer__pill-handle { font-size: 10.5px; }
}

/* ── Hero: 3-state scroll banner ──────────────────────────────────────────── */
/* .nlp-hero__track is two stage-heights tall; the stage inside it is sticky
 * just under the header, so scrolling through the track scrubs through the
 * three states without the page moving on. new-landing-page.js sets
 * data-state on the stage and --nlp-header-h on :root. The older
 * .nlp-slider rules above are no longer used by the markup. */

.nlp-hero {
  --nlp-hero-h: calc(100vh - var(--nlp-header-h, 72px));
  background: #eaf6fb;
}

@supports (height: 100dvh) {
  .nlp-hero { --nlp-hero-h: calc(100dvh - var(--nlp-header-h, 72px)); }
}

.nlp-hero__track {
  position: relative;
  height: calc(var(--nlp-hero-h) * 2);
}

.nlp-hero__stage {
  position: sticky;
  top: var(--nlp-header-h, 72px);
  height: var(--nlp-hero-h);
  overflow: hidden;
  isolation: isolate;
}

.nlp-hero__stage > * { position: absolute; }

.nlp-hero__sky {
  inset: 0;
  /* sky + sakura corners in one image (Banner Background.png) */
  background: url('/assets/nlp-hero-bg.webp') center center / cover no-repeat;
  transform: scale(1.06);
  transition: transform 1.6s cubic-bezier(0.16, 1, 0.3, 1);
}
.nlp-hero__stage:not([data-state="1"]) .nlp-hero__sky { transform: scale(1); }

/* State 1: welcome line + big wordmark */
.nlp-hero__intro {
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 0 var(--nlp-gutter) 8vh;
  text-align: center;
  z-index: 3;
  transition: opacity 0.6s ease, transform 0.8s cubic-bezier(0.65, 0, 0.35, 1), filter 0.6s ease;
}

.nlp-hero__welcome {
  margin: 0 0 clamp(8px, 2vh, 20px);
  font-family: 'Candy Beans', 'Montserrat Alternates', 'Manrope', sans-serif;
  font-size: clamp(18px, 2.6vw, 32px);
  font-weight: 400;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: #3d5a8a;
}

.nlp-hero__title { margin: 0; line-height: 0; }

.nlp-hero__logo-big {
  display: block;
  width: min(640px, 82vw);
  max-height: 46vh;
  height: auto;
  object-fit: contain;
}

.nlp-hero__stage:not([data-state="1"]) .nlp-hero__intro {
  opacity: 0;
  transform: translateY(-6vh) scale(0.94);
  filter: blur(6px);
  pointer-events: none;
}

/* State 2: the sprite rises in through a bottom-up wipe, overshoots a touch
 * and settles; it stays for state 3. */
.nlp-hero__sprite-wrap {
  left: 50%;
  bottom: 0;
  width: min(1180px, 112vw);
  max-height: 94%;
  aspect-ratio: 1230 / 713;
  transform: translateX(-50%);
  z-index: 1;
}

.nlp-hero__sprite {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center bottom;
  opacity: 0;
  transform: translateY(22%) scale(0.86);
  transform-origin: 50% 100%;
  filter: blur(12px) saturate(0.6);
  /* Soft bottom-up wipe instead of a clip-path (whose top edge was a hard
   * line). The mask is 2.5x the sprite's height: clear at the top, a 20%
   * feathered band, solid at the bottom. Sliding it from top-aligned (all
   * clear) to bottom-aligned (all solid) reveals the sprite upward with a
   * soft fading edge. */
  -webkit-mask-image: linear-gradient(to top, #000 0%, #000 40%, transparent 60%);
  mask-image: linear-gradient(to top, #000 0%, #000 40%, transparent 60%);
  -webkit-mask-size: 100% 250%;
  mask-size: 100% 250%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: 0 0%;
  mask-position: 0 0%;
  transition:
    opacity 0.5s ease,
    transform 1.1s cubic-bezier(0.34, 1.4, 0.64, 1),
    filter 0.9s ease,
    -webkit-mask-position 1s cubic-bezier(0.65, 0, 0.35, 1),
    mask-position 1s cubic-bezier(0.65, 0, 0.35, 1);
}

.nlp-hero__stage:not([data-state="1"]) .nlp-hero__sprite {
  opacity: 1;
  transform: none;
  filter: none;
  -webkit-mask-position: 0 100%;
  mask-position: 0 100%;
  transition-delay: 0.15s, 0.15s, 0.15s, 0.05s, 0.05s;
}

/* Bottom fade: ONE layer whose colour changes white -> dark (registered
 * custom properties, so the gradient's stops interpolate instead of one
 * layer fading out and another fading in). */
@property --nlp-fade-0 { syntax: '<color>'; inherits: false; initial-value: rgba(255, 255, 255, 1); }
@property --nlp-fade-1 { syntax: '<color>'; inherits: false; initial-value: rgba(255, 255, 255, 0.85); }
@property --nlp-fade-2 { syntax: '<color>'; inherits: false; initial-value: rgba(255, 255, 255, 0); }

.nlp-hero__fade {
  left: 0;
  right: 0;
  bottom: 0;
  height: 65%;
  z-index: 2;
  opacity: 0;
  pointer-events: none;
  background: linear-gradient(to top, var(--nlp-fade-0) 0%, var(--nlp-fade-1) 22%, var(--nlp-fade-2) 75%);
  transition: opacity 0.3s ease;
}

.nlp-hero__stage[data-state="2"] .nlp-hero__fade {
  opacity: 1;
  animation: nlp-hero-tint 0.55s cubic-bezier(0.4, 0, 0.2, 1) 0.45s both;
}

@keyframes nlp-hero-tint {
  from {
    --nlp-fade-0: rgba(255, 255, 255, 1);
    --nlp-fade-1: rgba(255, 255, 255, 0.85);
    --nlp-fade-2: rgba(255, 255, 255, 0);
  }
  to {
    --nlp-fade-0: rgba(18, 14, 28, 0.88);
    --nlp-fade-1: rgba(18, 14, 28, 0.6);
    --nlp-fade-2: rgba(18, 14, 28, 0);
  }
}

/* Small wordmark over the dark fade */
.nlp-hero__logo-small {
  left: 50%;
  bottom: clamp(56px, 11vh, 120px);
  width: min(380px, 62vw);
  height: auto;
  z-index: 3;
  opacity: 0;
  transform: translate(-50%, 24px) scale(0.92);
  transition: opacity 0.5s ease, transform 0.7s cubic-bezier(0.34, 1.56, 0.64, 1);
  pointer-events: none;
}

.nlp-hero__stage[data-state="2"] .nlp-hero__logo-small {
  opacity: 1;
  transform: translate(-50%, 0) scale(1);
  transition-delay: 0.7s;
}

.nlp-hero__cue {
  left: 50%;
  bottom: clamp(12px, 3vh, 28px);
  transform: translateX(-50%);
  z-index: 4;
}

/* Portrait screens: size the sprite by height instead, so the group fills
 * the lower part of the banner (sides crop, the middle of the group stays). */
@media (max-aspect-ratio: 1/1) {
  .nlp-hero__sprite-wrap { width: auto; height: 58%; max-height: none; }
  /* cover crops the sides on a tall screen: keep the top-left sakura in view */
  .nlp-hero__sky { background-position: left top; }
}

@media (max-width: 768px) {
  .nlp-hero__logo-small { bottom: clamp(64px, 12vh, 110px); }
}

@media (prefers-reduced-motion: reduce) {
  .nlp-hero__sky,
  .nlp-hero__intro,
  .nlp-hero__sprite,
  .nlp-hero__fade,
  .nlp-hero__logo-small { transition: opacity 0.3s ease; }
  .nlp-hero__sprite { transform: none; filter: none; -webkit-mask-image: none; mask-image: none; }
}

/* ── Mascot pop-up (Kumamaru / Panmy) ─────────────────────────────────────── */
/* Lives in the empty space outside the content column (--nlp-edge is the
 * distance from the screen edge to the content), so it never covers content:
 * mascot and bubble are both sized to fit inside that strip. Kumamaru on the
 * left, Panmy on the right. Hidden on screens too narrow to have that strip. */

.nlp-mascot {
  --nlp-room: calc(var(--nlp-edge) - 16px);
  position: fixed;
  bottom: -4px;
  z-index: 30;
  width: var(--nlp-room);
  pointer-events: none;
  transform: translateY(115%);
  transition: transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}

.nlp-mascot.is-left { left: 8px; }
.nlp-mascot.is-right { right: 8px; }
.nlp-mascot.is-in { transform: translateY(6%); transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); }

.nlp-mascot__img {
  display: block;
  width: min(104px, 100%);
  height: auto;
  margin: 0 auto;
  pointer-events: auto;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.nlp-mascot__img[hidden] { display: none; }
.nlp-mascot.is-in .nlp-mascot__img { animation: nlp-mascot-float 3s ease-in-out infinite; }
.nlp-mascot__img.is-hop { animation: nlp-mascot-hop 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) !important; }

@keyframes nlp-mascot-float {
  0%, 100% { transform: translateY(0) rotate(-2deg); }
  50% { transform: translateY(-6px) rotate(2deg); }
}

@keyframes nlp-mascot-hop {
  0% { transform: none; }
  35% { transform: translateY(-22px) scale(1.04, 0.97); }
  70% { transform: translateY(0) scale(0.98, 1.03); }
  100% { transform: none; }
}

/* Bubble sits above the mascot's head, as wide as its text (capped at the
 * strip's width so it still never reaches the content). */
.nlp-mascot__bubble {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 14px);
  width: max-content;
  max-width: var(--nlp-room);
  padding: 6px 10px;
  border: 2px solid var(--c-ink);
  border-radius: 12px;
  background: #fff;
  color: var(--c-ink);
  font-family: 'Manrope', sans-serif;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.35;
  text-align: center;
  box-shadow: 3px 3px 0 rgba(229, 48, 125, 0.25);
  opacity: 0;
  transform: translate(-50%, 8px) scale(0.9);
  transform-origin: 50% 100%;
  transition: opacity 0.25s ease, transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.nlp-mascot__bubble::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  margin-left: -7px;
  border: 7px solid transparent;
  border-top-color: var(--c-ink);
}

.nlp-mascot.is-talk .nlp-mascot__bubble { opacity: 1; transform: translate(-50%, 0); transition-delay: 0.2s; }

/* No strip outside the content on narrower screens: no mascot at all
 * (new-landing-page.js also stops toggling it there). */
@media (max-width: 1399px) {
  .nlp-mascot { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .nlp-mascot { transition: opacity 0.2s ease; }
  .nlp-mascot.is-in .nlp-mascot__img, .nlp-mascot__img.is-hop { animation: none !important; }
}

/* Hover lift. --tcg-lift is the last step of each fanned card's transform,
 * so it moves the card along its OWN tilted axis (straight out of the fan),
 * not straight up the screen - the outer cards are rotated up to 10deg.
 * Only after the deal-in has finished (.is-settled, added by
 * new-landing-page.js) so the hover doesn't inherit the staggered delays,
 * and only on devices that can actually hover. */
@media (hover: hover) {
  .nlp-tcg__fan.is-active.is-settled .nlp-tcg__card:nth-child(n) {
    transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.35s ease;
    transition-delay: 0s;
  }

  .nlp-tcg__fan.is-active.is-settled .nlp-tcg__card:hover {
    --tcg-lift: -26px;
    box-shadow: 0 30px 50px rgba(0, 0, 0, 0.28);
  }
}

/* ── Loading splash ───────────────────────────────────────────────────────── */
/* Same behaviour as the live site's .l-splash: wash background, logo rises in,
 * then the line under it, whole thing fades out (new-landing-page.js adds
 * .is-done after 3.2s). The line is the yumeshipper heartbeat with the same
 * pen write / hold / erase as .nlp-yume__beat-pulse, at a fixed speed since
 * the splash line has a fixed size. */

.nlp-splash {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  background: #fff7fb;
  transition: opacity 1.1s cubic-bezier(0.165, 0.84, 0.44, 1);
}

/* Exit: the logo and line pop out, then a circle opens from the middle and
 * grows until the whole page shows through (a radial mask whose transparent
 * centre's radius, --nlp-hole, animates from 0 to past the screen corners).
 * Browsers without @property just snap the hole open, so the opacity fade
 * below is kept as the fallback. */
.nlp-splash {
  /* starts below zero so the 48px soft edge is fully closed while loading
   * (at 0px it already showed a faint see-through spot in the middle) */
  --nlp-hole: -60px;
  -webkit-mask-image: radial-gradient(circle at 50% 50%, transparent var(--nlp-hole), #000 calc(var(--nlp-hole) + 48px));
  mask-image: radial-gradient(circle at 50% 50%, transparent var(--nlp-hole), #000 calc(var(--nlp-hole) + 48px));
}

.nlp-splash.is-done { pointer-events: none; }

/* Logo + line fade out on their own wrapper, so their entrance animations
 * (which hold opacity: 1) are never fought with. The circle only starts once
 * they're gone. */
.nlp-splash__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  transition: opacity 0.3s ease, transform 0.35s ease;
}

.nlp-splash.is-done .nlp-splash__inner {
  opacity: 0;
  transform: scale(1.12);
}

@property --nlp-hole {
  syntax: '<length>';
  inherits: false;
  initial-value: -60px;
}

.nlp-splash.is-done {
  --nlp-hole: 150vmax;
  transition: --nlp-hole 1s cubic-bezier(0.65, 0, 0.35, 1) 0.35s, opacity 0.4s ease 1.15s;
  opacity: 0;
}

.nlp-splash__mark {
  width: 220px;
  opacity: 0;
  animation: nlp-splash-rise 1.1s cubic-bezier(0.165, 0.84, 0.44, 1) forwards;
}

.nlp-splash__mark img { display: block; width: 100%; height: auto; }

.nlp-splash__beat {
  width: 96px;
  opacity: 0;
  animation: nlp-splash-rise 0.8s cubic-bezier(0.165, 0.84, 0.44, 1) 0.6s forwards;
}

.nlp-splash__beat svg { display: block; width: 100%; height: auto; overflow: visible; }

.nlp-splash__beat-path {
  fill: none;
  stroke: #f3a0c3;
  stroke-width: 8;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 100 100;
  stroke-dashoffset: 100;
  animation: nlp-yume-pen 1.8s cubic-bezier(0.45, 0, 0.55, 1) 0.8s infinite;
}

@keyframes nlp-splash-rise {
  0% { opacity: 0; transform: translateY(10px); }
  100% { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .nlp-splash { display: none; }
}


/* About intro paragraphs (above the checklist) */
.nlp-about__lead {
  margin: 0;
  font-family: 'Manrope', sans-serif;
  font-size: 16px;
  line-height: 1.6;
  color: var(--c-body);
}
.nlp-about__lead--2 { font-weight: 600; color: var(--c-ink); }

/* .nlp-stack--fit: cards keep their image's exact ratio instead of being
 * cropped to the stack box. --nlp-r (image width/height) is set from the
 * first card's <img> by new-landing-page.js; --nlp-cw is the card width as
 * a fraction of the stack, matching the widths above. */
.nlp-stack--fit { --nlp-cw: 0.62; aspect-ratio: calc(var(--nlp-r, 1.333) / var(--nlp-cw)); }
.nlp-stack--fit[data-count="2"] { --nlp-cw: 0.7; }
@media (max-width: 880px) { .nlp-stack--fit { --nlp-cw: 0.66; } }
/* one image's own ratio: show it whole. A shared data-ratio over mixed
 * images (the nobar stack): fill the card and crop lightly from the top. */
.nlp-stack--fit:not([data-ratio]) .nlp-stack__card img { object-fit: contain; }

/* Language switch hidden until the English copy exists (the toggle itself
 * still works, it just has nothing to switch yet). Remove this rule to bring
 * it back. */
.nlp-lang { display: none !important; }

/* Aktivitas Lainnya: two pink dot-grid rectangles in the section's top-left
 * and bottom-right corners, fully visible, inside the section padding. Behind the
 * content, hidden on narrow screens where there's no empty space. */
.nlp-activities { position: relative; }
.nlp-activities > .nlp-container { position: relative; z-index: 1; }
.nlp-activities__dots {
  position: absolute;
  z-index: 0;
  width: clamp(80px, 7vw, 104px);
  height: auto;
  pointer-events: none;
}
.nlp-activities__dots--a { top: 20px; left: 24px; }
.nlp-activities__dots--b { bottom: 20px; right: 24px; }
@media (max-width: 880px) { .nlp-activities__dots { display: none; } }
