/* ChunkArc depth layer — 3D motion, additive to styles.css.
 *
 * Motion personality: Playful, restrained. 200-350ms, ease-out, overshoot only
 * on hero elements. Three layers: primary (cards), secondary (voxel cubes),
 * ambient (grid drift, glow).
 *
 * Rules this file keeps:
 *  - animates transform/opacity only, never layout properties -> no CLS, GPU path
 *  - every motion is disabled under prefers-reduced-motion
 *  - no external requests (strict CSP: default-src 'self')
 */

:root {
  --ca-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ca-ease-soft: cubic-bezier(0.2, 0, 0, 1);
  --ca-dur-card: 260ms;
  --ca-dur-hero: 420ms;
}

/* ------------------------------------------------------------------ *
 * 1. Cards gain depth on hover — lift + a small 3D tilt.
 * ------------------------------------------------------------------ */

.section,
.hero,
.final-cta {
  perspective: 1400px;
}

.card {
  transform-style: preserve-3d;
  transition:
    transform var(--ca-dur-card) var(--ca-ease-out),
    box-shadow var(--ca-dur-card) var(--ca-ease-out),
    border-color var(--ca-dur-card) var(--ca-ease-out);
  will-change: transform;
}

@media (hover: hover) and (pointer: fine) {
  .card:hover {
    transform: translateY(-6px) translateZ(24px) rotateX(2.5deg);
    box-shadow:
      0 28px 60px rgba(0, 0, 0, 0.45),
      0 0 0 1px rgba(94, 220, 255, 0.18),
      0 0 40px rgba(63, 140, 255, 0.12);
  }

  /* Plan cards tilt toward the pointer side for a shelf-like feel. */
  .plan:hover {
    transform: translateY(-8px) translateZ(30px) rotateX(3deg) rotateY(-1.5deg);
  }
}

/* ------------------------------------------------------------------ *
 * 2. Hero card — idle float + pointer parallax (JS sets the vars).
 * ------------------------------------------------------------------ */

/* Specificity note: the reveal rule below is html.reveal-on [data-reveal].is-visible,
   which outranks a bare .hero-card and would reset this tilt to identity. The
   second selector matches that weight so the pointer tilt survives the reveal. */
.hero-card,
html.reveal-on .hero-card[data-reveal].is-visible {
  transform-style: preserve-3d;
  transform: perspective(1200px)
    rotateY(var(--ca-tilt-x, 0deg))
    rotateX(var(--ca-tilt-y, 0deg))
    translateZ(0);
  transition: transform 220ms var(--ca-ease-soft);
  will-change: transform;
}

.hero-card.ca-float {
  animation: ca-float 7s ease-in-out infinite;
}

@keyframes ca-float {
  0%,
  100% {
    translate: 0 0;
  }
  50% {
    translate: 0 -10px;
  }
}

/* Inner rows lift slightly off the card surface, so the tilt reads as real
   depth rather than a flat image being rotated. */
.hero-card > * {
  transform: translateZ(18px);
}

/* ------------------------------------------------------------------ *
 * 3. Reveal on scroll — rise with a touch of 3D rotation.
 *    reveal.js already toggles [data-reveal] -> .is-visible.
 * ------------------------------------------------------------------ */

/* Must stay gated behind html.reveal-on exactly like styles.css: that class is
   added by reveal.js, so with JS disabled or still loading nothing is hidden.
   Ungating this would make every revealed section invisible without JS. */
html.reveal-on [data-reveal] {
  transform: translateY(22px) rotateX(6deg);
  transform-origin: 50% 100%;
  transition:
    opacity var(--ca-dur-hero) var(--ca-ease-out),
    transform var(--ca-dur-hero) var(--ca-ease-out);
}

html.reveal-on [data-reveal].is-visible {
  transform: translateY(0) rotateX(0deg);
}

/* ------------------------------------------------------------------ *
 * 4. Voxel cubes — secondary layer. Real CSS 3D cubes (6 faces),
 *    decorative only, aria-hidden in the markup.
 * ------------------------------------------------------------------ */

.ca-voxels {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  perspective: 900px;
  z-index: 0;
}

