/*
 * Jolvex primitives — registered primitive classes for the public surface
 * (design-system.json `primitiveSources`). Every value resolves to a token
 * from tokens.css. The two documented exceptions (frozen-viewport media
 * conditions and the contract's own 2.75rem minimum target) are called out in
 * comments where they appear.
 *
 * Primitives: Container, Section, Stack, Cluster, Grid, Split, Surface, Badge,
 * Button, IconButton, Divider, SignalRule, Disclosure, NavLink, MenuButton,
 * LocaleControl, ThemeControl, Text roles, a11y utilities.
 */

/* ---------- a11y utilities ---------- */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: var(--space-4);
  left: var(--space-4);
  z-index: 100;
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem; /* contract minimumTargetPx: 44 */
  padding: var(--space-2) var(--space-4);
  background: var(--color-surface-raised);
  color: var(--color-text);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  line-height: var(--leading-label);
  text-decoration: none;
  box-shadow: var(--shadow-raised);
  transform: translateY(calc(-100% - var(--space-8)));
}

.skip-link:focus-visible {
  transform: none;
}

/*
 * Keyboard focus indicator: the frozen M11-B2 ring language — a 3px outside
 * ring (the frozen 0.1875rem geometry) offset 2px from the control, painted
 * with the solid frozen focus token. --color-focus holds >=3:1 against every
 * adjacent light surface (and its dark remap #8FC9FF against every dark
 * surface); the alpha --shadow-focus token blends to ~1.7:1 on light
 * surfaces and must never paint focus. Outline (not box-shadow) keeps the
 * ring above sibling backgrounds and layout-free.
 */
:focus-visible {
  outline: 0.1875rem solid var(--color-focus);
  outline-offset: 2px;
}

/* ---------- Container ---------- */

.container {
  width: 100%;
  max-width: var(--size-container-content);
  margin-inline: auto;
  padding-inline: var(--space-4); /* gutter 320 */
}

.container.container-shell {
  max-width: var(--size-container-shell);
}

.container.container-narrow {
  max-width: var(--size-container-narrow);
}

@media (min-width: 390px) {
  .container { padding-inline: var(--space-5); } /* gutter 390 */
}

@media (min-width: 768px) {
  .container { padding-inline: var(--space-6); } /* gutter 768 */
}

@media (min-width: 1024px) {
  .container { padding-inline: var(--space-6); } /* gutter 1024 */
}

@media (min-width: 1200px) {
  .container { padding-inline: var(--space-8); } /* gutter 1200/1440 */
}

/* ---------- Section ---------- */

.section {
  padding-block: var(--space-10);
}

.section.section-editorial {
  padding-block: var(--space-12);
}

.section.section-compact {
  padding-block: var(--space-8);
}

@media (min-width: 1200px) {
  .section { padding-block: var(--space-12); }
  .section.section-editorial { padding-block: var(--space-16); }
}

/* ---------- Stack ---------- */

.stack { display: flex; flex-direction: column; }
.stack > * { margin-block: 0; }

.stack.stack-gap-1 > * + * { margin-block-start: var(--space-1); }
.stack.stack-gap-2 > * + * { margin-block-start: var(--space-2); }
.stack.stack-gap-3 > * + * { margin-block-start: var(--space-3); }
.stack.stack-gap-4 > * + * { margin-block-start: var(--space-4); }
.stack.stack-gap-5 > * + * { margin-block-start: var(--space-5); }
.stack.stack-gap-6 > * + * { margin-block-start: var(--space-6); }
.stack.stack-gap-8 > * + * { margin-block-start: var(--space-8); }
.stack.stack-gap-10 > * + * { margin-block-start: var(--space-10); }
.stack.stack-gap-12 > * + * { margin-block-start: var(--space-12); }

/* ---------- Cluster ---------- */

.cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
}

.cluster.cluster-gap-1 { gap: var(--space-1); }
.cluster.cluster-gap-2 { gap: var(--space-2); }
.cluster.cluster-gap-3 { gap: var(--space-3); }
.cluster.cluster-gap-4 { gap: var(--space-4); }
.cluster.cluster-gap-5 { gap: var(--space-5); }
.cluster.cluster-gap-6 { gap: var(--space-6); }

