/* FateTips V3 — base: reset, typography, the 402px column + desktop device frame,
   ::selection, focus, page transitions. Values come from tokens.css only. */

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  background: var(--paper);
  scrollbar-width: none;            /* 家規 3: no native scrollbars anywhere */
  color-scheme: light;
}
::-webkit-scrollbar { display: none; width: 0; height: 0; }
* { scrollbar-width: none; }

body {
  min-height: 100svh;
  background-color: var(--paper);
  /* xuan-paper fibre: a faint inline noise tile, scrolls with the page (no fixed layer) */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .08 0 0 0 0 .09 0 0 0 0 .07 0 0 0 .05 0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--fs-body);
  line-height: 1.6;
  font-kerning: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  overflow-x: clip;
  isolation: isolate;               /* own stacking context: .ft-bg (z -1) paints above the paper, below content */
}
html.ft-locked { overflow: hidden; }            /* sheet scroll lock (released on every close) */
html.ft-locked body { overscroll-behavior: none; }

img, svg, video, canvas { display: block; max-width: 100%; }
svg { flex-shrink: 0; }
button, input, textarea { font: inherit; color: inherit; letter-spacing: inherit; }
button { background: none; border: 0; padding: 0; cursor: pointer; text-align: inherit; }
a { color: inherit; text-decoration: none; }
input, textarea { font-size: max(16px, 1em); }  /* ≥16px: no iOS focus zoom */

/* ── Typography ───────────────────────────────────────────────────────────── */
h1, h2, h3, .t-display-xl, .t-display-l, .t-display-m {
  font-family: var(--font-display);
  font-weight: 300;                  /* L 2026-09-26: light type; hierarchy from size, space, ink tone */
  line-height: 1.22;
  letter-spacing: .005em;
  text-wrap: balance;
}
h1, .t-display-xl { font-size: var(--fs-display-xl); line-height: 1.16; }
h2, .t-display-l { font-size: var(--fs-display-l); }
h3, .t-display-m { font-size: var(--fs-display-m); }
.t-title { font-family: var(--font-ui); font-size: var(--fs-title); font-weight: 400; line-height: 1.35; letter-spacing: -.005em; }
.t-sub { font-size: var(--fs-sub); font-weight: 400; line-height: 1.45; color: var(--ink-2); }   /* under a title, ≤2 lines */
.t-label { font-size: var(--fs-label); font-weight: 400; line-height: 1.4; color: var(--ink-2); }  /* group / field label */
.t-body { font-size: var(--fs-body); line-height: 1.7; }
.t-small { font-size: var(--fs-small); line-height: 1.6; }
.t-caption { font-size: var(--fs-caption); line-height: 1.5; color: var(--ink-2); }
.t-micro {                               /* instrument label */
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  line-height: 1.4;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.serif { font-family: var(--font-display); }
.mono, .num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; letter-spacing: 0; }
.muted { color: var(--ink-2); }
/* Line endings (L 2026-09-27:「we never only have 1 or two words on the next line」). Zero specificity, so any
   component rule overrides; longhand text-wrap-style, so nowrap / pre-line keep their wrap mode. Running text inherits
   pretty (WebKit's pretty lifts any short last line, Chrome's only a one-word one); short text balances. */
body { text-wrap-style: pretty; }
:where(h4, h5, h6, li, dt, dd, th, td, label, legend, figcaption, caption, button, summary,
       .t-title, .t-sub, .t-label, .t-caption, .t-micro) { text-wrap-style: balance; }
p { text-wrap: pretty; }
:lang(en) h1, :lang(en) .t-display-xl { letter-spacing: -.005em; }

