/* Garrampa ERP — hoja compartida de los prototipos de Pedidos, Mis proyectos
   y FileList (ficheros). Tokens y componentes porteados 1:1 de
   apps/gafe/src/index.css y de las piezas reales del toolkit (RowCard, Badge
   fórmula H, FilterChip, PageHeader, Pagination, ListPage) — no del wireframe
   viejo, que quedó desfasado. */

@import url("https://fonts.googleapis.com/css2?family=Archivo:wght@500;600;700&family=Inter:wght@400;500;600&display=swap");

:root {
	--font-ui: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
	--font-display: "Archivo", var(--font-ui);

	--brand-900: #00394d;
	--brand-800: #005472;
	--brand-700: #006e95;
	--brand-600: #0b83ad;
	--brand-500: #1e9cc8;
	--brand-100: #d5eaf3;
	--brand-050: #eaf4f9;

	--canvas: #f2f5f7;
	--surface: #ffffff;
	--surface-2: #f7f9fb;
	--surface-3: #eef2f5;
	--line: #dde4ea;
	--line-strong: #c6d1da;
	--ink: #131c24;
	--ink-2: #3d4d5c;
	--faded: #5c7080;
	--faint: #78899a;

	--ok-bg: #e2f3ec;
	--ok-fg: #0f6b52;
	--warn-bg: #fbeedd;
	--warn-fg: #96490b;
	--danger-bg: #fbe6e4;
	--danger-fg: #a52a20;
	--info-bg: var(--brand-050);
	--info-fg: var(--brand-800);
	--neutral-bg: #e9eef2;
	--neutral-fg: #4a5c6b;

	--s1: #006e95;
	--s2: #b4530a;
	--s3: #8673d9;
	--s4: #019f68;

	--elev-1: 0 1px 2px rgba(16, 32, 45, 0.06);
	--elev-2: 0 4px 16px -4px rgba(16, 32, 45, 0.12);
	--elev-3: 0 18px 48px -12px rgba(16, 32, 45, 0.28);

	--sidebar-w: 248px;
	--topbar-h: 56px;
	color-scheme: light;
}

[data-theme="dark"] {
	--brand-100: #10394b;
	--brand-050: #0d2c3a;
	--canvas: #0d141a;
	--surface: #151e26;
	--surface-2: #1a242d;
	--surface-3: #202c36;
	--line: #27333e;
	--line-strong: #384856;
	--ink: #e7edf2;
	--ink-2: #bccada;
	--faded: #8fa2b3;
	--faint: #6c8093;

	--ok-bg: #0f2f26;
	--ok-fg: #6fd2ae;
	--warn-bg: #33240f;
	--warn-fg: #e2a765;
	--danger-bg: #3a1d1a;
	--danger-fg: #ef9a90;
	--info-bg: #0d2c3a;
	--info-fg: #7cc9e6;
	--neutral-bg: #212d37;
	--neutral-fg: #9fb1c1;

	--s1: #1e9cc8;
	--s2: #c06325;
	--s3: #8f5fc0;
	--s4: #318f5a;

	--elev-1: 0 1px 2px rgba(0, 0, 0, 0.4);
	--elev-2: 0 4px 16px -4px rgba(0, 0, 0, 0.5);
	--elev-3: 0 18px 48px -12px rgba(0, 0, 0, 0.7);
	color-scheme: dark;
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
	margin: 0;
	font-family: var(--font-ui);
	font-size: 13px;
	line-height: 1.5;
	color: var(--ink);
	background: var(--canvas);
	font-feature-settings: "cv05" 1;
}
h1, h2, h3, h4, h5, h6, p, dl, dd, dt { margin: 0; }
button { font: inherit; color: inherit; }
a { color: inherit; }
:focus-visible { outline: 2px solid var(--brand-600); outline-offset: 2px; border-radius: 3px; }
.tabular { font-variant-numeric: tabular-nums; }
.tabular-code { font-variant-numeric: tabular-nums slashed-zero; }
.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;
}
/* Every inline icon in these prototypes is a stroked line icon by default —
   only the brand mark (a solid glyph) opts back into a fill. */
svg { fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.brand-mark svg { stroke: none; }
svg.icon { width: 15px; height: 15px; flex-shrink: 0; }
svg.icon-sm { width: 13px; height: 13px; }

/* ---------- Shell ---------- */
/* `height`, not `min-height`: the whole point of `.page-body-scroll` having
   `overflow-y: auto` is to be the thing that scrolls, not the window. With
   `min-height` the shell was free to grow past 100vh, so that overflow never
   had anywhere to clip to and the browser scrolled the document instead —
   and every `position: sticky` inside `.page-body-scroll` computes against
   *it*, so with it never scrolling, none of them ever moved. */
.shell { display: grid; grid-template-columns: var(--sidebar-w) 1fr; height: 100vh; }

.sidebar {
	position: sticky; top: 0; height: 100vh;
	display: flex; flex-direction: column;
	background: var(--surface); border-right: 1px solid var(--line);
	overflow: hidden;
}
.sidebar-brand {
	height: var(--topbar-h); flex: none; display: flex; align-items: center; gap: 10px;
	padding: 0 14px; border-bottom: 1px solid var(--line);
}
.brand-mark {
	width: 30px; height: 30px; border-radius: 7px; background: var(--brand-800);
	display: grid; place-items: center; flex-shrink: 0;
}
.brand-mark svg { width: 12px; height: 22px; }
.sidebar-brand b { font-family: var(--font-display); font-size: 14px; font-weight: 600; letter-spacing: -0.01em; }

.sidebar-nav { flex: 1; overflow-y: auto; padding: 10px 8px; display: flex; flex-direction: column; gap: 14px; }
.nav-group-label {
	font-family: var(--font-display); font-size: 11px; font-weight: 600; letter-spacing: 0.09em;
	text-transform: uppercase; color: var(--faint); padding: 0 8px; margin-bottom: 2px;
}
.nav-group { display: flex; flex-direction: column; gap: 1px; }
.nav-item {
	display: flex; align-items: center; gap: 9px; padding: 7px 10px; border-radius: 5px;
	font-size: 13px; color: var(--ink-2); text-decoration: none; cursor: pointer; border: none; background: none; text-align: left; width: 100%;
}
.nav-item:hover { background: var(--surface-3); color: var(--ink); }
.nav-item.active { background: var(--brand-050); color: var(--brand-800); font-weight: 500; }
[data-theme="dark"] .nav-item.active { color: var(--brand-500); }
.nav-item svg { width: 16px; height: 16px; flex-shrink: 0; }
.nav-children { display: flex; flex-direction: column; gap: 1px; margin-left: 19px; padding-left: 11px; border-left: 1px solid var(--line); }
.nav-children .nav-item { padding: 6px 10px; font-size: 12.5px; }

.sidebar-foot { flex: none; display: flex; align-items: center; gap: 6px; padding: 8px; border-top: 1px solid var(--line); }
.icon-btn {
	display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px;
	border-radius: 8px; border: none; background: transparent; color: var(--faded); cursor: pointer;
}
.icon-btn:hover { background: var(--surface-3); color: var(--ink); }

.main { display: flex; flex-direction: column; height: 100vh; min-width: 0; }

.topbar {
	position: sticky; top: 0; z-index: 20; height: var(--topbar-h); flex: none;
	display: flex; align-items: center; gap: 12px; padding: 0 16px;
	border-bottom: 1px solid var(--line);
	background: color-mix(in srgb, var(--surface) 86%, transparent);
	backdrop-filter: blur(10px);
}
.breadcrumbs { display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--faded); min-width: 0; }
.breadcrumbs b { color: var(--ink); font-weight: 500; }
.breadcrumbs .sep { color: var(--faint); }
.topbar-search {
	margin-left: 12px; display: flex; align-items: center; gap: 7px; height: 34px;
	width: min(360px, 34vw); padding: 0 10px; border-radius: 8px; border: 1px solid var(--line);
	background: var(--surface-2); color: var(--faded); font-size: 12.5px;
}
.topbar-search svg { width: 14px; height: 14px; flex-shrink: 0; }
.topbar-search kbd {
	margin-left: auto; font-family: var(--font-ui); font-size: 10.5px; color: var(--faint);
	border: 1px solid var(--line); border-radius: 4px; padding: 1px 5px; background: var(--surface);
}
.topbar-user { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.topbar-user .name { font-size: 12.5px; font-weight: 500; }

.page-body-scroll { flex: 1; overflow-y: auto; }

/* ---------- Page header (PageHeader.tsx) ---------- */
.page-header { position: sticky; top: 0; z-index: 10; flex: none; border-bottom: 1px solid var(--line); background: var(--surface); }
.page-header-inner {
	max-width: 1152px; margin: 0 auto; padding: 16px 24px;
	display: flex; align-items: center; gap: 12px;
}
.page-header h1 {
	font-family: var(--font-display); font-size: 24px; font-weight: 600; letter-spacing: -0.01em; color: var(--ink);
}
.page-header .page-subtitle { margin-top: 4px; font-size: 12px; color: var(--faded); }
.page-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-shrink: 0; }

