/**
 * 100x Life Planning System - Base Styles
 * Reset, typography, and common elements
 */

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

/* Prevent page-level horizontal scroll from off-canvas elements (the parking-lot
   drawer slides off the right edge). Clipping at the root doesn't create a new
   scroll container, so the sticky header/nav and the action grid's own
   horizontal scroll are unaffected; it's a no-op when nothing overflows. */
html {
  overflow-x: hidden;
}

/* Bootstrap's reboot sets :root { scroll-behavior: smooth }, which races
   Turbo Drive's anchor positioning — deep links like /time#currentChart were
   landing at the top of the page. Navigation scrolling must be instant and
   deterministic (lib/anchor_scroll.js owns it); anything that wants an
   animated scroll asks for it explicitly in JS. (:root to match Bootstrap's
   specificity; this sheet loads after Bootstrap, so it wins the cascade.) */
:root {
  scroll-behavior: auto;
}

/* Anchored sections land below the sticky header/journey bar instead of
   underneath it. scroll-margin only affects anchor/scrollIntoView positioning,
   so applying it to every id-carrying element is safe. */
[id] {
  scroll-margin-top: 72px;
}

body {
  font-family: var(--font-body);
  font-size: var(--size-body);
  background: var(--bg-secondary);
  color: var(--text-dark);
  min-height: 100dvh;
  position: relative;
  line-height: var(--leading-ui);
}

/* Full-width topography texture. Transcribed from the reference's backdrop
   element (docs/design-reference/extracted/state-complete-hub.html): the
   texture tiles at 900px, sits at 0.3 opacity, is fixed against the viewport,
   and fades out over the top 60% of the page. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  background-image: var(--texture-topography);
  background-size: 900px;
  background-repeat: repeat;
  background-attachment: fixed;
  background-position: center top;
  opacity: 0.3;
  pointer-events: none;
  z-index: 0;
  mask-image: linear-gradient(to bottom, rgb(0, 0, 0) 0%, transparent 60%);
  -webkit-mask-image: linear-gradient(to bottom, rgb(0, 0, 0) 0%, transparent 60%);
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading);
  font-weight: 500;
  line-height: 1.2;
}

/* Typography - 100x Forum sizes */
h1 {
  font-size: calc(48px + var(--text-bump));
}

h2 {
  font-size: calc(32px + var(--text-bump));
}

h3 {
  font-size: calc(26px + var(--text-bump));
}

h4 {
  font-size: calc(24px + var(--text-bump));
}

h5 {
  font-size: calc(20px + var(--text-bump));
}

h6 {
  font-size: calc(18px + var(--text-bump));
}

p {
  line-height: 1.7;
  margin-bottom: 1rem;
}

/* Lead text style */
.lead {
  font-size: calc(20px + var(--text-bump));
  font-weight: 400;
  line-height: 1.7;
}

/* UI font utility */
.font-ui {
  font-family: var(--font-ui);
}

/* Links */
a {
  color: var(--teal-ink);
  text-decoration: none;
  transition: color var(--motion-quick) var(--ease-out);
}

a:hover {
  color: var(--text-dark);
}

/* The focus ring. One rule, one set of tokens (variables.css), no overrides
   anywhere: every `outline: none` in the app was deleted on 2026-08-11 because
   between them they had removed the ring from most of the surfaces a keyboard
   user needs it on — worst of all the People map, whose medallions are its only
   controls.
   :focus-visible, not :focus, so a mouse click on a button still does not draw
   a ring; the paired rule below suppresses the UA's default in that case. */
:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

:focus:not(:focus-visible) {
  outline: none;
}

/* Bootstrap ships its own `outline: 0` on these, at a specificity the bare
   :focus-visible above cannot beat, and substitutes a box-shadow built from
   --bs-* variables the app never sets — so every .btn in the app had no ring
   at all. base.css loads after Bootstrap, so restating the ring at the same
   specificity is enough. */
.btn:focus-visible,
.btn-close:focus-visible,
.nav-link:focus-visible,
.page-link:focus-visible,
.accordion-button:focus-visible,
.navbar-toggler:focus-visible,
.form-control:focus-visible,
.form-select:focus-visible,
.form-check-input:focus-visible,
.form-range:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* The UA stylesheet gives every <dialog> `color: CanvasText`, so a dialog does
   NOT inherit the page's text colour — which in dark theme meant black text on
   the modal's own dark card (the assessment review, the scenario modals).
   Element specificity, so any dialog that wants its own colour still wins. */