/* ── Column & page ────────────────────────────────────────────────────────── */
.page {
  padding: calc(var(--sat) + 8px) var(--gutter) var(--nav-clear);
  min-height: 100svh;
}
.page--bare { padding-bottom: calc(var(--sab) + 24px); }   /* pages without the nav */
.stack > * + * { margin-top: var(--space-4); }
.stack-lg > * + * { margin-top: var(--space-6); }
.sr-only {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ── Shared background (No-cards round, L 2026-09-26): ONE static, out-of-focus grayscale ink painting
   per page; everything else sits on it. chrome.js injects <div class="ft-bg"> as body's first child on
   every app page (onboarding keeps its own painting stage). iOS rules (knowledge/mobile-ios + 家規 1/10):
   sticky, never fixed (a viewport-edge fixed layer is the Safari 26 sampling shape); 100svh, never lvh;
   the bottom dissolves to transparent, so the large-viewport strip under it is plain paper (no seam);
   no backdrop-filter, no parallax, never moves; lives in the column (body is the 402px column ≥600px).
   The painting is ink with alpha ≤ .17, so --ink-2 text stays ≥ 4.7:1 anywhere over it.
   Per-page composition: <body data-bg="…"> presets (space-separated) or the --bg-* properties. */
.ft-bg {
  --bg-h: calc(100svh - var(--frame-top) - var(--frame-bottom));
  position: sticky;
  top: var(--frame-top);
  z-index: -1;
  display: block;
  height: var(--bg-h);
  margin-bottom: calc(-1 * var(--bg-h));
  overflow: hidden;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(#000 76%, transparent);
  mask-image: linear-gradient(#000 76%, transparent);
}
.ft-bg::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url('../assets/bg/pine-mist.webp') var(--bg-x, 100%) var(--bg-y, 0%) / var(--bg-size, cover) no-repeat;
  transform: scaleX(var(--bg-flip, 1));
  opacity: var(--bg-opacity, 1);
}
body[data-bg~="left"] { --bg-flip: -1; }                                  /* branch enters from the left */
body[data-bg~="near"] { --bg-size: auto 138%; --bg-y: 38%; }              /* closer, larger, softer shapes */
body[data-bg~="high"] { --bg-size: auto 124%; --bg-y: 100%; }             /* painting rides higher, rock enters right */
body[data-bg~="low"]  { --bg-size: auto 100%; --bg-y: 24svh; }            /* top quarter left as bare paper */
body[data-bg~="faint"] { --bg-opacity: .55; }                            /* busy data pages */
body[data-bg~="none"] .ft-bg { display: none; }
@media (prefers-contrast: more) { .ft-bg { display: none; } }

/* Ink surface — retired as a slab (No-cards round): inside .surface-ink / .card--ink the ink tokens are
   re-scoped so a legacy dark hero renders as ink-on-paper content on the shared background. Page CSS
   that paints var(--ink-surface) / var(--on-ink*) keeps working and simply reads light. */
.surface-ink, .card--ink {
  --ink-surface: transparent;
  --ink-surface-2: transparent;
  --on-ink: var(--ink);
  --on-ink-2: var(--ink-2);
  --on-ink-line: var(--line);
  color: var(--ink);
}
.surface-ink .muted, .surface-ink .t-caption, .surface-ink .t-micro,
.card--ink .muted, .card--ink .t-caption, .card--ink .t-micro { color: var(--ink-2); }

/* ── ::selection — two variants, contrast computed in tokens.css ──────────── */
::selection { background: var(--jade); color: var(--paper-bright); }
/* light-text variant (the few remaining ink objects, e.g. the ink action pill) */
.btn--primary::selection, .btn--primary ::selection, .toast::selection { background: var(--jade-soft); color: var(--ink); }

/* ── Focus ────────────────────────────────────────────────────────────────── */
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--jade); outline-offset: 3px; }
/* Focus never lands under the sticky top bar or the fixed tab bar (WCAG 2.4.11). Scoped to the
   focused element, not html scroll-padding: that would add to pages' own scroll-margin section jumps. */
:focus { scroll-margin-top: calc(var(--sat) + 8px); }
:where(html:has(.topbar)) :focus { scroll-margin-top: calc(var(--sat) + 60px); }
:where(html:has(> body[data-tab])) :focus { scroll-margin-bottom: var(--nav-clear); }
.sheet-ov :focus, .devbar :focus { scroll-margin: 0; }   /* own scroller / fixed layer */

/* ── Desktop: the app column becomes a device on a deeper paper backdrop ──── */
.statusbar { display: none; }
@media (min-width: 600px) {
  :root { --frame-top: 24px; --frame-bottom: 24px; --status-h: 46px; }
  html {
    background: var(--backdrop)
      radial-gradient(120% 80% at 50% 0%, rgba(255, 255, 255, .45), transparent 60%) fixed;
  }
  body {
    max-width: var(--col);
    margin-inline: auto;
    padding-top: var(--frame-top);
    padding-bottom: var(--frame-bottom);
  }
  /* Rounded device window: a pointer-less mask whose spread paints the backdrop over any
     content that scrolls outside the rounded rect. Not a backdrop-filter layer. */
  body::after {
    content: "";
    position: fixed;
    z-index: var(--z-frame);
    top: var(--frame-top);
    bottom: var(--frame-bottom);
    left: 0;
    right: 0;
    width: var(--col);
    margin-inline: auto;
    border-radius: 48px;
    pointer-events: none;
    box-shadow:
      0 0 0 1px rgba(21, 23, 19, .1),
      0 0 0 7px rgba(248, 246, 239, .55),
      0 0 0 8px rgba(21, 23, 19, .08),
      0 40px 80px -30px rgba(21, 23, 19, .38),
      0 0 0 200vmax var(--backdrop);
  }
  .statusbar {
    display: flex;
    position: fixed;
    z-index: calc(var(--z-frame) - 1);
    top: var(--frame-top);
    left: 0;
    right: 0;
    width: var(--col);
    margin-inline: auto;
    height: var(--status-h);
    align-items: center;
    justify-content: space-between;
    padding: 6px 30px 0 34px;
    pointer-events: none;
    font-family: var(--font-ui);
    font-size: 15px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
    background: linear-gradient(var(--paper) 62%, rgba(243, 240, 231, 0));
  }
  .statusbar svg { height: 12px; width: auto; }
  .statusbar__icons { display: flex; gap: 6px; align-items: center; margin-right: 88px; }
  body[data-surface="ink"] .statusbar { color: var(--on-ink); background: linear-gradient(var(--ink-surface) 62%, rgba(21, 23, 19, 0)); }
}

/* ── Route change: 360ms cross-fade with a soft blur bridge (MPA view transitions).
   The nav is NOT given its own name: it rides the root snapshot, so it stays frosted.
   Opt-in: every page ALSO needs <style>@view-transition{navigation:auto}</style> inline, first in
   <head>. On a tap, Chrome reveals the new page before this linked rule is applied, skips the fade
   and throws "ViewTransition opt-in disabled" (qa/shell/vt-variants.cjs). This copy covers back/forward. ── */
@view-transition { navigation: auto; }
::view-transition-old(root) { animation: ft-route-out var(--dur-route) var(--ease-out) both; }
::view-transition-new(root) { animation: ft-route-in var(--dur-route) var(--ease-out) both; }
@keyframes ft-route-out { to { opacity: 0; filter: blur(3px); } }
@keyframes ft-route-in { from { opacity: 0; filter: blur(3px); } }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
  *, *::before, *::after { scroll-behavior: auto !important; }
}
