/* ninben components: single source (spec 2026-10-21 piezas al nivel del portal §2 O1 y §7). Edit here, then run
   python3 design-system/assets/brand/sync-brand.py --write <template>… to copy it into each piece, and
   node scripts/sync-brand.mjs in the portal to copy it into its brand/. It goes after ninben.css (colours,
   radius, fonts) and holds the scale tokens, the frame and the shared components, with the values of the
   portal's stylesheet. Mobile first; one breakpoint at 672px (the factory grid's first tier). Nothing uses a
   loose value (FUN-35), and nothing here may ask the network for anything: no imports, no external files. */

:root {
  --text-small: 14px;
  --text-body: 16px;
  --text-title: 24px;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-between-blocks: var(--space-6);

  --margin-page: 16px;
  /* The frame reaches the widest review width (1440) with the page margin (24) on each side, and centres above it
     (MAR-11). The portal uses this same frame. */
  --frame-page: 1392px;
  --measure-form: 440px;
  --measure-text: 40rem;

  --radius-pill: 999px;
  --target-min: 48px;
  --mark: 4px; /* the accent stripe of a notice */

  /* The frame contract every design system of the factory exposes (design-md-spec, adaptador-claude-design;
     MAR-08, MAR-09, MAR-11, FUN-05, FUN-37): the geometry measure reads these names. They only point at the
     tokens above, which stay the source of truth. */
  --marco-pagina: calc(var(--frame-page) + 2 * var(--margin-page));
  --marco-margen-sm: var(--space-4);
  --marco-margen-pagina: var(--margin-page);
  --medida-formulario: var(--measure-form);
  --espacio-entre-bloques: var(--space-between-blocks);
  --espacio-1: var(--space-1);
  --espacio-2: var(--space-2);
  --espacio-3: var(--space-3);
  --espacio-4: var(--space-4);
  --espacio-5: var(--space-5);
  --espacio-6: var(--space-6);
  --espacio-7: var(--space-7);
}

@media (min-width: 672px) {
  :root { --margin-page: 24px; }
}

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 400 var(--text-body)/1.5 var(--font-sans);
  overflow-wrap: break-word;
}
:focus-visible { outline: 2px solid var(--signal); outline-offset: 2px; }

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

/* Loose links: the signal means «you can touch this». */
a { color: var(--signal); text-underline-offset: 3px; }

/* ---- frame ------------------------------------------------------------------------------ */

.frame {
  width: 100%;
  max-width: var(--marco-pagina);
  margin-inline: auto;
  padding-inline: var(--margin-page);
}
/* The ninben logo: the 亻 stroke in signal, left of «ninben» in Inter 600, lowercase (brand spec §2.3).
   Tight tracking and line-height 1 keep it apart from the headings. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-body);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
}
.brand svg { flex: none; }
/* Inside the portal the host brings the frame (MAR-13): a piece's own bar goes away. */
html[data-factory-host="portal"] .bar { display: none; }

/* ---- notices ---------------------------------------------------------------------------- */

/* A notice is a sheet with a stripe: grey for information, ink for a warning, the error colour for an
   error. Never the signal, which means «you can touch this». */
.notice {
  --notice-accent: var(--muted);
  display: grid;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line);
  border-inline-start: var(--mark) solid var(--notice-accent);
  border-radius: var(--radius);
  background: var(--sheet);
}
.notice--warning { --notice-accent: var(--ink); }
.notice--error { --notice-accent: var(--error); }
.notice p { max-width: var(--measure-text); }
.notice-actions { display: flex; flex-wrap: wrap; column-gap: var(--space-5); }

/* ---- fields ----------------------------------------------------------------------------- */

.field { display: grid; gap: var(--space-2); }
label { font-weight: 600; }
.hint { color: var(--muted); font-size: var(--text-small); }
input, textarea, select {
  width: 100%;
  min-height: var(--target-min);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--muted);
  border-radius: var(--radius);
  background: var(--sheet);
  color: var(--ink);
  font: inherit;
  font-size: var(--text-body); /* 16px or more keeps iOS from zooming in */
}
input[aria-invalid="true"], textarea[aria-invalid="true"], select[aria-invalid="true"] {
  border-color: var(--error);
  box-shadow: inset 0 0 0 1px var(--error);
}
.field-error { color: var(--error); font-size: var(--text-small); font-weight: 600; }

/* ---- buttons and actions ---------------------------------------------------------------- */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--target-min);
  padding: var(--space-2) var(--space-5);
  border: 1px solid transparent;
  border-radius: var(--radius);
  font: inherit;
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
}
.button--primary { background: var(--signal); border-color: var(--signal); color: var(--on-signal); }
.button--secondary { background: transparent; color: var(--ink); border-color: var(--muted); }