.ca-voxel {
  position: absolute;
  width: var(--s, 46px);
  height: var(--s, 46px);
  transform-style: preserve-3d;
  animation: ca-spin var(--spin, 26s) linear infinite,
    ca-drift var(--drift, 13s) ease-in-out infinite;
  opacity: 0.72;
}

.ca-voxel span {
  position: absolute;
  inset: 0;
  border: 1.5px solid rgba(94, 220, 255, 0.78);
  background: rgba(63, 140, 255, 0.16);
  box-shadow: 0 0 18px rgba(94, 220, 255, 0.22), inset 0 0 14px rgba(63, 140, 255, 0.16);
}

/* six faces of a cube, half-size offset on each axis */
.ca-voxel span:nth-child(1) { transform: translateZ(calc(var(--s, 46px) / 2)); }
.ca-voxel span:nth-child(2) { transform: rotateY(180deg) translateZ(calc(var(--s, 46px) / 2)); }
.ca-voxel span:nth-child(3) { transform: rotateY(90deg)  translateZ(calc(var(--s, 46px) / 2)); }
.ca-voxel span:nth-child(4) { transform: rotateY(-90deg) translateZ(calc(var(--s, 46px) / 2)); }
.ca-voxel span:nth-child(5) { transform: rotateX(90deg)  translateZ(calc(var(--s, 46px) / 2)); }
.ca-voxel span:nth-child(6) { transform: rotateX(-90deg) translateZ(calc(var(--s, 46px) / 2)); }

@keyframes ca-spin {
  from { rotate: y 0deg; }
  to   { rotate: y 360deg; }
}

@keyframes ca-drift {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -26px; }
}

/* Keep the decoration clear of the hero copy on small screens. */
@media (max-width: 860px) {
  .ca-voxels { display: none; }
}

/* ------------------------------------------------------------------ *
 * 5. Ambient — a slow perspective grid under the hero.
 * ------------------------------------------------------------------ */

.hero {
  position: relative;
  isolation: isolate;
}

.ca-grid {
  position: absolute;
  inset: 42% -30% -20% -30%;
  pointer-events: none;
  z-index: 0;
  transform: perspective(700px) rotateX(74deg);
  background-image:
    linear-gradient(rgba(94, 220, 255, 0.13) 1px, transparent 1px),
    linear-gradient(90deg, rgba(94, 220, 255, 0.13) 1px, transparent 1px);
  background-size: 84px 84px;
  mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.7), transparent 70%);
  -webkit-mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.7), transparent 70%);
  animation: ca-grid-drift 18s linear infinite;
}

@keyframes ca-grid-drift {
  from { background-position: 0 0, 0 0; }
  to   { background-position: 0 84px, 0 84px; }
}

/* Hero content must sit above the ambient layers. */
.hero > .wrap,
.hero-copy,
.hero-card {
  position: relative;
  z-index: 1;
}

/* ------------------------------------------------------------------ *
 * 6. Buttons — press feedback within the 120-180ms band.
 * ------------------------------------------------------------------ */