.page-body {
	max-width: 1152px; margin: 0 auto; padding: 20px 24px 56px;
	display: flex; flex-direction: column; gap: 20px;
}

/* ---------- Buttons ---------- */
.btn {
	display: inline-flex; align-items: center; gap: 7px; height: 34px; padding: 0 13px;
	border-radius: 8px; font-size: 13px; font-weight: 500; cursor: pointer; border: 1px solid var(--line);
	background: var(--surface); color: var(--ink); box-shadow: var(--elev-1);
	transition: background-color .12s, border-color .12s;
}
.btn:hover { background: var(--surface-3); }
.btn-primary { background: var(--brand-700); border-color: var(--brand-700); color: #fff; }
.btn-primary:hover { background: var(--brand-800); border-color: var(--brand-800); }
.btn-ghost { border-color: transparent; background: transparent; color: var(--faded); box-shadow: none; }
.btn-ghost:hover { background: var(--surface-3); color: var(--ink); }
.btn-danger { background: var(--danger-bg); border-color: color-mix(in srgb, var(--danger-fg) 35%, transparent); color: var(--danger-fg); }
.btn-danger:hover { background: color-mix(in srgb, var(--danger-bg) 60%, var(--danger-fg) 18%); }
.btn svg { width: 15px; height: 15px; }

/* ---------- Filter bar / chips (filter-chip.tsx) ---------- */
.filter-bar { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.filter-chips { display: flex; flex: 1; flex-wrap: wrap; align-items: center; gap: 8px; min-width: 0; }
.chip {
	display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 10px;
	border-radius: 8px; font-size: 12px; cursor: pointer; white-space: nowrap;
}
.chip-empty { border: 1px dashed var(--line-strong); background: var(--surface); color: var(--faded); }
.chip-empty:hover { border-color: var(--brand-600); color: var(--ink-2); }
.chip-empty .plus { color: var(--faint); }
.chip-applied { border: 1px solid var(--brand-700); background: var(--brand-050); color: var(--brand-800); font-weight: 500; }
[data-theme="dark"] .chip-applied { color: var(--brand-500); }
.chip-toolbar { border: 1px solid var(--line); background: var(--surface); color: var(--ink-2); }
.chip-toolbar:hover { background: var(--surface-3); }
.filter-clear { height: 30px; padding: 0 6px; background: none; border: none; color: var(--faded); font-size: 12px; cursor: pointer; }
.filter-clear:hover { color: var(--ink); }
.filter-sort { display: flex; align-items: center; gap: 8px; padding-left: 8px; flex-shrink: 0; }
.filter-sort .rule { width: 1px; height: 20px; background: var(--line); }

/* ---------- Row list ---------- */
.rows { display: flex; flex-direction: column; gap: 8px; }
.row-card {
	container-type: inline-size;
	display: flex; flex-wrap: wrap; align-items: center; column-gap: 14px; row-gap: 8px;
	width: 100%; border-radius: 12px; border: 1px solid var(--line); background: var(--surface);
	padding: 12px 14px; text-align: left; cursor: pointer; box-shadow: var(--elev-1);
	transition: box-shadow .14s, border-color .14s; text-decoration: none; color: inherit;
}
.row-card:hover { border-color: var(--line-strong); box-shadow: var(--elev-2); }
.row-card:active { transform: translateY(0.5px); }
.row-lead {
	width: 40px; height: 40px; border-radius: 8px; flex-shrink: 0;
	display: grid; place-items: center; background: var(--surface-3); color: var(--faded);
}
.row-lead svg { width: 18px; height: 18px; }
.row-lead.tone-ok { background: var(--ok-bg); color: var(--ok-fg); }
.row-lead.tone-warn { background: var(--warn-bg); color: var(--warn-fg); }
.row-lead.tone-danger { background: var(--danger-bg); color: var(--danger-fg); }
.row-main { display: flex; flex: 1; min-width: 0; flex-direction: column; gap: 2px; }
@container (min-width: 42rem) { .row-main { min-width: 224px; } }
.row-title { display: flex; min-width: 0; align-items: center; gap: 8px; }
.row-title .name { font-family: var(--font-display); font-size: 14px; font-weight: 600; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* RowCard reads its own width, not the viewport (`.row-card` is the
   `@container`): below 42rem of *row* width the subtitle stacks one facet
   per line; at or above it, one row, no separator glyph, each Facet just a
   gap apart. */
.row-sub { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; font-size: 12px; color: var(--faded); }
@container (min-width: 42rem) { .row-sub { flex-direction: row; align-items: center; gap: 16px; flex-wrap: wrap; row-gap: 4px; } }
.facet { display: inline-flex; align-items: center; gap: 5px; min-width: 0; max-width: 100%; }
.facet svg { width: 13px; height: 13px; color: var(--faint); flex-shrink: 0; }
.facet .v { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Same container, `stackTail`: below 42rem the tail drops to its own
   full-width line with a border-top divider; at or above it, one inline
   group pinned right, no divider — a tail with several values (badge +
   amount, or more) always earns this, never just a lone chevron. */
.row-tail { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; width: 100%; margin-top: 2px; padding-top: 8px; border-top: 1px solid var(--line); }
@container (min-width: 42rem) { .row-tail { flex-direction: row; flex-shrink: 0; align-items: center; gap: 10px; width: auto; margin-top: 0; margin-left: auto; padding-top: 0; border-top: none; } }
/* Bare amount, not bold — matches BudgetsList.tsx's own row value exactly:
   `w-24 text-right tabular-nums text-body text-ink-2`. Fixed width so every
   amount in the list ends at the same right edge, comparable at a glance. */
.row-value { display: inline-block; min-width: 96px; text-align: right; font-size: 13px; font-weight: 400; color: var(--ink-2); }
/* Figure.tsx's own perUnit line: text-micro text-faint, right under the value —
   never shown on its own, only as the second storey of an amount that has one. */
.row-value-unit { display: block; margin-top: 1px; font-size: 11px; font-weight: 400; color: var(--faint); }
/* Cobrado and Facturado used to share one glyph and rely on the tooltip
   alone to say which was which — invisible without a hover. Tried two
   distinct stroked icons next (a euro curve, a document fold) at 14, then
   16px; neither read clean at either size — thin multi-stroke line art is
   the wrong tool this small, the same lesson a stat number learns before
   a bar chart does. Text instead, same monogram idiom `.group-key`
   already proved legible at 22px in Mis proyectos: a letter in a circle,
   color carrying on/off, no stroke-width fight to have. */
.flag { display: inline-flex; align-items: center; gap: 4px; }
/* On/off used to be a solid-vs-dashed circle — unmistakable regardless of
   what it was a circle *of*. Swapping to color-only (green border vs gray
   border) lost that: a 1.5px stroke changing hue at 18px doesn't carry the
   same weight a filled shape does, so the thing this fix was supposed to
   keep (state, legible at a glance) got quietly worse while fixing the
   other thing (which flag it is). Filled vs outline brings that back —
   solid fill reads as "on" the same way a checked checkbox or a filled
   toggle does, no color vision needed to tell them apart, and the letter
   still says which flag it is either way. */
.flag-mark { display: grid; place-items: center; width: 18px; height: 18px; flex-shrink: 0; border-radius: 999px; border: 1.5px solid var(--line-strong); font-family: var(--font-display); font-size: 10px; font-weight: 700; color: var(--faded); }
.flag.on .flag-mark { border-color: var(--ok-fg); background: var(--ok-fg); color: var(--surface); }

/* ---------- Badge (badge.tsx, fórmula H) ---------- */
.badge {
	display: inline-flex; align-items: center; gap: 5px; height: 21px; padding: 0 8px;
	border-radius: 999px; font-size: 11px; white-space: nowrap; flex-shrink: 0;
	border: 1px solid var(--line); background: var(--surface-2); color: var(--ink-2);
}
.badge .dot { width: 6px; height: 6px; border-radius: 999px; flex-shrink: 0; }
.badge-neutral .dot { background: var(--faint); }
.badge-ok { } .badge-ok .dot { background: var(--ok-fg); }
.badge-warn { } .badge-warn .dot { background: var(--warn-fg); }
.badge-info { } .badge-info .dot { background: var(--info-fg); }
.badge-danger { } .badge-danger .dot { background: var(--danger-fg); }
.badge-alarm { border-color: color-mix(in srgb, var(--danger-fg) 25%, transparent); background: var(--danger-bg); color: var(--danger-fg); }
.badge-code { font-variant-numeric: tabular-nums slashed-zero; }

/* ---------- Avatar ---------- */
.avatar {
	display: grid; place-items: center; border-radius: 999px; background: var(--brand-100);
	color: var(--brand-800); font-family: var(--font-display); font-weight: 600; letter-spacing: 0.02em;
	flex-shrink: 0; overflow: hidden;
}
[data-theme="dark"] .avatar { color: var(--brand-500); }

/* ---------- Pagination ---------- */
.pagination { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.pagination .count { font-size: 12px; color: var(--faded); }
.pagination .pager { display: flex; align-items: center; gap: 4px; }
.pagination .pager span { padding: 0 8px; font-size: 12px; color: var(--faded); }
.pagination .pg-btn {
	display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px;
	border-radius: 8px; border: 1px solid var(--line); background: var(--surface); color: var(--ink-2); cursor: pointer;
}
.pagination .pg-btn:hover { background: var(--surface-3); }
.pagination .pg-btn:disabled { opacity: .5; cursor: default; }
.pagination .pg-btn svg { width: 15px; height: 15px; }

/* ---------- SectionedLayout / Section (section-nav.tsx) ---------- */
/* The real ficha grammar: a sticky label nav plus a flowing column of sections
   divided by hairlines. No card ever wraps a field group — a card is for a
   distinct object (a note, a row), never for "a box of fields", which is the
   mat-expansion-panel reflex this replaces. */
.sectioned { display: flex; gap: 40px; align-items: flex-start; }
/* SectionNav is `hidden md:flex` in the real component — below md there is no
   substitute nav, the section headings inside the flow are the only wayfinding. */
.section-nav { display: none; position: sticky; top: 76px; width: 148px; flex-shrink: 0; flex-direction: column; gap: 4px; }
@media (min-width: 768px) { .section-nav { display: flex; } }
.section-nav a { padding: 6px 8px; border-radius: 5px; font-size: 12px; color: var(--faded); text-decoration: none; }
.section-nav a:hover { color: var(--ink); }
.section-nav a.active { color: var(--ink); font-weight: 500; }
.sectioned-body { flex: 1; min-width: 0; }
.ficha-section { padding: 32px 0; border-top: 1px solid var(--line); }
.ficha-section:first-child { padding-top: 0; border-top: none; }
.ficha-section h3 { font-family: var(--font-display); font-size: 14px; font-weight: 600; margin-bottom: 16px; }
.ficha-section .section-hint { margin-top: -12px; margin-bottom: 16px; font-size: 12px; color: var(--faded); }

.field-grid { display: grid; grid-template-columns: 1fr; gap: 16px 24px; }
@media (min-width: 768px) { .field-grid { grid-template-columns: 1fr 1fr; } }
.field-grid .field.span-2 { grid-column: 1 / -1; }

/* A named group of fields inside one Section — same idiom as .accordion-block:
   a label, then a border only between groups, never before the first. */
.detail-group { margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--line); }
.detail-group .section-title { margin-bottom: 12px; }
/* The totals that close a list of lines — Artículos in `pedido.html`,
   Líneas in `mi-proyecto.html` — same job in both sibling fichas, one
   shared recipe. Started life as two: a bare `.section-title` + `.ticket`
   with no heading here, and a separate `.project-totals` over there with
   one, drifting apart in every number (20/16/1px vs 32/20/2px) for no
   reason either page could name. "No heading needed, the row labels
   already say what it is" held for pedido on its own — until it sat next
   to a page where the same block *did* need one (Mis proyectos repeats
   "Datos económicos de la línea" once per article, so the project's own
   total needs to say it isn't a fourth one). Two fichas showing the same
   pattern differently isn't two decisions, it's one made twice by
   accident — so both take the heading and the heavier numbers now,
   whether or not either page's totals ever collide with a repeated label
   on their own. A rule above it, heavier than the hairlines everywhere
   else, is the receipt convention this ticket shape already borrows from
   — the total line at the bottom gets a rule, not a shaded box (tried
   `--surface-2` here first; wrong call — see `.badge`'s own note on that
   token already meaning "generic dimmed panel" in six other places). */
.lines-totals { margin-top: 32px; padding-top: 20px; border-top: 2px solid var(--line-strong); }
.lines-totals-heading { font-family: var(--font-display); font-size: 14px; font-weight: 600; color: var(--ink); margin-bottom: 14px; }
/* `.ticket-row.ticket-grand-total` carries its own rule and extra
   margin/padding above "Total" — the right call standing alone, the wrong
   one here: this block already opens with a heavier rule, so the ticket's
   own border landed a few rows down doing the same job again, and the
   margin+padding that came with it broke the uniform 7px rhythm
   `.ticket-row.derived` gave up elsewhere in this same file. Total's own
   weight (bold, the bigger of the two type sizes in the ticket) already
   carries it without either. */
.lines-totals .ticket-row.ticket-grand-total { margin-top: 0; padding-top: 0; border-top: none; }

/* ---------- Cards / sections ---------- */
.card { border-radius: 12px; border: 1px solid var(--line); background: var(--surface); box-shadow: var(--elev-1); }
.card-head { padding: 13px 16px; border-bottom: 1px solid var(--line); display: flex; align-items: center; gap: 8px; }
.card-head h3 { font-family: var(--font-display); font-size: 14px; font-weight: 600; }
.card-body { padding: 16px; }
.card-foot { padding: 10px 16px; border-top: 1px solid var(--line); background: var(--surface-2); font-size: 12px; color: var(--faded); border-radius: 0 0 12px 12px; }
.card-flush .card-body { padding: 0; }

.grid-2 { display: grid; grid-template-columns: 1fr; gap: 16px; }
@media (min-width: 768px) { .grid-2 { grid-template-columns: 1fr 1fr; } }

.field { display: flex; flex-direction: column; gap: 3px; }
.field .l { font-size: 12px; font-weight: 500; color: var(--faded); }
.field .v { font-size: 13px; color: var(--ink); line-height: 1.5; }
.field .v.empty { color: var(--faded); }
/* Field's own real hint slot (field.tsx: text-micro text-faded under the
   value) — the same trailing note CustomerFields.tsx already uses for a
   contact's address summary. Reused here for delivery/payment terms, so nothing
   about this field is invented: label, value, hint are the three real parts
   of the primitive. */
.field .h { margin-top: 5px; font-size: 11px; color: var(--faded); }
/* A field that carries far more than its field-grid neighbours (Envío,
   Facturación next to one-line Cliente/Agente gestor) gets extra room before
   it, on top of the grid's own gap — so the jump in density reads as
   deliberate breathing room, not a layout that forgot to notice it. */
.field.dense { margin-top: 14px; }
.deflist { display: grid; grid-template-columns: 1fr; gap: 14px 20px; }
@media (min-width: 768px) { .deflist { grid-template-columns: 1fr 1fr; } }

.section-title {
	font-family: var(--font-display); font-size: 11px; font-weight: 600; letter-spacing: 0.09em;
	text-transform: uppercase; color: var(--faint); margin-bottom: 10px;
}

/* ---------- Group report (líneas de proyecto) ----------
   Comercial asked for no accordions anywhere — and the previous shape (a
   rounded, bordered, shadowed box with a head row above a revealed body)
   read as one whether or not it actually collapsed. This drops the box
   vocabulary for both a printing group and its articles: no card, no
   tinted zone, no rounded corners anywhere in this section. What's left is
   plain typographic hierarchy — headings, fields, hairlines — the same
   grammar "Detalles" above already uses with no box either. A group and its
   articles read as sections of one document, not as a stack of components
   that might open. */
.group-report + .group-report { margin-top: 32px; padding-top: 32px; border-top: 1px solid var(--line); }
/* Sticky, like `.topbar` (top: 0) and `.section-nav` (top: 76px) already are
   in this file — which group you're inside stays legible the whole time you
   scroll through its lines. `top: 67px`, not 0 — `.page-header` (67px tall)
   is sticky too, pinned at `top: 0` in the same scroll container, so
   `top: 0` here would fight it for the same spot instead of stacking
   underneath it. */
/* Went through a quiet text label, then a compact pill, before landing
   here — both lost to a straight comparison against GarrampaNet's real
   `_info_printing_group.php`: however plain the rest of that screen is, a
   full-width, solidly-colored band per group is why its hierarchy reads
   instantly and nothing here so far did. Can't borrow its actual teal —
   that color is reserved for Garrampa's identity, not spent on UI chrome
   (`CLAUDE.md`, "El teal no se toca") — so this fills with `--ink` instead:
   the one neutral in the whole palette that never appears as a fill
   anywhere else on this page, which is exactly what guarantees nothing
   below it — carded or not, photographed or not — ever outweighs it again. */
.group-heading { position: sticky; top: 67px; z-index: 5; display: flex; align-items: center; flex-wrap: wrap; gap: 10px; padding: 10px 16px; border-radius: 10px; background: var(--ink); color: var(--surface); }
.group-key { display: grid; place-items: center; width: 22px; height: 22px; flex-shrink: 0; border-radius: 999px; background: var(--surface); color: var(--ink); font-size: 11px; font-weight: 700; }
.group-marking { font-family: var(--font-display); font-size: 13px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--surface); }
.group-count { margin-left: auto; flex-shrink: 0; font-size: 11px; color: color-mix(in srgb, var(--surface) 65%, transparent); }
/* The group's own actions ("Volver tarea anterior", "Cancelar") used to
   sit inside `.group-facts`, at the end of the provider/date/task row —
   readable as belonging to "Tarea actual" specifically, or to the row in
   general, never clearly to the group itself. `.page-header` already
   answers this the same way one level up: its own `⋮` sits in the page's
   own title bar, not buried in a field below it. Moved here to match.
   `.menu`/`.menu-popup` become `<span>` in this one spot only — `<h4>`'s
   content model doesn't allow `<div>`, and everywhere else those two stay
   `<div>` because nothing forces otherwise. */
.group-heading .menu { margin-left: 4px; }
.group-heading .menu-trigger { color: var(--surface); }
.group-heading .menu-trigger:hover { background: color-mix(in srgb, var(--surface) 18%, transparent); color: var(--surface); }
/* No indent now — the filled band is a strong enough break on its own that
   a second signal saying the same thing was just spending width nothing
   needed spent. Flush-left content under a solidly-colored group header,
   until the next one, is the same reading a grouped table in Notion or
   Linear already relies on with zero indent: the header is loud enough
   that "everything until the next one is its group" doesn't need restating
   in the geometry too. */
.group-facts { margin-top: 14px; }
.group-marking-detail { margin-top: 16px; }
/* "Artículos" is a plain label, same idiom as `.section-title` — naming
   where the group's own facts end and its articles begin without drawing a
   seam to say so. Its own `margin-bottom` (not the more usual gap on what
   follows) is what puts air before the first card: the label's inline
   `<span class="section-title">` carries `style="margin:0"` precisely so
   this div's margin is the only source, not two half-measures adding up
   to the wrong number. Left at 0 outright before this pass — a label
   sitting flush against a bordered, shadowed card read as touching it,
   not introducing it. */
.group-articles-label { margin-top: 20px; margin-bottom: 12px; }

/* An article gets `.row-card`'s own skin back — radius, border, surface,
   `--elev-1` — so it visually belongs to the same family as the other 51
   listados in this app, not to a different, unrelated typographic system.
   What it deliberately does NOT copy is `.row-card`'s behavior: no
   `cursor: pointer`, no hover/active states. Someone resting a pointer over
   this gets no feedback at all, which is itself a signal — a real RowCard
   answers a hover, this doesn't, because it isn't one. Padding is
   `.note-card`'s own `14px 16px`, not a round 16 on every side — the other
   card this ficha shows (Histórico) already set that recipe, and two
   different padding scales on two cards in the same page was exactly the
   kind of thing worth noticing here. Radius and shadow stay RowCard's, on
   purpose: the two things this card needs to match aren't the same thing
   in both directions, and it doesn't have to pick one identity over the
   other to satisfy each on its own axis. */
.line-report { border-radius: 12px; border: 1px solid var(--line); background: var(--surface); box-shadow: var(--elev-1); padding: 14px 16px; }
.line-report + .line-report { margin-top: 12px; }
/* The photo is back (it was never the accordion tell — a small *bordered
   square icon*, the UI-affordance shape for "this row does something", was;
   see the note on `.line-report-block` above). A rounded photo with a soft
   shadow and no border reads as a picture of the product, the same way
   `.file-row`'s icon never gets mistaken for a button despite sitting at
   the start of a line — no crisp edge for the eye to register as a control.
   Sized up from the old 40px icon slot to 44px so it unmistakably reads as
   an image, not a glyph. */
.line-report-heading { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.line-report-thumb { width: 44px; height: 44px; border-radius: 10px; background: var(--surface-3); box-shadow: var(--elev-1); flex-shrink: 0; display: grid; place-items: center; color: var(--faded); overflow: hidden; }
.line-report-thumb svg { width: 20px; height: 20px; }
/* Now that the card itself carries `.row-card`'s skin, the one thing left
   to avoid is `.row-card`'s own *layout* — name flush left, value pushed to
   the far right with `margin-left: auto`. That specific "title … value"
   spread, on a bordered card, is RowCard's exact silhouette; keeping it
   here would have undone the point of changing anything. The price moves
   under the name instead, quiet and left-aligned, so nothing about this
   card completes a sentence with "click to open." */
.line-report-caption { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.line-report-title { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.line-report-heading .name { font-family: var(--font-display); font-size: 14px; font-weight: 600; color: var(--ink); }
.line-report-price { font-size: 12px; color: var(--ink-2); }
.line-report-price .unit { margin-left: 4px; color: var(--faint); }
.line-report-block { padding-top: 14px; }
.line-report-block + .line-report-block { border-top: 1px solid var(--line); margin-top: 14px; }
/* 20px/28px borrowed the spacing this file reserves for *distinct*
   things (`.group-meta-row`'s three unrelated facts use the same 20px) —
   wrong scale for five numbers that are one breakdown of one quantity,
   not five separate facts. At that gap each size read like its own
   floating stat with nothing tying it to its neighbors; tightened to
   read as one row, one distribution, five cells of it. */
.figure-row { display: flex; flex-wrap: wrap; gap: 10px 18px; }
.figure { display: flex; flex-direction: column; gap: 2px; }
.figure .l { font-size: 11px; color: var(--faded); }
.figure .v { font-size: 14px; font-weight: 600; color: var(--ink); }
.figure .v.strong { font-family: var(--font-display); }

/* Ticket shape: Figure.tsx's own narrow-tier layout (TicketLabel — label, a
   dotted leader, the value), used here as the normal shape for a breakdown of
   amounts read top to bottom, not side by side. */
.ticket { display: flex; flex-direction: column; gap: 7px; }
.ticket-row { display: flex; align-items: baseline; gap: 8px; }
.ticket-row .l { flex-shrink: 0; font-size: 12px; color: var(--faded); white-space: nowrap; }
.ticket-row .dots { flex: 1; min-width: 12px; align-self: baseline; border-bottom: 1px dotted var(--line-strong); }
.ticket-row .v { flex-shrink: 0; font-size: 13px; color: var(--ink-2); white-space: nowrap; }
/* Figure.tsx's own `tone="strong"` — font-medium text-ink, no size bump, no
   font-display — is what a row's *own* total earns; `border-line-strong` is
   reserved (verified against ArticlesGrid.tsx:144) for the one footer that
   sums every row above it, never repeated per row. A per-line ticket like
   this one is that inner row, not the footer, so it takes the quieter pair:
   plain `--line`, medium weight. Repeated once per product down a long page,
   the loud version would out-shout the section titles above it. */
/* No divider: weight + ink (against every other row's --faded label) already
   says "this is the total" — a line on top of that repeated once per line
   item, down a long page, was signal competing with itself. */
.ticket-row.ticket-total .l { color: var(--ink); font-weight: 500; font-size: 12px; }
.ticket-row.ticket-total .v { color: var(--ink); font-weight: 500; font-size: 13px; }
/* The one real footer per page (Figure.tsx's `tone="footerTotal"`: text-strong
   font-semibold, still Inter — not font-display, that part of the old comment
   didn't check out either). Used once: the order total in `pedido.html`, the
   project total in `mi-proyecto.html`. */
.ticket-row.ticket-grand-total { margin-top: 2px; padding-top: 8px; border-top: 1px solid var(--line-strong); }
.ticket-row.ticket-grand-total .l { color: var(--ink); font-weight: 600; font-size: 13px; }
.ticket-row.ticket-grand-total .v { color: var(--ink); font-weight: 600; font-size: 14px; }
/* Used to carry its own extra `margin-top` (10px against the ticket's own
   7px gap) as the one thing marking Coste and Beneficio as sums of what's
   above them rather than fresh input lines — no color or weight change,
   spacing alone. Too quiet to read as a signal at all: it looked like an
   uneven ticket, not an intentional grouping, in both the per-line and the
   project ticket. Dropped — every row in a `.ticket` keeps the same 7px
   rhythm now, and Coste/Beneficio's relationship to the rows around them
   reads from order and label, the same way any receipt states a subtotal
   without needing a bigger gap to prove it's one. */
.ticket-row.derived { margin-top: 0; }
/* Figure.tsx's real tone logic: profit < 0 is 'negative' (danger), never a
   fixed green — a margin can be a loss, and the color has to say so. */
.ticket-row .v.positive { color: var(--ok-fg); }
.ticket-row .v.negative { color: var(--danger-fg); }

/* No <table> anywhere here on purpose — DESIGN.md §"Do's and Don'ts" bans it
   outside the two named exceptions (CostsFields/FaresFields), and a line item
   is not one of them. Pantone swatches and cost/marking entries are rows. */
.pantone { display: inline-flex; align-items: center; gap: 5px; }
.pantone .sw { width: 12px; height: 12px; border-radius: 3px; box-shadow: inset 0 0 0 1px rgba(0,0,0,.16); }
/* A single literal space in the markup was the only thing separating two
   consecutive chips — 3-4px, less than the 5px `.pantone` already spends
   *inside* itself between swatch and label. A marking with three colors
   read as one run-on cluster, tighter between chips than within one. This
   wrapper gives chip-to-chip more room than swatch-to-label gets, which is
   the order that should hold. */
.pantone-list { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 10px; }

/* One row per marking position, same idiom as ActivityFields.tsx's
   .activity-item (fixed first column, hairline-divided, stacks on mobile).
   Started as pedido's own, simpler shape (QuoteDetailItemCustomizationViewModel:
   position/technique/inks/colors/file, no notes fields); a project's
   PrintingMethods carries more (a separate Pos. descripción, size, an
   optional customer note) but earns the same row, not a heavier box — see
   `.group-markings` above for why the boxed version this replaced was
   dropped project-wide. */
.marking-list { margin-top: 2px; }
.marking-row { display: flex; flex-direction: column; gap: 3px; padding: 8px 0; border-bottom: 1px solid var(--line); }
.marking-row:last-child { border-bottom: none; }
@media (min-width: 768px) { .marking-row { flex-direction: row; align-items: baseline; gap: 14px; } }
.marking-row .position { flex-shrink: 0; font-size: 12px; font-weight: 600; color: var(--ink); }
@media (min-width: 768px) { .marking-row .position { width: 84px; } }
/* Same fix as `.purchase-row` just above, same reason: `.detail` used to
   be one run of text (technique · position · size · colors) that wrapped
   wherever it happened to run out of width — one marking breaking after
   "Centrado en" and its sibling breaking after "12 ×", so a list of two or
   three markings never lined up the same way twice. Split into
   `.detail-line` per fact so mobile always breaks at the same, predictable
   points — one fact per line — and desktop still reads as a single flowing
   sentence, dot-separated, the same as before. */
.marking-row .detail { display: flex; flex-direction: column; flex: 1; gap: 2px; font-size: 12px; color: var(--faded); }
.marking-row .detail b { font-weight: 600; color: var(--ink); }
@media (min-width: 768px) {
	.marking-row .detail { flex-direction: row; flex-wrap: wrap; align-items: baseline; row-gap: 2px; }
	.marking-row .detail-line + .detail-line::before { content: " · "; color: var(--faint); }
}
/* "Ver arte" used to be a bare text link, then a 32px thumbnail squeezed
   into the end of the text row — better than the link, still cramped:
   a photo fighting for space against position/technique/color text in one
   dense line never had room to read as an actual preview. The real fix is
   the one already sitting right there in this same ficha — `.file-grid`
   of real `.file-tile`s, `.file-tile-name` holding the position instead of
   a filename, underneath `.marking-list` rather than squeezed into it.
   Same component FileList already is everywhere else (Muestra virtual,
   fichas), not a smaller one-off version of it. */

/* ActivityFields.tsx, the budgets ficha's own Actividad section, verbatim: no
   icon — just when / what / who, hairline-divided, date column fixed-width on
   desktop. Read-only end to end, newest first. */
.activity-item { display: flex; flex-direction: column; gap: 2px; padding: 10px 0; border-bottom: 1px solid var(--line); }
.activity-item:last-child { border-bottom: none; }
@media (min-width: 768px) { .activity-item { flex-direction: row; align-items: baseline; gap: 16px; } }
.activity-item .when { flex-shrink: 0; font-size: 12px; color: var(--faded); }
@media (min-width: 768px) { .activity-item .when { width: 128px; } }
.activity-item .action { flex: 1; font-size: 13px; color: var(--ink); }
.activity-item .agent { flex-shrink: 0; font-size: 12px; color: var(--faded); }

.empty-note { padding: 10px 0; font-size: 12px; color: var(--faded); }

/* FileList.tsx's own stored-file row: a paperclip, the name, a note
   underneath. No remove button — this context is read-only, same as
   FileList with no onRemove passed. The whole row is the link now, not
   just the name — `<a class="file-row">`, same idiom `.row-card` already
   uses for "the whole thing opens something": before this, only the name
   text itself was clickable, leaving the icon, the note, and all the
   padding around them as dead space that looked exactly as clickable as
   the part that worked. Hover/active match `.row-card`'s own, so a file
   that opens something feels like it does. */
.file-row { display: flex; align-items: center; gap: 12px; border-radius: 12px; border: 1px solid var(--line); background: var(--surface); padding: 12px 14px; box-shadow: var(--elev-1); text-decoration: none; color: inherit; cursor: pointer; transition: box-shadow .14s, border-color .14s; }
.file-row:hover { border-color: var(--line-strong); box-shadow: var(--elev-2); }
.file-row:active { transform: translateY(0.5px); }
.file-row svg { width: 16px; height: 16px; flex-shrink: 0; color: var(--faint); }
.file-row .meta { display: flex; min-width: 0; flex: 1; flex-direction: column; gap: 1px; }
.file-row .name { font-size: 13px; color: var(--ink); }
.file-row .note { font-size: 12px; color: var(--faded); }
/* An optional trailing slot for a file's own status (Muestra virtual's
   Aprobada/Rechazada/Por confirmar, on the sample files in this ficha's
   articles) — not every file has one, so it's a slot, not a required part
   of the row. `margin-left: auto` pushes it to the far edge regardless of
   how long the name is. */
.file-row-status { margin-left: auto; }

/* ---------- FileList's second mode: previsualización ----------
   The row is right for a document collection you read (contracts,
   invoices) — name and note matter more than what the file looks like.
   It's the wrong shape for a collection that's mostly photos (product
   images, marking art): the one thing worth seeing first, the image
   itself, is exactly what a row hides. Same data, same component, a
   different arrangement — grid of tiles instead of a stack of rows.
   Fixed-size columns (132px), not `minmax(132px, 1fr)` — a line with one
   muestra virtual is the common case, and `1fr` would have let that one
   tile stretch to claim the entire row, oversized, instead of sitting at
   its natural size with the rest of the row left empty. A line with
   several (a rejected mockup and its revision, say) still wraps into a
   real grid — this only stops a single tile from ever being told to grow. */
.file-grid { display: grid; grid-template-columns: repeat(auto-fill, 132px); gap: 12px; }
/* `<a class="file-tile">`, same reasoning as `.file-row` above: the whole
   card opens the file, not just its name — a bare thumbnail with no link
   anywhere on it is the more common mistake, but "only the caption is
   clickable" has the identical dead-space problem at tile scale. */
.file-tile { display: flex; flex-direction: column; border-radius: 12px; border: 1px solid var(--line); background: var(--surface); box-shadow: var(--elev-1); overflow: hidden; text-decoration: none; color: inherit; cursor: pointer; transition: box-shadow .14s, border-color .14s; }
.file-tile:hover { border-color: var(--line-strong); box-shadow: var(--elev-2); }
.file-tile:active { transform: translateY(0.5px); }
.file-tile-preview { position: relative; aspect-ratio: 4 / 3; display: grid; place-items: center; background: var(--surface-3); color: var(--faded); overflow: hidden; }
.file-tile-preview img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* A non-image doesn't get a generic paperclip glyph here — at tile size, a
   small icon reads as empty space. The extension itself, set big, is the
   preview: "PDF" tells you more at a glance than a document icon would,
   and it's the one piece of information every non-image file actually
   has in common. */
.file-tile-ext { font-family: var(--font-display); font-size: 15px; font-weight: 700; letter-spacing: 0.03em; color: var(--faded); }
.file-tile-meta { display: flex; flex-direction: column; gap: 2px; padding: 8px 10px 10px; }
.file-tile-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: var(--ink); }
.file-tile-note { font-size: 11px; color: var(--faded); }
/* Same status slot as `.file-row-status`, but there's no trailing edge of
   a row to push to here — it sits over the corner of the preview instead,
   the same place a photo app or a file manager already puts a status
   ribbon. `.badge`'s own background is opaque (`--surface-2`, never
   transparent), so it stays legible over whatever color the preview is,
   photo or flat placeholder alike, without needing its own backdrop. */
.file-tile-status { position: absolute; top: 6px; right: 6px; }


.inline-notice { display: flex; align-items: flex-start; gap: 8px; font-size: 12px; padding: 10px 12px; border-radius: 8px; }
.inline-notice svg { width: 15px; height: 15px; flex-shrink: 0; margin-top: 1px; }
.inline-notice.info { background: none; padding-left: 0; color: var(--info-fg); }
.inline-notice.warn { background: var(--warn-bg); color: var(--warn-fg); border: 1px solid color-mix(in srgb, var(--warn-fg) 25%, transparent); }

/* ---------- Printing group meta: provider, delivery, current task + progress ----------
   Sits under the group heading — one per `printing_group` (a letter in
   Garrampa's model, not the marking technique), ported from
   `_info_printing_group.php`. Three facts, so a 3-up grid on desktop
   divides the row evenly between them instead of flex letting "Tarea
   actual" (the one with a progress bar to fit) claim whatever the other
   two don't need — Proveedor and Fecha de entrega earn the same width as
   the busiest block, not just what their own short values require. One
   column on mobile, the same collapse `.field-grid` already uses. The
   actions menu moved out of this row entirely, into `.group-heading` —
   see the note there. */
.group-meta-row { display: grid; grid-template-columns: 1fr; gap: 14px; }
@media (min-width: 768px) { .group-meta-row { grid-template-columns: repeat(3, 1fr); gap: 20px; } }
.group-meta-block { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.group-meta-block .l { font-size: 11px; font-weight: 500; color: var(--faded); }
.group-meta-block .v { font-size: 13px; color: var(--ink); }
.group-meta-block .micro { font-size: 11px; color: var(--faded); }

/* Marcaje is the group's own data (`_info_printing_group.php:238` reads it
   off a single representative line, once, before the per-product loop even
   starts at :402) — every product under this group shares the same setup,
   so it sits here, not repeated inside each product below, but plain: same
   `.marking-list`/`.marking-row` idiom `pedido.html` already uses for a
   line's own markings (position, a one-line detail, hairline-divided), not
   the old boxed `.field-grid` treatment — that was a card's worth of fields
   for what reads better as a couple of dense lines. A group can carry two
   fields `pedido.html`'s simpler version doesn't (Posición and Pos.
   descripción are separate — `CLAUDE.md`, "Lo que queda abierto de
   ventas"-adjacent finding in `MIGRACION_MIS_PROYECTOS.md` §4) and an
   optional customer note, both folded into `.detail` below rather than
   earning their own column. */
.group-markings .section-title { margin-bottom: 4px; }
.group-markings .marking-row .position { width: auto; }
@media (min-width: 768px) { .group-markings .marking-row .position { width: 132px; } }
/* Forced onto its own line inside `.detail` (not a sibling element) so it
   stays attached to the marking it belongs to instead of floating free —
   danger ink is the only color signal in this whole section, on purpose:
   everything else here is plain text, so the one line that needs to stop a
   reader is the only line that isn't. `flex-basis: 100%` is load-bearing
   now that `.detail` is a flex row on desktop: without it, a flex item
   doesn't break onto its own line just because it's `display: block` —
   that only works in normal flow, not inside a flex container. */
.marking-alert { display: block; flex-basis: 100%; margin-top: 3px; color: var(--danger-fg); font-weight: 500; }

/* Segmented bar: one segment per task, filled ones done — replaces a
   continuous bar + legend + a row of milestone icons with one line. The five
   milestone icons this displaced never actually lined up with the real task
   count (5 icons for a 10-task group); this doesn't have that mismatch, and
   it's the same discrete-block grammar the size breakdown in "Personalización"
   already uses (S/M/L/XL/XXL as separate figures, not one continuous total). */
/* No `margin-top` of its own — `.group-meta-block`'s `gap: 3px` already
   spaces it from "Producción" above; the extra 5px on top of that was
   stacking two spacing sources for one gap, 8px total against the 3px
   every other label/value pair in this block gets. */
.task-segments { display: inline-flex; align-items: center; gap: 6px; }
.task-segments .bar { display: inline-flex; gap: 2px; }
.task-segments .seg { width: 12px; height: 5px; border-radius: 2px; background: var(--surface-3); }
.task-segments .seg.done { background: var(--brand-700); }
.task-segments .count { font-size: 11px; color: var(--faded); }

/* ---------- Purchases per size (grupos comprados a proveedor) ----------
   Same domain as PurchaseController (MIGRACION_TAREAS.md etapa 3), read here
   straight off the task's own data. Went through a real `<table>` first (the
   same responsive answer CostsFields.tsx/FaresFields.tsx use —
   `overflow-x-auto` + `min-width`, scroll sideways instead of reflowing) but
   the ask was to keep that column-aligned, scan-down-a-column reading
   without a `<table>` element at all. This is a CSS grid instead: real
   aligned columns above 42rem — the same threshold RowCard's own `@container`
   already uses — and below it, each cell prints its own `data-label` via
   `::before`, so one markup serves both layouts; nothing to keep in sync
   between a "desktop" and a "mobile" version because there isn't one. */
/* Tried `.marking-row`'s shape here too — one label, the rest as a flowing
   sentence, no columns — on the theory that a table-shaped grid was only
   passing the letter of "no `<table>`", not the spirit. Reverted the same
   day: with six attributes per row, the sentence read *worse* than the
   grid, not just differently — values sat next to their own labels
   ("pedido 02/07", "previsto 10/07") instead of under a column header, so
   comparing "F. recibido" across five sizes meant re-reading each full
   sentence instead of scanning straight down. Marcaje's version of this
   pattern works because it's two or three facts, not six; Compras earns
   the grid this file otherwise avoids, the same way CostsFields/FaresFields
   already do — a named exception, not a rule broken quietly. */
.purchase-list { container-type: inline-size; display: flex; flex-direction: column; gap: 6px; }
.purchase-head { display: none; }
/* One `data-label` cell per line on mobile, not wherever `flex-wrap`
   happened to break — with `wrap` + inline cells, how many cells shared a
   line depended on how long each one's text was, so one card wrapped
   after "Uds." and the next wrapped after "F. compra", and the cards read
   ragged sitting next to each other. `flex-direction: column` gives every
   card the same, predictable one-datum-per-line shape regardless of
   content length; the grid layout above 42rem is unaffected. */
.purchase-row {
	display: flex; flex-direction: column; gap: 3px;
	border: 1px solid var(--line); border-radius: 8px; padding: 8px 12px;
	font-size: 12.5px; color: var(--ink-2);
}
.purchase-row .cell.size { font-family: var(--font-display); font-size: 13px; font-weight: 600; color: var(--ink); }
.purchase-row .cell[data-label]::before { content: attr(data-label) ": "; color: var(--faded); font-weight: 500; }

@container (min-width: 42rem) {
	.purchase-list { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; gap: 0; }
	.purchase-head,
	.purchase-row { display: grid; grid-template-columns: 2.5rem 4rem 6.5rem 5rem 1fr 5rem; align-items: center; gap: 0 16px; }
	.purchase-head { padding: 8px 12px; background: var(--surface-2); font-size: 11px; font-weight: 500; color: var(--faded); border-bottom: 1px solid var(--line); }
	.purchase-row { border: none; border-radius: 0; padding: 7px 12px; }
	.purchase-row + .purchase-row { border-top: 1px solid var(--line); }
	.purchase-row .cell[data-label]::before { content: none; }
}

/* ---------- Warn card (Reprogramar / Denegar proyecto) ---------- */
.warn-card { border-radius: 12px; border: 1px solid color-mix(in srgb, var(--danger-fg) 28%, transparent); background: var(--surface); overflow: hidden; }
.warn-card-head { display: flex; align-items: center; gap: 8px; padding: 12px 16px; background: var(--danger-bg); color: var(--danger-fg); font-family: var(--font-display); font-weight: 600; font-size: 14px; }
.warn-card-head svg { width: 16px; height: 16px; }
.warn-card-body { padding: 16px; }
.warn-card-body p { margin: 0; font-size: 13px; color: var(--ink-2); }
.warn-card-body ul { margin: 10px 0 16px; padding-left: 18px; display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--ink-2); }
.warn-card-foot { display: flex; justify-content: center; padding: 0 16px 16px; }

/* ---------- Histórico: NoteCard.tsx, ported 1:1 (not a new "timeline" look) ----------
   The real component: identity + state badges + date on one line, the text,
   a quiet tags row (departments/reminder/responsible), attachments as pill
   chips, an optional answer block, and — only on a note still pending for
   me — a bottom action row. A system entry is the same shell with
   `author: null`: the Cog-in-a-circle treatment Author() already gives it,
   just without the tags/answer/actions that don't apply to a log line. */
.note-list { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 8px; }
.note-card { display: flex; flex-direction: column; gap: 12px; border-radius: 8px; border: 1px solid var(--line); background: var(--surface); padding: 14px 16px; }
.note-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.note-author { display: flex; align-items: center; gap: 6px; }
.note-author b { font-size: 12px; font-weight: 500; color: var(--ink); }
.note-author .bot { display: grid; width: 24px; height: 24px; flex-shrink: 0; place-items: center; border-radius: 999px; background: var(--surface-3); color: var(--faded); }
.note-author .bot svg { width: 12px; height: 12px; }
.note-date { margin-left: auto; font-size: 11px; color: var(--faint); }
.note-text { text-align: left; font-size: 13px; line-height: 1.6; color: var(--ink); white-space: pre-line; }
.note-tags { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; font-size: 11px; color: var(--faded); }
.note-tags .dept { display: inline-flex; height: 20px; align-items: center; border-radius: 999px; background: var(--surface-3); padding: 0 8px; font-size: 11px; font-weight: 500; color: var(--ink-2); }
.note-attachments { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.note-attachment { display: inline-flex; max-width: 200px; align-items: center; gap: 6px; height: 22px; border-radius: 6px; border: 1px solid var(--line); background: var(--surface-2); padding: 0 8px 0 6px; font-size: 11px; color: var(--ink-2); text-decoration: none; }
.note-attachment svg { width: 12px; height: 12px; flex-shrink: 0; color: var(--faint); }
.note-attachment span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.note-answer { display: flex; flex-direction: column; gap: 4px; border-left: 2px solid var(--line-strong); border-radius: 0 6px 6px 0; background: var(--surface-2); padding: 8px 12px; }
.note-answer b { font-size: 11px; color: var(--ink-2); }
.note-answer p { margin: 0; font-size: 12px; line-height: 1.5; color: var(--ink); }
.note-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; border-top: 1px solid var(--line); padding-top: 10px; }
.note-link { font-size: 11px; font-weight: 500; color: var(--brand-700); text-decoration: none; }
.note-link:hover { text-decoration: underline; }

/* ---------- Contextual menu (dropdown-menu.tsx) ----------
   Every DropdownMenuTrigger in the real app is icon-only (⋮, size-7,
   text-faint) — never a labeled button — so three page-header actions
   collapse into one trigger instead of three `.btn`s competing for
   attention next to the title. */
.menu { position: relative; display: inline-block; }
.menu-trigger { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 6px; border: none; background: none; color: var(--faint); cursor: pointer; }
.menu-trigger:hover { background: var(--surface-3); color: var(--ink-2); }
.menu-trigger svg { width: 15px; height: 15px; fill: currentColor; stroke: none; }
.menu-popup { display: none; position: absolute; right: 0; top: calc(100% + 4px); z-index: 40; flex-direction: column; gap: 1px; width: 200px; padding: 4px; border-radius: 8px; border: 1px solid var(--line); background: var(--surface); box-shadow: var(--elev-3); }
.menu-popup.open { display: flex; }
.menu-item { display: flex; width: 100%; align-items: center; gap: 7px; padding: 7px 8px; border-radius: 6px; border: none; background: none; text-align: left; font-size: 13px; color: var(--ink-2); text-decoration: none; cursor: pointer; }
.menu-item:hover { background: var(--surface-3); color: var(--ink); }
.menu-item svg { width: 15px; height: 15px; flex-shrink: 0; color: var(--faded); }
.menu-item.destructive { color: var(--danger-fg); }
.menu-item.destructive svg { color: var(--danger-fg); }
.menu-item.destructive:hover { background: var(--danger-bg); }

/* ---------- Add-row button (add-row-button.tsx) ---------- */
.add-row-btn { display: flex; width: 100%; height: 34px; align-items: center; justify-content: center; gap: 6px; border: 1px dashed var(--line-strong); border-radius: 8px; background: none; color: var(--faded); font-size: 12px; font-weight: 500; cursor: pointer; }
.add-row-btn:hover { border-color: var(--brand-600); color: var(--ink-2); }
.add-row-btn svg { width: 14px; height: 14px; }

/* ---------- Mobile shell (<768px) ----------
   The real Sidebar (Sidebar.tsx) turns into a fixed off-canvas drawer below
   md, translated out and slid in with `drawerOpen`; Topbar.tsx shows a
   `md:hidden` menu button to open it. Breadcrumbs/GlobalSearch/UserMenu all
   shrink instead of overflowing (max-md:hidden on their own text). Ported
   here 1:1, since a 248px rail fixed in a grid column would otherwise eat
   most of a phone-width viewport. */
.menu-btn { display: none; }
.scrim { display: none; }

@media (max-width: 767.98px) {
	.shell { display: block; }

	.sidebar {
		position: fixed; inset: 0 auto 0 0; z-index: 40; width: var(--sidebar-w);
		box-shadow: var(--elev-3);
		transform: translateX(-100%);
		transition: transform .2s ease-out;
	}
	.sidebar.open { transform: translateX(0); }

	.scrim {
		display: block; position: fixed; inset: 0; z-index: 30;
		background: rgba(10, 16, 22, .32); opacity: 0; pointer-events: none;
		transition: opacity .2s ease-out;
	}
	.scrim.open { opacity: 1; pointer-events: auto; }

	.menu-btn { display: inline-flex; }

	.breadcrumbs { min-width: 0; }
	.breadcrumbs > span:not(.sep),
	.breadcrumbs > a,
	.breadcrumbs > b {
		min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
	}

	.topbar-search { width: auto; padding: 0 8px; }
	.topbar-search .label,
	.topbar-search kbd { display: none; }

	.topbar-user .name { display: none; }

	.page-header-inner { padding: 16px 14px; }
	.page-body { padding: 20px 14px 56px; }

	/* Ticket rows carry longer labels here than Figure.tsx's own column
	   headers ever do ("Sudadera Denver 320gr (Textiles Norte)" vs. "Coste") —
	   nowrap-plus-dots would overflow a phone. The leader is decorative, so it
	   goes; the label wraps and the value stays pinned to its baseline. */
	.ticket-row .l { white-space: normal; flex: 1 1 auto; min-width: 0; }
	.ticket-row .dots { display: none; }
	.ticket-row .v { flex-shrink: 0; }
}