dialog {
  color: var(--text-dark);
}

/* Screen reader only - visually hidden but accessible */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Skip link for keyboard navigation */
.skip-link {
  position: absolute;
  top: -100px; /* fully off-screen until focused (its height exceeded the old -40px) */
  left: 0;
  background: var(--surface-inverse);
  color: var(--text-on-accent);
  padding: 8px 16px;
  z-index: 9999;
  text-decoration: none;
  font-weight: 500;
}

.skip-link:focus {
  top: 0;
}

/* Scrollbars are native — modern browsers do this fine on their own.
   (The custom 8px always-visible bar came over from the prototype and was
   removed on request; per-element hides like .tapscroll stay.) */

/* Selection */
::selection {
  background: var(--teal-light);
  color: var(--navy-primary);
}

/* Utility classes */
.text-muted {
  color: var(--text-muted);
}

.text-secondary {
  color: var(--text-secondary);
}

.text-primary {
  color: var(--navy-primary);
}

.text-accent {
  color: var(--teal-ink);
}

.font-heading {
  font-family: var(--font-heading);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Expandable Text - for user content that shouldn't be truncated */
.expandable-text {
  display: inline;
}

.btn-expand-text {
  background: none;
  border: none;
  color: var(--teal-ink);
  cursor: pointer;
  font-size: calc(0.85em + var(--text-bump));
  padding: 0;
  margin-left: 0.25rem;
  font-weight: 500;
}

.btn-expand-text:hover {
  text-decoration: underline;
}

/* ── Page-to-page view transitions (motion & polish spec §2) ──────────────
   Turbo 8 reads the layout's <meta name="view-transition"> and wraps Drive
   renders in document.startViewTransition where the API exists; browsers
   without it (Firefox) navigate exactly as before. The default UA cross-fade
   is ~250ms; the spec calls for a quicker 150ms fade. The sticky chrome gets
   its own named group so the header band holds still while the page content
   fades under it. */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: var(--motion-quick);
  animation-timing-function: var(--ease-out);
}

.header {
  view-transition-name: x-header;
}

.app-chrome {
  view-transition-name: x-chrome;
}

::view-transition-old(x-header),
::view-transition-new(x-header),
::view-transition-old(x-chrome),
::view-transition-new(x-chrome) {
  animation: none;
}

/* ── The house entrance grammar (motion & polish spec §5/§8) ──────────────
   One rise and one fade, defined once (keyframes are global to the document,
   whichever sheet declares them). Surfaces opt in per-component; the stagger
   idiom is 40ms per sibling, capped at the sixth. Reduced motion pins these
   via the global block below. */
