/* =============================================================================
   individual.css  ·  the /individual journey page          (2026-07-31)
   =============================================================================
   The public "Individual" journey Tanya specified as seven beats:
     Explore Crypto · Research Faster · Find Your Tool · Ask & Discover ·
     Unlock Access · Learn More · Mirror Ai Community

   WHY A PAGE-PRIVATE STYLESHEET, in the `ind-` namespace:
     Everything here is one page's furniture. landing.html's design language is
     the reference, but its CSS is inline in a 400KB template and welded to that
     page's scroll engine, so it cannot be shared without dragging the engine
     along. This file reads ONLY the shared token contract (site-tokens.css):
     no new curve, duration, type size, radius, z-band or accent is invented
     here. That is the same rule the contract puts on every other consumer.

   LOAD ORDER: base.html links site-tokens.css and site-chrome.css (blocks
   `site_tokens_css` / `site_chrome_css`, both above `additional_head`), then
   this file. So this file wins ties against the chrome, and every var() below
   is guaranteed to be defined by the time it is read.

   MOTION / NO-JS CONTRACT (this is the important bit):
     Every rule below paints the page FULLY VISIBLE by default. The reveal
     choreography is opt-in and double-gated: it needs BOTH `html.ind-js`
     (set by an inline script in the template's head, so scripting-off never
     arms it) AND `prefers-reduced-motion: no-preference`. There is therefore
     no state in which content can be stranded at opacity 0, which is the
     failure mode landing.html had to add a whole `html:not(.js)` fallback
     block for. Same reason the section rail is hidden without JS: its dots
     scroll, and a control that cannot do its job should not take a tab stop.

   CONTRAST: body copy sits on --ink-2 (.85) and ledes on --ink-3 (.6) over the
   page's own #070a1c..#0b0f24, measured at 12.4:1 and 7.2:1. --ink-4 is NOT
   used for any body-sized text here: it is calibrated against the landing's
   blacks and only just clears 4.5:1, and this page's background is its own.
   ============================================================================= */

/* ---------------------------------------------------------------------------
   PAGE SHELL
   base.html's `body` rule is an inline <style> that comes AFTER
   {% block additional_head %}, so a bare `body` selector here would lose the
   tie. `body.ind-page` (0,1,1) beats it. The page also drops base's animated
   orb entirely (see the template's page_background override). An infinite
   20s float loop behind reading text is a WCAG 2.3.3 problem, not a flourish.
   --------------------------------------------------------------------------- */
body.ind-page {
  background: #070a1c;
  font-family: var(--font-ui);
  color: var(--ink-2);
}

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

/* The one static backdrop. No animation, no JS, no canvas. */
.ind-backdrop {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(ellipse 70% 50% at 50% -10%, rgba(0, 242, 254, .07), transparent 70%),
    radial-gradient(ellipse 60% 45% at 90% 105%, rgba(88, 185, 255, .05), transparent 72%);
}

.ind-main { position: relative; z-index: var(--z-content); }
.ind-main:focus { outline: none; }   /* it receives programmatic focus from the skip link */

/* ---------------------------------------------------------------------------
   SECTION + CONTENT COLUMN
   1200 / 40 is the landing's own act9 measure (`.act9-wrap`), so this page's
   text column starts and ends on the same verticals as the shared footer
   directly beneath it. Anything narrower reads as inset against the header.
   The consequence is that the column DOES reach into the fixed rail's label
   zone below 1600px, which is why the persistent active label is gated at that
   width in the rail block below. --rail-safe documents the measurement.
   --------------------------------------------------------------------------- */
.ind-section {
  position: relative;
  padding: var(--section-pad-y) 0;
}
/* The rail moves focus to the section it scrolls to (js/individual.js), which
   needs tabindex="-1" in the template. Programmatic focus must not draw a ring
   around a whole band: the ring belongs on the control the user activated. */
.ind-section:focus { outline: none; }
.ind-section + .ind-section { padding-top: 0; }

.ind-wrap {
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 clamp(20px, 4vw, 40px);
}

/* A hairline between beats so seven stacked bands read as a sequence. */
.ind-section::before {
  content: "";
  position: absolute; left: 50%; top: 0;
  width: min(1200px, 100% - 2 * clamp(20px, 4vw, 40px));
  transform: translateX(-50%);
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--accent-line), transparent);
}
.ind-section:first-of-type::before { display: none; }