.button,
.btn {
  transition:
    transform 140ms var(--ca-ease-out),
    box-shadow 200ms var(--ca-ease-out),
    filter 200ms var(--ca-ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .button:hover,
  .btn:hover {
    transform: translateY(-2px);
  }
}

.button:active,
.btn:active {
  transform: translateY(0) scale(0.985);
}

/* ------------------------------------------------------------------ *
 * 7. Reduced motion — stop everything, keep the layout identical.
 * ------------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  .card,
  .hero-card,
  .button,
  .btn,
  html.reveal-on [data-reveal] {
    transition: none !important;
    animation: none !important;
  }

  .hero-card {
    transform: none !important;
  }

  .card:hover,
  .plan:hover {
    transform: none !important;
  }

  html.reveal-on [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
  }

  .ca-voxel,
  .ca-grid {
    animation: none !important;
  }

  .ca-voxels {
    display: none;
  }
}

/* ------------------------------------------------------------------ *
 * 8. Header fit — pre-existing responsive gap, fixed here.
 *
 * Below 860px styles.css turns the nav into a horizontal scroller. Above
 * that, nothing constrains it until the layout is comfortable again, so
 * between roughly 861px and 1180px the nav flex-shrinks under its natural
 * width (measured: 543px wanted, 505px given at 974px) and labels wrap —
 * "How it works" broke onto three lines and collided with the wordmark.
 *
 * Fix: never break a label mid-phrase, and reclaim horizontal space in that
 * band so the nav fits at its natural width instead of being squeezed.
 * ------------------------------------------------------------------ */

.site-nav a {
  white-space: nowrap;
}

.brand {
  white-space: nowrap;
  flex: 0 0 auto;
}

@media (min-width: 861px) and (max-width: 1180px) {
  .site-header {
    gap: 10px;
  }

  .site-nav {
    gap: 0;
    flex-shrink: 1;
    min-width: 0;
  }

  .site-nav a {
    padding-inline: 8px;
    font-size: 0.9rem;
  }

  .header-actions {
    gap: 8px;
    flex: 0 0 auto;
  }
}

/* ------------------------------------------------------------------ *
 * ChunkArc motion v2 — mobile parity, idle cost, component feedback.
 *
 * v1 built a 3D layer that only desktop ever saw. reveal.js, the header
 * condense and every ambient element were gated behind a 860px breakpoint, so
 * phones got a completely static page — and for a Minecraft host that is where
 * most of the audience is. v2 does not ship the desktop treatment to mobile;
 * it gives mobile a lighter one of its own, and makes the desktop layer stop
 * costing anything once it is out of sight.
 *
 * Same rules as v1: transform/opacity only, everything off under
 * prefers-reduced-motion, no external requests.
 * ------------------------------------------------------------------ */

/* Lets block-size animate to and from `auto`, which the FAQ disclosure needs.
   Ignored by browsers that do not support it, where the panel simply snaps
   open exactly as it does today. */
:root {
  interpolate-size: allow-keywords;
}

/* ------------------------------------------------------------------ *
 * 1. Mobile reveal.
 *
 * A thumb flings a page far faster than a mouse wheel scrolls it, so the
 * desktop 550ms rise with a 6deg rotation arrives late and reads as jank.
 * Mobile gets the same idea at half the distance, no 3D, and quick enough to
 * keep up with the scroll.
 * ------------------------------------------------------------------ */

@media (max-width: 860px) {
  html.reveal-on [data-reveal] {
    transform: translateY(10px);
    transition-duration: 300ms;
  }

  html.reveal-on [data-reveal].is-visible {
    transform: none;
  }

  /* The plan cards sit in a horizontal scroller on mobile: 1358px of cards in
     a 358px viewport, so only the first one is ever intersecting. Revealing
     them would leave three cards at opacity 0 next to blank space, and the
     peek of the next card is the only thing telling anyone the row scrolls.
     They stay visible; the reveal has nothing useful to add here. */
  html.reveal-on .plan[data-reveal] {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* Grouped cards arrive in sequence instead of as one block. Scoped to
   consecutive cards, which is exactly the nine workflow steps and the four
   plan cards; standalone section headings have no card sibling and are
   untouched, so nothing that reveals on its own is ever delayed. */
html.reveal-on .card[data-reveal] + .card[data-reveal] {
  transition-delay: 70ms;
}

html.reveal-on .card[data-reveal] + .card[data-reveal] + .card[data-reveal] {
  transition-delay: 140ms;
}

html.reveal-on .card[data-reveal] + .card[data-reveal] + .card[data-reveal] + .card[data-reveal] {
  transition-delay: 210ms;
}

/* ------------------------------------------------------------------ *
 * 2. Ambient motion stops when nobody is looking.
 *
 * The four voxels and the grid ran twelve infinite animations for the whole
 * visit, still compositing when the hero was five thousand pixels above the
 * viewport. depth.js now flags them idle; this is what idle costs.
 * ------------------------------------------------------------------ */

.ca-voxels.is-idle .ca-voxel,
.ca-grid.is-idle {
  animation-play-state: paused;
}

/* ------------------------------------------------------------------ *
 * 3. FAQ answers open instead of snapping.
 *
 * Native <details> has no in-between state. ::details-content gives one
 * without touching the markup or adding a line of JavaScript, and where it is
 * unsupported the disclosure behaves exactly as it does now.
 * ------------------------------------------------------------------ */

@supports selector(details::details-content) {
  .faq details::details-content {
    overflow: hidden;
    block-size: 0;
    transition:
      block-size 300ms var(--ca-ease-out),
      content-visibility 300ms allow-discrete;
    transition-behavior: allow-discrete;
  }

  .faq details[open]::details-content {
    block-size: auto;
  }
}

/* The marker already rotates; give it the same easing as everything else so
   the two halves of the gesture agree. */
.faq summary::after {
  transition: transform 220ms var(--ca-ease-out);
}

.faq summary {
  transition: background-color 160ms ease, color 160ms ease;
}

/* ------------------------------------------------------------------ *
 * 4. Plan finder.
 *
 * Eleven options and, until now, no feedback that a choice had registered
 * beyond the native radio dot. The answer to a question should look answered.
 * ------------------------------------------------------------------ */

.finder-option {
  transition:
    border-color 160ms ease,
    background-color 160ms ease,
    transform 140ms var(--ca-ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .finder-option:hover {
    border-color: rgb(94 220 255 / 40%);
    transform: translateY(-1px);
  }
}

.finder-option:active {
  transform: scale(0.985);
}

.finder-option:has(input:checked) {
  border-color: rgb(94 220 255 / 62%);
  background: rgb(94 220 255 / 8%);
}

/* The recommendation is the payoff of answering three questions, so it should
   arrive rather than appear. plan-finder.js reveals it by clearing [hidden]. */
.plan-finder-result:not([hidden]) {
  animation: ca-result-in 380ms var(--ca-ease-out) both;
}

@keyframes ca-result-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ------------------------------------------------------------------ *
 * 5. Navigation.
 *
 * The header already changes on scroll but jumped between the two states.
 * ------------------------------------------------------------------ */

.site-header {
  transition:
    background-color 240ms ease,
    border-color 240ms ease,
    box-shadow 240ms ease;
}

.site-nav a {
  position: relative;
  transition: color 160ms ease;
}

/* A rule that grows from the centre, rather than a colour that switches. */
.site-nav a::after {
  content: "";
  position: absolute;
  left: 50%;
  right: 50%;
  bottom: -4px;
  height: 2px;
  border-radius: 2px;
  background: var(--cyan);
  opacity: 0;
  transition: left 220ms var(--ca-ease-out), right 220ms var(--ca-ease-out), opacity 220ms ease;
}

.site-nav a.active::after,
.site-nav a:hover::after {
  left: 0;
  right: 0;
  opacity: 1;
}

/* ------------------------------------------------------------------ *
 * 6. Reduced motion — v2 additions switched off with the rest.
 * ------------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  .faq details::details-content,
  .faq summary,
  .faq summary::after,
  .finder-option,
  .site-header,
  .site-nav a,
  .site-nav a::after {
    transition: none !important;
  }

  .plan-finder-result:not([hidden]) {
    animation: none !important;
  }

  .finder-option:hover,
  .finder-option:active {
    transform: none !important;
  }

  /* block-size is deliberately left alone here: 0 when closed, auto when open
     is the correct instant behaviour once the transition is gone. Forcing it
     to auto would hold every answer permanently open. */
}

/* ------------------------------------------------------------------ *
 * ChunkArc motion v3 — continuity between pages, and the guides.
 *
 * Measured before writing any of this: mobile LCP is 1.08s under a 4x CPU
 * throttle with CLS 0, so performance needed nothing and none of the work
 * below chases it. Two real gaps were left instead:
 *
 *  - navigating between the 32 pages was a hard white-out reload, which on a
 *    site where the guides cross-link each other constantly is the single most
 *    repeated moment in the whole experience
 *  - the 22 guide pages had literally zero motion: no hero for depth.js to
 *    decorate, and reveal.js is not loaded there at all
 *
 * The guides are also the pages that earn the traffic, so they get treated as
 * a reading surface rather than a leftover.
 *
 * Everything here is CSS-only — no new JavaScript, nothing for the strict CSP
 * to object to, and scroll-driven work runs off the main thread.
 * ------------------------------------------------------------------ */

/* ------------------------------------------------------------------ *
 * 1. Cross-document view transitions.
 *
 * One at-rule turns every internal navigation from a flash into a hand-off.
 * Browsers without support simply navigate as before — there is no fallback to
 * maintain and no JavaScript involved.
 * ------------------------------------------------------------------ */

@view-transition {
  navigation: auto;
}

::view-transition-old(root) {
  animation: ca-vt-out 160ms var(--ca-ease-out, ease) both;
}

::view-transition-new(root) {
  animation: ca-vt-in 240ms var(--ca-ease-out, ease) both;
}

@keyframes ca-vt-out {
  to {
    opacity: 0;
  }
}

@keyframes ca-vt-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* The header is on every page and in the same place. Naming it takes it out of
   the page snapshot so it holds still while the content behind it changes —
   the difference between "the site moved me" and "the browser reloaded". */
.site-header {
  view-transition-name: ca-header;
}

/* ------------------------------------------------------------------ *
 * 2. Reading progress, guides only.
 *
 * These articles run past 4,000 characters with a table of contents. A reader
 * has no sense of how much is left. Driven by scroll() rather than a scroll
 * listener, so it costs nothing on the main thread and cannot jank the scroll
 * it is measuring.
 * ------------------------------------------------------------------ */

@supports (animation-timeline: scroll()) {
  body.content-page::before {
    content: "";
    position: fixed;
    z-index: 90;
    top: 0;
    left: 0;
    width: 100%;
    height: 3px;
    background: linear-gradient(90deg, var(--cyan, #5edcff), #3f8cff);
    transform: scaleX(0);
    transform-origin: 0 50%;
    animation: ca-read-progress linear both;
    animation-timeline: scroll(root block);
    pointer-events: none;
  }

  @keyframes ca-read-progress {
    to {
      transform: scaleX(1);
    }
  }
}

/* ------------------------------------------------------------------ *
 * 3. Guide content arrives as you reach it.
 *
 * Deliberately applied to the standalone blocks only — comparison panels, fact
 * boxes, answer boxes, guide cards. Headings and body copy are left completely
 * alone: text that shifts while it is being read is worse than text that does
 * not move, and this is a reading page before it is a showcase.
 *
 * Opacity stays at 1 throughout. The small vertical movement keeps the visual
 * cue without reducing text contrast while a block enters the viewport.
 * ------------------------------------------------------------------ */

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    body.content-page .answer-box,
    body.content-page .comparison-item,
    body.content-page .fact,
    body.content-page .guide-card,
    body.content-page .step-list > li,
    body.content-page .notice {
      animation: ca-view-rise linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 40%;
    }
  }

  @keyframes ca-view-rise {
    from {
      opacity: 1;
      transform: translateY(14px);
    }

    to {
      opacity: 1;
      transform: none;
    }
  }
}

/* ------------------------------------------------------------------ *
 * 4. Guide components respond to the pointer.
 *
 * The guide grid is how someone moves from one article to the next, and it had
 * no hover state at all.
 * ------------------------------------------------------------------ */

.guide-card,
.comparison-item {
  transition:
    transform 200ms var(--ca-ease-out, ease),
    border-color 200ms ease,
    box-shadow 200ms ease;
}

@media (hover: hover) and (pointer: fine) {
  .guide-card:hover,
  .comparison-item:hover {
    transform: translateY(-3px);
    border-color: rgb(94 220 255 / 32%);
    box-shadow: 0 18px 40px rgb(0 0 0 / 32%);
  }
}

.article-nav a,
.breadcrumbs a,
.source-links a {
  transition: color 160ms ease, opacity 160ms ease;
}

/* ------------------------------------------------------------------ *
 * 5. Reduced motion.
 *
 * The view transition is not switched off — removing it mid-navigation leaves
 * a stale snapshot on screen — it is made instant instead, which is what the
 * preference actually asks for.
 * ------------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root),
  ::view-transition-old(ca-header),
  ::view-transition-new(ca-header) {
    animation-duration: 1ms !important;
  }

  body.content-page::before {
    display: none;
  }

  .guide-card,
  .comparison-item,
  .article-nav a,
  .breadcrumbs a,
  .source-links a {
    transition: none !important;
  }

  .guide-card:hover,
  .comparison-item:hover {
    transform: none !important;
  }
}