@keyframes motion-rise {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes motion-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Overlay cards settle rather than slide: a touch of scale with the rise. */
@keyframes motion-settle {
  from {
    opacity: 0;
    transform: translateY(10px) scale(0.985);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

/* The third entrance: a rise with a 2px blur that clears (2026-08-17). The
   blur is what makes content read as focusing INTO place rather than
   flicking on, and it is the difference between an entrance that looks
   animated and one that looks intended. Shorter travel than motion-rise,
   because the blur is doing part of the work.

   Filtering is the expensive property here, so this is for ONE arriving
   surface at a time — a dialog, a popover, a revealed panel. Long lists keep
   motion-rise. */
@keyframes motion-focus {
  from {
    opacity: 0;
    transform: translateY(6px);
    filter: blur(2px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
    filter: blur(0);
  }
}

/* ── Overlays arrive; they do not appear ──────────────────────────────────
   Every modal <dialog> in the app (the assessment forms, the share picker,
   the interview shell) gets the blur entrance, and its backdrop fades under
   it rather than snapping to black. One element each, so the filter is
   cheap. Components that already animate their own panel keep doing so —
   this is the floor, not an override. */
dialog[open] {
  animation: motion-focus var(--motion-enter) var(--ease-smooth) both;
}

dialog[open]::backdrop {
  animation: motion-fade var(--motion-enter) var(--ease-smooth) both;
}

/* ── Working: the label itself is the spinner ─────────────────────────────
   Buttons that carry data-turbo-submits-with already swap their words while
   in flight ("Synthesizing…", "Drafting…", "Sending…"). Turbo disables the
   submitter for exactly that window, which is the hook: a slow light sweeps
   across the word and back, about every two seconds. Calm and alive rather
   than flashy, and no spinner competing with the words for attention.

   The sweep is a pseudo-element rather than a gradient clipped to the text,
   so nothing has to restate its own foreground colour. It is drawn in
   currentColor, which is the one value guaranteed to contrast with the
   button it sits on: white on a filled teal button, teal on a ghost one. A
   fixed white would have been invisible on half the buttons in the app, and
   fixed dark on the other half. */
button[data-turbo-submits-with]:disabled,
input[type="submit"][data-turbo-submits-with]:disabled {
  position: relative;
  overflow: hidden;
}

button[data-turbo-submits-with]:disabled::after,
input[type="submit"][data-turbo-submits-with]:disabled::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    100deg,
    transparent 35%,
    color-mix(in srgb, currentColor 20%, transparent) 50%,
    transparent 65%
  );
  transform: translateX(-100%);
  animation: label-sweep 2s var(--ease-in-out) infinite;
  pointer-events: none;
}

@keyframes label-sweep {
  to { transform: translateX(100%); }
}

/* ── Numbers hold their column (motion & polish spec §7) ──────────────────
   Every count, meter and position readout in the app renders with
   tabular figures so a value ticking over never makes the line shiver. */
.hub-next-count,
.hub-chapter-card-state,
.hub-tile-count,
.hub-disclosure-count,
.journey-line-position,
.journey-node-state,
.journey-done-count,
.guide-rail-step-count,
.guide-chapter-count,
.modules-tray-count,
.step-areas-count,
.step-plan-count,
.char-counter,
.step-week-total,
.step-gap-total {
  font-variant-numeric: tabular-nums;
}

/* ── Press: every control answers the finger ──────────────────────────────
   0.98, not 0.9 — a firm press, not a collapse. Applied broadly rather than
   per component so a new button is tactile by default instead of by someone
   remembering. Anything that opts out can set its own :active transform.

   :active only, and transform only, so this costs nothing on hover and never
   moves layout. Links are excluded: a pressed <a> that shrinks reads as a
   button, and most of ours are text in a sentence. */
button:not(:disabled):active,
[role="button"]:not([aria-disabled="true"]):active,
summary:active,
input[type="submit"]:not(:disabled):active,
.btn:not(:disabled):not(.disabled):active {
  transform: scale(0.98);
}

button,
[role="button"],
summary,
input[type="submit"] {
  transition: transform var(--motion-quick) var(--ease-smooth);
}

/* Bootstrap's own .btn transition is restated rather than replaced: this file
   loads after the CDN bundle, so a bare `transition: transform` here would
   drop its colour and shadow fades. Same four properties, moved onto the
   house curve, plus transform. */
.btn {
  transition:
    color var(--transition-fast),
    background-color var(--transition-fast),
    border-color var(--transition-fast),
    box-shadow var(--transition-fast),
    transform var(--motion-quick) var(--ease-smooth);
}

/* ── Reveal height without the max-height hack ────────────────────────────
   grid-template-rows 0fr → 1fr animates to the content's REAL height. A
   max-height guess either clips tall content or spends most of the duration
   travelling through empty space, which is what makes those reveals feel
   mistimed. The child must own the overflow. */
.reveal {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--motion-enter) var(--ease-smooth);
}

.reveal[data-open="true"] { grid-template-rows: 1fr; }
.reveal > * { overflow: hidden; }

/* ── Reduced motion: the one global override (motion & polish spec) ───────
   All non-essential motion collapses here, at the token layer, rather than
   per component. Durations go to 1ms instead of 0 so transitionend /
   animationend still fire for any JS that waits on them; animations are
   pinned to a single run so nothing loops. Scroll stays instant (it already
   is — see the :root rule above) and view transitions drop their cross-fade. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --motion-quick: 1ms;
    --motion-enter: 1ms;
    --motion-ceremony: 1ms;
  }

  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
  }

  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none !important;
  }
}