/* ---------------------------------------------------------------------------
   SECTION HEADERS
   The `// LABEL` mono eyebrow is the site's established section marker
   (landing: .wim__eyebrow / .a-eyebrow / .act9-eyebrow). Each eyebrow repeats
   the rail label for the same beat, so the rail and the page name the sections
   identically. That is the point of the label, not decoration.
   --------------------------------------------------------------------------- */
.ind-head { max-width: 760px; }
.ind-head--center { margin-inline: auto; text-align: center; }

.ind-eyebrow {
  display: block;
  margin: 0 0 var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--type-m2);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--accent);
}

.ind-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--type-d3);
  font-weight: 600;
  line-height: var(--lh-display);
  letter-spacing: var(--track-display);
  color: var(--ink-1);
}
.ind-title--hero { font-size: var(--type-d2); }

.ind-sub {
  margin: var(--space-5) 0 0;
  font-size: var(--type-u3);
  line-height: var(--lh-body);
  color: var(--ink-3);
}

/* ---------------------------------------------------------------------------
   BUTTONS
   Two variants only. Both are >=48px tall, i.e. clear of the 44px tap-target
   floor with room for the border, at every breakpoint.
   --------------------------------------------------------------------------- */
.ind-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  min-height: 48px;
  padding: 0 var(--space-5);
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font-family: var(--font-ui);
  font-size: var(--type-u5);
  font-weight: 600;
  letter-spacing: var(--track-tight);
  text-decoration: none;
  cursor: pointer;
  transition:
    transform  var(--dur-2) var(--ease-out),
    box-shadow var(--dur-2) var(--ease-out),
    background var(--dur-2) var(--ease-out),
    border-color var(--dur-2) var(--ease-out),
    color      var(--dur-2) var(--ease-out);
}
.ind-btn--primary {
  background: var(--grad-accent);
  color: #04121a;
  box-shadow: 0 6px 18px rgba(0, 242, 254, .18);
}
.ind-btn--primary:hover {
  transform: translateY(-1px);
  box-shadow: 0 10px 26px var(--accent-glow);
  color: #04121a;
}
.ind-btn--ghost {
  background: var(--accent-wash);
  border-color: var(--accent-line);
  color: var(--ink-1);
}
.ind-btn--ghost:hover {
  border-color: var(--accent-line-hi);
  background: rgba(0, 242, 254, .1);
  color: var(--ink-1);
}

.ind-ctas {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4);
  margin-top: var(--space-6);
}
.ind-ctas--center { justify-content: center; }

.ind-note {
  margin: var(--space-4) 0 0;
  font-family: var(--font-mono);
  font-size: var(--type-m2);
  letter-spacing: var(--track-data);
  color: var(--ink-3);
}
.ind-note--center { text-align: center; }

/* ---------------------------------------------------------------------------
   FOCUS
   ONE visible ring for every interactive element on the page. The site accent,
   not the UA default: Chromium draws its default ring in rgb(16,16,16), which
   on this navy is an invisible focus indicator (the same finding that produced
   the landing's W5.4 rail fix).
   --------------------------------------------------------------------------- */
.ind-page a:focus-visible,
.ind-page button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}
.ind-btn:focus-visible { border-radius: var(--r-pill); }

/* ---------------------------------------------------------------------------
   (1) EXPLORE CRYPTO  ·  page hero
   --------------------------------------------------------------------------- */
.ind-hero { padding-top: clamp(56px, 8vw, 104px); }
.ind-hero .ind-sub { color: var(--ink-2); }

/* ---------------------------------------------------------------------------
   (2) RESEARCH FASTER  ·  three benefit cards, no CTA
   Tanya's mockup used emoji here. Emoji render as a different typeface per OS,
   carry no reliable accessible name, and do not tint with the brand accent, so
   they are inline stroke SVG instead, the same idiom the shared footer's
   social row already uses. Each is aria-hidden: the card's own <h3> is the
   accessible name, and an icon that repeats it is noise to a screen reader.
   --------------------------------------------------------------------------- */
.ind-grid {
  display: grid;
  gap: var(--space-5);
  margin-top: var(--space-7);
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.ind-card {
  position: relative;
  padding: var(--space-6) var(--space-5);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-lg);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .04), rgba(255, 255, 255, .012));
  transition: border-color var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
}
.ind-card:hover { border-color: var(--accent-line-hi); }

.ind-card__icon {
  display: grid; place-items: center;
  width: 44px; height: 44px;
  margin-bottom: var(--space-5);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-md);
  background: var(--accent-wash);
  color: var(--accent);
}
.ind-card__icon svg { display: block; }