.cluster.cluster-align-start { align-items: flex-start; }
.cluster.cluster-align-end { align-items: flex-end; }
.cluster.cluster-nowrap { flex-wrap: nowrap; }

/* ---------- Alignment utilities ---------- */

/* Badges that open a raised surface keep their intrinsic pill width
   instead of stretching to the stack's full inline size. */
.self-start { align-self: flex-start; }

/* ---------- Grid ---------- */

.grid { display: grid; gap: var(--space-4); }
.grid.grid-gap-3 { gap: var(--space-3); }
.grid.grid-gap-4 { gap: var(--space-4); }
.grid.grid-gap-5 { gap: var(--space-5); }
.grid.grid-gap-6 { gap: var(--space-6); }
.grid.grid-gap-8 { gap: var(--space-8); }

.grid.grid-cols-1 { grid-template-columns: 1fr; }

@media (min-width: 768px) {
  .grid.grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
  /* frozen hierarchy: the four-category coverage grid is 2 columns at 768/1024 */
  .grid.grid-cols-4 { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 1200px) {
  .grid.grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
  .grid.grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
}

/* ---------- Split ---------- */

.split { display: grid; gap: var(--space-6); }

@media (min-width: 768px) {
  .split.split-content-visual {
    grid-template-columns: minmax(0, 58fr) minmax(0, 42fr);
    align-items: center;
  }
  .split.split-equal { grid-template-columns: repeat(2, 1fr); }
}

/* ---------- Surface ---------- */

.surface {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.surface-plain {
  background: transparent;
  border: 0;
  border-radius: 0;
}

.surface-subtle {
  background: var(--color-bg-subtle);
  border-color: var(--color-border);
}

.surface-raised {
  background: var(--color-surface-raised);
  border-color: var(--color-border);
  box-shadow: var(--shadow-raised);
}

.surface-selected {
  background: var(--color-accent-soft);
  border-color: var(--color-border-strong);
}

/* ---------- Text roles ---------- */

.text-display {
  font-family: var(--font-display);
  font-size: var(--text-display);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-display);
}

.text-heading-lg {
  font-family: var(--font-display);
  font-size: var(--text-heading-lg);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-heading-lg);
}

.text-heading {
  font-family: var(--font-body);
  font-size: var(--text-heading);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-heading);
}

.text-heading-md {
  font-family: var(--font-body);
  font-size: var(--text-heading-md);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-heading);
}

.text-heading-sm {
  font-family: var(--font-body);
  font-size: var(--text-heading-sm);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-heading);
}

.text-heading-xs {
  font-family: var(--font-body);
  font-size: var(--text-heading-xs);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-heading);
}

.text-body-lg {
  font-size: var(--text-body-lg);
  font-weight: var(--weight-regular);
  line-height: var(--leading-body-lg);
}

.text-body {
  font-size: var(--text-body);
  font-weight: var(--weight-regular);
  line-height: var(--leading-body);
}

.text-label {
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  line-height: var(--leading-label);
}

.text-caption {
  font-size: var(--text-caption);
  font-weight: var(--weight-regular);
  line-height: var(--leading-caption);
}

/* Responsive floor (2026-09-06 audit): the frozen 3rem heading-lg value
   inverts against the 2.625rem display floor on narrow viewports, so the
   compact band steps the token down on the reference clamp curve. The
   frozen token value in tokens.css is untouched. */
@media (max-width: 48rem) {
  :root {
    --text-heading-lg: clamp(2rem, 5vw, 3rem);
  }
}

.text-mono {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  line-height: var(--leading-mono);
  font-variant-ligatures: none;
}

.text-muted { color: var(--color-text-muted); }
/* Accent TEXT tone (DS-002/RP-023): light uses the registered accent-text
   pair (--color-accent-text, 5.85:1 on --color-bg); dark resolves the dark
   accent (4.68:1 on --color-dark-bg) via the registered dark pair. Brand
   coral #E4572E itself is block/signal-only and never small text. */
.text-accent { color: var(--color-accent-text); }
[data-theme="dark"] .text-accent { color: var(--color-accent); }
.text-success { color: var(--color-success); }
.text-warning { color: var(--color-warning); }
.text-danger { color: var(--color-danger); }
.text-info { color: var(--color-info); }