.link-button {
  appearance: none;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--signal);
  font: inherit;
  text-align: start;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.secondary-actions { display: grid; justify-items: start; gap: var(--space-1); }
/* A row of actions: the first one is the primary and goes at the start; the rest follow it. */
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4); }

/* ---- rows and status -------------------------------------------------------------------- */

.rows { list-style: none; padding: 0; border-top: 1px solid var(--line); }
.row { border-bottom: 1px solid var(--line); }

.status { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-small); white-space: nowrap; }
/* The state is said in words; the dot only repeats it: hollow grey to start, ink while active, ok once done. */
.status-dot { width: 10px; height: 10px; border: 2px solid var(--dot, var(--muted)); border-radius: var(--radius-pill); background: var(--dot-fill, transparent); }
.status--active .status-dot { --dot: var(--ink); --dot-fill: var(--ink); }
.status--done .status-dot { --dot: var(--ok); --dot-fill: var(--ok); }

/* ---- pieces ------------------------------------------------------------------------------ */

/* A button that does not apply at all (while sending) looks off, never like the signal. One that is missing something
   keeps its normal look and, when touched, takes the focus to what is missing (COM-14). */
.button[disabled] { background: var(--line); border-color: var(--line); color: var(--muted); cursor: not-allowed; }

/* What the factory's HTML sheets (the questions, the flow review, the demo sheet) share and the portal does not:
   each one carries <body class="piece">, the portal's index.html does not, so none of this reaches the portal's
   own pages. :where() keeps each selector as light as without the scope, so a sheet's own rules still win. */
:where(.piece) p, :where(.piece) h1, :where(.piece) h2, :where(.piece) h3, :where(.piece) ul, :where(.piece) dl,
:where(.piece) dd { margin: 0; }
:where(.piece) h1 { font-size: var(--text-title); line-height: 1.25; font-weight: 600; letter-spacing: -0.02em; }
:where(.piece) h1:focus { outline: none; } /* the title takes the focus on each new view; it is not a control */
:where(.piece) h2, :where(.piece) h3 { font-size: var(--text-body); line-height: 1.4; font-weight: 600; }
:where(.piece) main p, :where(.piece) main dd, :where(.piece) legend { max-width: var(--measure-text); } /* about 80 characters a line (FUN-12) */
:where(.piece) main { padding-block: var(--space-6) var(--space-7); }
/* The title band: the h1 with what goes right before or after it (a step count, a saved state, a date). */
:where(.piece) .heading { display: grid; gap: var(--space-2); }
/* «Guardado» (EST-08) is not a verdict: its word alone, in the secondary text colour, with no dot and no verdict colour
   (FUN-40), like the portal's own line (.piece-status). */
:where(.piece) .saved-state { color: var(--muted); font-size: var(--text-small); white-space: nowrap; }
/* A sheet opened on its own brings its bar: the brand and which sheet this is, on the frame's edge (the markup,
   <header class="bar"><div class="frame bar-inner">…, is each sheet's). Inside the portal the rule above hides it. */
:where(.piece) .bar { border-bottom: 1px solid var(--line); background: var(--sheet); }
:where(.piece) .bar-inner { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--space-2) var(--space-4); padding-block: var(--space-3); }
:where(.piece) .where { color: var(--muted); font-size: var(--text-small); }
/* A fold (<details class="more">) with a name that says what it holds (FOR-13): a chevron in SVG, in the signal colour
   like every link, that turns when it opens; only the words are underlined, and the whole summary is a 44 px target. */
:where(.piece) .more summary { display: inline-flex; align-items: center; gap: var(--space-2); min-height: 44px; color: var(--signal);
  cursor: pointer; list-style: none; }
:where(.piece) .more summary::-webkit-details-marker { display: none; }
:where(.piece) .more summary span { text-decoration: underline; text-underline-offset: 3px; }
:where(.piece) .more summary svg { flex: none; width: var(--space-4); height: var(--space-4); }
:where(.piece) .more[open] summary svg { transform: rotate(90deg); }
/* Inside the portal the iframe is the portal's frame box: <main> takes the page margin and the frame in it no longer
   pads, so every block, the frame too, starts on the portal brand's left edge (MAR-13). Only a sheet in the portal
   carries data-factory-host. */
html[data-factory-host="portal"] main { padding-inline: var(--margin-page); }
html[data-factory-host="portal"] main > .frame { padding-inline: 0; }