.ind-card__title {
  margin: 0 0 var(--space-3);
  font-family: var(--font-display);
  font-size: var(--type-u2);
  font-weight: 600;
  letter-spacing: var(--track-tight);
  color: var(--ink-1);
}
.ind-card__body {
  margin: 0;
  font-size: var(--type-u5);
  line-height: var(--lh-body);
  color: var(--ink-2);
}

/* ---------------------------------------------------------------------------
   (3) FIND YOUR TOOL  ·  three persona cards
   The photography is the existing photoreal persona master set, re-derived for
   this page as 4:3 WebP at 1200w / 800w (see the generation note in the
   template). width/height are on the <img> so the 4:3 box is reserved before
   the bytes land: no layout shift, no CLS on the tallest section of the page.
   --------------------------------------------------------------------------- */
.ind-persona {
  display: flex; flex-direction: column;
  overflow: hidden;
  padding: 0;
}
.ind-persona__media {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: #0c1226;
}
.ind-persona__media img {
  display: block;
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform var(--dur-5) var(--ease-out);
}
.ind-persona:hover .ind-persona__media img { transform: scale(1.04); }
/* A cool wash keeps three separately-shot photos reading as one set, and lifts
   the label off the brightest frames. */
.ind-persona__media::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(7, 10, 28, .1) 0%, rgba(7, 10, 28, .72) 100%);
}
.ind-persona__body {
  display: flex; flex-direction: column; flex: 1 1 auto;
  gap: var(--space-3);
  padding: var(--space-5);
}
.ind-persona__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--type-u2);
  font-weight: 600;
  letter-spacing: var(--track-tight);
  color: var(--ink-1);
}
.ind-persona__outcome {
  margin: 0 0 var(--space-4);
  font-size: var(--type-u5);
  line-height: var(--lh-body);
  color: var(--ink-2);
}
.ind-persona__cta { margin-top: auto; align-self: flex-start; }

/* ---------------------------------------------------------------------------
   (4) ASK & DISCOVER
   DESIGN DECISION (client-approved): this section does NOT clone the landing's
   live streaming terminal. That terminal is inline in landing.html and welded
   to its own controller; duplicating it would be a refactor with regression
   risk on the page's single highest-value interaction. This band carries the
   copy, the three suggested questions from the mockup, and sends every one of
   them to the real terminal at /#act4.
   --------------------------------------------------------------------------- */
.ind-chips {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: var(--space-3);
  margin: var(--space-6) 0 0;
  padding: 0;
  list-style: none;
}
.ind-chip {
  display: inline-flex; align-items: center; gap: var(--space-2);
  min-height: 44px;
  padding: 0 var(--space-4);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .03);
  font-family: var(--font-mono);
  font-size: var(--type-m1);
  letter-spacing: var(--track-data);
  color: var(--ink-2);
  text-decoration: none;
  transition: border-color var(--dur-2) var(--ease-out), background var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out);
}
.ind-chip:hover {
  border-color: var(--accent-line-hi);
  background: var(--accent-wash);
  color: var(--ink-1);
}
.ind-chip__mark { color: var(--accent); }

/* ---------------------------------------------------------------------------
   (5) UNLOCK ACCESS
   DESIGN DECISION (founder instruction): pricing lives in exactly ONE place.

   2026-08-03 · WP2 · that place is now THIS section. It used to be the landing
   page, and this band was deliberately tier-free (no cards, no amounts, no
   currency) with a secondary link out to the numbers. Glass Lane's skeleton V1
   (27/07/2026) gives the landing nine sections, none of them pricing, and names
   this beat as the trial surface, so the Stripe tier cards moved here.

   `.ind-band` below still styles ONLY the conversion band, the bordered card
   carrying her heading, subheading and two CTAs. The tier grid is a sibling of
   the band inside the same section, is not styled from this file at all, and
   brings its own act9- namespace (components/landing/act9_styles.html, scoped
   `--act9-*` tokens from site-chrome.css). The two do not share a selector.
   --------------------------------------------------------------------------- */
.ind-band {
  padding: clamp(40px, 5vw, 64px) var(--section-pad-x);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-lg);
  background:
    radial-gradient(ellipse 80% 120% at 50% 0%, rgba(0, 242, 254, .1), transparent 70%),
    linear-gradient(180deg, rgba(255, 255, 255, .045), rgba(255, 255, 255, .012));
  text-align: center;
}
.ind-band .ind-head { margin-inline: auto; }