.measure-display-zh { max-width: var(--size-measure-display-zh); }
.measure-display-en { max-width: var(--size-measure-display-en); }
.measure-body { max-width: var(--size-measure-body); }

/* paragraph-level reset so stacks control rhythm */
.text-block { margin: 0; }

/* semantic list reset for layout lists */
.list-plain {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* ---------- Badge ---------- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-border-strong);
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  line-height: var(--leading-label);
  color: var(--color-text);
  background: var(--color-surface);
}

.badge-sm {
  padding: 0 var(--space-2);
  font-size: var(--text-caption);
}

.badge-accent {
  background: var(--color-action-accent);
  border-color: var(--color-action-accent);
  /* DS-002/RP-023 (reconciled pair): the filled accent badge is a small
     filled tag, so it uses the darker action token with the white on-accent
     ink. Frozen pairing: --color-accent-ink on --color-action-accent is
     4.53:1 in light and via the dark remap in dark. */
  color: var(--color-accent-ink);
}

[data-theme="dark"] .badge-accent {
  color: var(--color-dark-accent-ink);
}

/* Semantic variants (2026-09-04 density revision): soft fill with solid
   semantic text — the fill is the shape, so the border stays transparent.
   Registered pairs: light softs passed with their semantic text; dark softs
   are the frozen --color-dark-*-soft tints (7.2–8.6:1). */
.badge-success { background: var(--color-success-soft); border-color: transparent; color: var(--color-success); }
.badge-warning { background: var(--color-warning-soft); border-color: transparent; color: var(--color-warning); }
.badge-danger { background: var(--color-danger-soft); border-color: transparent; color: var(--color-danger); }
.badge-info { background: var(--color-info-soft); border-color: transparent; color: var(--color-info); }

/* ---------- motion (frozen variants; honoured once, never looping) ---------- */

.motion-content-enter {
  animation: content-enter var(--duration-base) var(--ease-enter) both;
}

@keyframes content-enter {
  from { opacity: 0; transform: translateY(var(--space-2)); }
  to { opacity: 1; transform: none; }
}

/* DS-008 press-enter: the one signature entry — a press card settles into
 * place once, delayed past first paint; never loops. */
.motion-press-enter {
  animation: press-enter 380ms var(--ease-standard) 150ms both;
}

@keyframes press-enter {
  from { opacity: 0; transform: translateY(var(--space-2)) scale(0.985); }
  to { opacity: 1; transform: none; }
}

/* Scroll-reveal entry (owner 2026-09-14): a progressive enhancement on the
 * frozen content-enter variant. The server ships every [data-motion-reveal]
 * region fully visible; the client (scripts/client.ts) arms the pending
 * state only when IntersectionObserver exists and the visitor has not asked
 * for reduced motion, then swaps to the run state once as the region enters
 * the viewport — children stagger on the fast duration. Missing JS, a
 * failed observer or reduced motion all leave the final composition
 * visible (DS-008: nothing starts hidden waiting on a script). */
.motion-reveal-pending,
.motion-reveal-pending > * { opacity: 0; }

[data-motion-reveal].motion-reveal-run,
[data-motion-reveal].motion-reveal-run > * {
  animation: content-enter var(--duration-base) var(--ease-enter) both;
}

[data-motion-reveal].motion-reveal-run > *:nth-child(2) { animation-delay: calc(var(--duration-fast) * 0.66); }

[data-motion-reveal].motion-reveal-run > *:nth-child(3) { animation-delay: calc(var(--duration-fast) * 1.33); }

[data-motion-reveal].motion-reveal-run > *:nth-child(4) { animation-delay: calc(var(--duration-fast) * 2); }

@media (prefers-reduced-motion: reduce) {
  .motion-content-enter { animation: none; }
  .motion-press-enter { animation: none; }

  .motion-reveal-pending,
  .motion-reveal-pending > * { opacity: 1; }

  [data-motion-reveal].motion-reveal-run,
  [data-motion-reveal].motion-reveal-run > * { animation: none; }

  .disclosure-icon,
  .skip-link,
  .btn {
    transition: none;
  }
}