/* ---------------------------------------------------------------------------
   (7) MIRROR AI COMMUNITY
   Four destinations, every href an account that already exists in this
   codebase. X, LinkedIn and Discord come from components/site_footer.html and
   Telegram from templates/faq.html (the landing's #act9-socials band resolves
   the same four the same way). No URL is invented here.
   --------------------------------------------------------------------------- */
.ind-socials {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: var(--space-4);
  margin: var(--space-6) 0 0;
  padding: 0;
  list-style: none;
}
.ind-social {
  display: inline-flex; flex-direction: column; align-items: center; gap: var(--space-2);
  min-width: 92px; min-height: 92px;
  padding: var(--space-4) var(--space-3);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-md);
  background: rgba(255, 255, 255, .03);
  color: var(--ink-2);
  text-decoration: none;
  transition: border-color var(--dur-2) var(--ease-out), background var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
}
.ind-social:hover {
  border-color: var(--accent-line-hi);
  background: var(--accent-wash);
  color: var(--accent);
  transform: translateY(-2px);
}
.ind-social svg { display: block; }
.ind-social__name {
  font-family: var(--font-mono);
  font-size: var(--type-m3);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}

/* =============================================================================
   SECTION RAIL  ·  .irail
   -----------------------------------------------------------------------------
   Same pattern as the landing's .mrail, rebuilt self-contained under its own
   namespace (the landing's copy is inline in that template and carries its
   scroll-engine assumptions). Kept from the original, deliberately:
     · real <button>s, so the rail is keyboard reachable and announced as a
       navigation list rather than a row of decorative dots;
     · a transparent ::before that grows the 20x20 dot's HIT area to the rail's
       full 44 x 42 pitch, without changing a pixel of what is painted;
     · an explicit :focus-visible ring in the accent, because the UA default is
       near-black on a near-black page;
     · labels off below 860px.
   ADDED here: `aria-current="true"` moves with `.is-active` (landing W5.9), so
   a screen reader can tell WHICH of seven identical "Go to X" buttons is the
   section it is currently in.
   ============================================================================= */
.irail {
  position: fixed; top: 50%; right: clamp(14px, 2vw, 26px);
  transform: translateY(-50%);
  z-index: var(--z-rail);
  /* 24, not the landing's 22. The dot is 20px, so 20 + 24 makes the rail's
     pitch exactly 44px, which is what lets the hit area below be a full
     44 x 44 without two dots' zones overlapping. The landing's 42px pitch
     forces a 44 x 42 target, i.e. 2px under the floor on the vertical axis. */
  display: flex; flex-direction: column; align-items: flex-end; gap: 24px;
}
.irail::before {
  content: "";
  position: absolute; right: 9px; top: -8px; bottom: -8px; width: 1px;
  background: linear-gradient(180deg, transparent, rgba(0, 242, 254, .3), transparent);
}
.irail__dot {
  position: relative;
  appearance: none; -webkit-appearance: none;
  border: 0; background: transparent; cursor: pointer;
  width: 20px; height: 20px;
  display: grid; place-items: center;
  padding: 0;
}
.irail__dot::after {
  content: "";
  width: 8px; height: 8px; border-radius: var(--r-full);
  background: rgba(130, 205, 255, .32);
  box-shadow: 0 0 0 1px rgba(0, 242, 254, .25);
  transition: transform var(--dur-2) var(--ease-out), background var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
}
/* Hit area, not paint: the pseudo grows the 20x20 button to a full 44 x 44
   target. 44 wide is free (nothing sits beside the rail); 44 tall is exactly
   the rail's pitch (20px dot + 24px gap), so adjacent dots tile without ever
   overlapping each other's zone. Visually identical, the pseudo paints
   nothing. */
.irail__dot::before { content: ""; position: absolute; inset: -12px; }
.irail__dot:hover::after { background: rgba(0, 242, 254, .7); transform: scale(1.2); }
.irail__dot.is-active::after {
  background: var(--accent); transform: scale(1.35);
  box-shadow: 0 0 0 4px rgba(0, 242, 254, .18), 0 0 10px rgba(0, 242, 254, .55);
}
.irail__dot:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-sm);
}
.irail__label {
  position: absolute; right: 26px; top: 50%;
  transform: translateY(-50%) translateX(6px);
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: var(--type-m3);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: rgba(255, 255, 255, .9);
  background: rgba(9, 14, 32, .82);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  padding: 4px 9px;
  border-radius: var(--r-sm);
  border: 1px solid var(--accent-line);
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
}
.irail__dot:hover .irail__label { opacity: 1; transform: translateY(-50%) translateX(0); }
/* The ACTIVE label is persistent, so unlike the hover label it must never sit
   on top of the content column. --rail-safe is 216px (offset 26 + dot 20 + the
   widest label + air); the content column is 1200 wide with 40px padding, so
   the painted content clears the rail only from 1600px up:
     1600 -> content ends at 1360, label starts at ~1420. Clear by 60px.
     1440 -> content ends at 1280, label would start at ~1260. OVERLAP, so off.
   Below 1600 the dots still work and still label themselves on hover, which is
   transient and sits on a blurred pill. Only the always-on label is withheld.
   Same trade the landing makes at its own breakpoint, one step lower. */
@media (min-width: 1600px) {
  .irail__dot.is-active .irail__label { opacity: 1; transform: translateY(-50%) translateX(0); }
}
@media (max-width: 860px) {
  .irail { right: 8px; gap: 16px; }
  .irail__label { display: none; }
}
/* Without JS the dots cannot scroll anywhere, so they must not take seven tab
   stops on the way to the content. */
html:not(.ind-js) .irail { display: none; }

/* =============================================================================
   REVEAL CHOREOGRAPHY  ·  double-gated, opt-in
   -----------------------------------------------------------------------------
   Default (this file's normal state): nothing is hidden. The transform below
   only exists inside BOTH gates, so:
     · JS off / script threw  -> no `html.ind-js`  -> page renders as a plain
                                 fully readable document;
     · reduced motion         -> media query excludes -> everything static and
                                 at full opacity.
   ============================================================================= */
@media (prefers-reduced-motion: no-preference) {
  html.ind-js .ind-reveal {
    opacity: 0;
    transform: translateY(20px);
    transition: opacity var(--dur-6) var(--ease-out), transform var(--dur-6) var(--ease-out);
  }
  html.ind-js .ind-reveal.is-in {
    opacity: 1;
    transform: none;
  }
  /* Children stagger off their parent's reveal via an inline --i index. */
  html.ind-js .ind-rise {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity var(--dur-5) var(--ease-out), transform var(--dur-5) var(--ease-out);
    transition-delay: calc(var(--stagger-lead) + var(--i, 0) * var(--stagger-unit));
  }
  html.ind-js .ind-reveal.is-in .ind-rise {
    opacity: 1;
    transform: none;
  }
}

/* =============================================================================
   RESPONSIVE
   Verified at 1440 / 1024 / 768 / 390. Nothing here may introduce a horizontal
   scrollbar: every grid track is minmax(0, 1fr) (not 1fr, which floors at
   min-content and is the usual cause of a grid overflowing its container), and
   every image is width:100%.
   ============================================================================= */
@media (max-width: 1023px) {
  .ind-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* An orphaned third card in a 2-up grid looks like a mistake; centring it
     under the pair reads as deliberate. */
  .ind-grid > :last-child:nth-child(odd) { grid-column: 1 / -1; max-width: 460px; margin-inline: auto; }
}

@media (max-width: 767px) {
  .ind-grid { grid-template-columns: minmax(0, 1fr); gap: var(--space-4); }
  .ind-grid > :last-child:nth-child(odd) { max-width: none; }
  .ind-head { max-width: none; }
  .ind-ctas { gap: var(--space-3); }
  .ind-btn { width: 100%; }
  .ind-persona__cta { width: auto; align-self: stretch; }
}

@media (max-width: 480px) {
  /* NOTE the `> li`. Both rows are <ul>/<li>, so the FLEX ITEM is the list
     item, not the <a> inside it. Sizing the anchor here does nothing: it just
     fills an li that is still sized by its own content. Measured, not assumed:
     the first attempt put these on .ind-chip / .ind-social and the four social
     tiles still came out 51 / 92 / 84px wide on one row. */
  .ind-chips > li { width: 100%; }
  .ind-chip { width: 100%; justify-content: flex-start; }
  /* flex-SHRINK must be 0. With the default `1` the four tiles squeeze
     themselves down to fit three on the first row and leave LinkedIn stranded
     alone on the second, which reads as a wrap bug rather than a grid. Locking
     the basis at half the row (minus the gap) forces a clean 2 x 2. */
  .ind-socials > li { flex: 1 0 calc(50% - var(--space-4)); }
  .ind-social { min-width: 0; width: 100%; }
}
