/* Salida shared components (DESIGN.md sections 4 to 7). Linked after tokens.css on every
   app page (flow, status, admin, 404). Mobile first; light, dock mode and high contrast
   all come from the tokens, so nothing here names a color, font or radius of its own.
   Interactive targets: 48 px minimum, 56 px for primary actions and flow inputs. */

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;text-size-adjust:100%}
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--font-body);font-size:var(--fs-body);line-height:var(--lh-body);overflow-wrap:break-word}
h1,h2,h3{font-family:var(--font-display);font-stretch:var(--font-display-stretch);margin:0 0 var(--space-4)}
h1{font-size:var(--fs-h1);line-height:var(--lh-h1);letter-spacing:var(--ls-h1);font-weight:700}
h2{font-size:var(--fs-h2);line-height:var(--lh-h2);font-weight:700}
h3{font-size:var(--fs-h3);line-height:var(--lh-h3);font-weight:600}
p{margin:0 0 var(--space-4);max-width:var(--measure)}
a{color:var(--brand);text-underline-offset:.2em}
a:hover{color:var(--brand-hover)}
img,svg{max-width:100%}
img{height:auto}
[hidden]{display:none!important}
:focus-visible{outline:var(--focus-w) solid var(--focus);outline-offset:var(--focus-offset)}
.mono,.num,code{font-family:var(--font-mono);font-variant-numeric:tabular-nums}
.tnum{font-variant-numeric:tabular-nums}
.meta{font-size:var(--fs-meta);color:var(--ink-2)}
.nowrap{white-space:nowrap}
@media screen{.print-only{display:none}}
.sr-only{position:absolute!important;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.skip-link{position:absolute;left:var(--space-2);top:var(--space-2);z-index:100;padding:var(--space-3) var(--space-4);background:var(--surface);color:var(--brand);border:var(--border-w) solid var(--line-strong);border-radius:var(--r-control);transform:translateY(-200%)}
.skip-link:focus{transform:none}
.icon{width:1.25em;height:1.25em;flex:none;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:-.2em}

/* Layout (section 5): 1200 px container; the flow is one 600 px column at every width. */
.container{max-width:var(--container);margin-inline:auto;padding-inline:var(--gutter)}
.column{max-width:600px;margin-inline:auto;padding-inline:var(--gutter)}
.stack>*+*{margin-top:var(--space-4)}
.page{padding-block:var(--space-6) var(--space-7)}
.section+.section{margin-top:var(--space-7);padding-top:var(--space-6);border-top:var(--border-w) solid var(--line)}
/* The status page's action card follows the ticket, which is not a .section. */
.ticket+.section{margin-top:var(--space-6)}
.cluster{display:flex;flex-wrap:wrap;gap:var(--tap-gap);align-items:center}

/* Header, footer, theme and language toggles */
.site-header{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);min-height:var(--tap-primary);padding-inline:var(--gutter);border-bottom:var(--border-w) solid var(--line);background:var(--surface)}
.wordmark{font-family:var(--font-display);font-stretch:var(--font-display-stretch);font-weight:700;font-size:var(--fs-h3);color:var(--ink);text-decoration:none;display:inline-flex;align-items:center;gap:var(--space-2);min-height:var(--tap-min)}
.site-footer{margin-top:var(--space-8);padding:var(--space-6) var(--gutter) calc(var(--space-6) + env(safe-area-inset-bottom));border-top:var(--border-w) solid var(--line);color:var(--ink-2);font-size:var(--fs-meta)}
.site-footer p{margin:0 0 var(--space-2)}
.site-footer__links{display:flex;flex-wrap:wrap;gap:0 var(--space-4);list-style:none;margin:var(--space-3) 0;padding:0}
.site-footer__links a{display:inline-flex;align-items:center;justify-content:center;min-height:var(--tap-min);min-width:var(--tap-min)} /* min-width: short links like "Privacy" measured 47 px wide */
.lang-toggle{display:inline-flex;align-items:center;min-height:var(--tap-min);padding-inline:var(--space-2);font-weight:600}
.theme-toggle[aria-pressed="true"]{border-color:var(--brand);box-shadow:inset 0 0 0 var(--border-w) var(--brand)}

/* Buttons. Act on release (native click); press feedback only, nothing loops. */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);min-height:var(--tap-min);padding:var(--space-2) var(--space-5);border:var(--border-w) solid transparent;border-radius:var(--r-control);font:600 var(--fs-label)/1.25 var(--font-body);text-align:center;text-decoration:none;cursor:pointer;touch-action:manipulation;-webkit-tap-highlight-color:transparent;transition:background-color var(--dur-hover) var(--ease-enter),border-color var(--dur-hover) var(--ease-enter),transform var(--dur-press) var(--ease-enter)}
.btn:active{transform:translateY(1px)}
.btn--primary{min-height:var(--tap-primary);background:var(--brand);color:var(--on-brand)}
.btn--primary:hover{background:var(--brand-hover);color:var(--on-brand)}
.btn--secondary{background:var(--surface);color:var(--ink);border-color:var(--line-strong)}
.btn--secondary:hover{background:var(--surface-2);color:var(--ink)}
.btn--quiet{background:transparent;color:var(--brand);text-decoration:underline;padding-inline:var(--space-2)}
.btn--danger{background:var(--surface);color:var(--danger);border-color:var(--danger)}
.btn--danger:hover{background:var(--danger-tint);color:var(--danger)}
.btn--lg{min-height:var(--tap-primary)}
.btn--block{display:flex;width:100%}
.btn:disabled,.btn[aria-disabled="true"]{opacity:.55;cursor:not-allowed;transform:none}
.btn[aria-busy="true"]{cursor:progress}
.btn-row{display:grid;gap:var(--space-3)}

/* Fields with label, hint and error (section 6.2). No asterisks; optional is written out. */
.field{display:grid;gap:var(--space-2);margin-bottom:var(--space-5)}
.field__label,.legend{font-size:var(--fs-label);font-weight:var(--fw-label);color:var(--ink)}
.field__optional{font-weight:400;color:var(--ink-2)}
.field__hint{font-size:var(--fs-meta);color:var(--ink-2);margin:0}
.field__error{display:flex;gap:var(--space-2);align-items:flex-start;font-size:var(--fs-meta);font-weight:600;color:var(--danger);margin:0}
.input,.select,.textarea{width:100%;min-height:var(--tap-min);padding:var(--space-2) var(--space-4);font:400 max(var(--fs-input),1rem)/1.4 var(--font-body);color:var(--ink);background:var(--surface);border:var(--border-w) solid var(--line-strong);border-radius:var(--r-control);touch-action:manipulation}
.textarea{min-height:calc(var(--tap-primary) * 2);padding-block:var(--space-3);resize:vertical}
.select{appearance:none;background-image:none;padding-right:var(--space-6)}
.select-wrap{position:relative}
.select-wrap::after{content:"";position:absolute;right:var(--space-4);top:50%;width:.5em;height:.5em;border:solid var(--ink-2);border-width:0 2px 2px 0;transform:translateY(-70%) rotate(45deg);pointer-events:none}
.input::placeholder,.textarea::placeholder{color:var(--ink-2)}
.input[aria-invalid="true"],.select[aria-invalid="true"],.textarea[aria-invalid="true"]{border-color:var(--danger);box-shadow:inset 0 0 0 var(--border-w) var(--danger)}
.flow .input,.flow .select{min-height:var(--tap-primary)}
fieldset{border:0;margin:0 0 var(--space-5);padding:0;min-width:0}
legend{padding:0;margin-bottom:var(--space-3)}
.check{display:flex;gap:var(--space-3);align-items:flex-start;min-height:var(--tap-min);padding-block:var(--space-2);cursor:pointer}
.check input{width:24px;height:24px;margin:0;flex:none;accent-color:var(--brand)}
.error-summary{margin-bottom:var(--space-5);padding:var(--space-4);border:calc(var(--border-w) * 2) solid var(--danger);border-radius:var(--r-card);background:var(--danger-tint)}
.error-summary h2{font-size:var(--fs-h3);color:var(--danger);margin-bottom:var(--space-2)}
.error-summary ul{margin:0;padding-left:var(--space-5)}
/* Review fix (mobile-design-14): inline-block links put the list bullet on the last line of a
   wrapped item ("Enter the 6-character REF, / • for example 7K3M2Q."). Block links, as in the
   flow's summary, keep it on the first line and still give a 48 px target. */
.error-summary a{display:block;min-height:var(--tap-min);padding-block:var(--space-2);color:var(--danger);font-weight:600}

/* Inputs styled through their face: the real radio or checkbox stays in the page,
   invisible but focusable, so keyboard and screen readers work unchanged. */
.choice__input,.chip-toggle__input,.segmented__input{position:absolute;opacity:0;width:1px;height:1px;margin:0}
.choice,.chip-toggle,.segmented__item{position:relative;display:block;cursor:pointer;-webkit-tap-highlight-color:transparent}
.choice__input:focus-visible+.choice__face,.chip-toggle__input:focus-visible+.chip-toggle__face,.segmented__input:focus-visible+.segmented__face{outline:var(--focus-w) solid var(--focus);outline-offset:var(--focus-offset)}
.choice__input:disabled+.choice__face,.chip-toggle__input:disabled+.chip-toggle__face,.segmented__input:disabled+.segmented__face{opacity:.55;cursor:not-allowed}

/* Chips: static labels and status chips; color is always paired with a word or icon. */
.chip{display:inline-flex;align-items:center;gap:var(--space-1);min-height:32px;padding:var(--space-1) var(--space-3);border:var(--border-w) solid var(--line);border-radius:var(--r-chip);background:var(--surface-2);color:var(--ink);font-size:var(--fs-meta);font-weight:600;line-height:1.3}
.chip--brand{color:var(--brand);background:var(--brand-tint);border-color:var(--brand)}
.chip--info{color:var(--info);background:var(--info-tint);border-color:var(--info)}
.chip--warn{color:var(--warn);background:var(--warn-tint);border-color:var(--warn)}
.chip--danger{color:var(--danger);background:var(--danger-tint);border-color:var(--danger)}
.chip-group{display:flex;flex-wrap:wrap;gap:var(--tap-gap)}
/* min-width (review fix accessibility-14): one-character chips such as the pickup unit
   letters and numbers measured 41 to 47 px wide at 320 px; every chip is at least 48. */
.chip-toggle__face{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-2);min-height:var(--tap-min);min-width:var(--tap-min);padding:var(--space-2) var(--space-4);border:var(--border-w) solid var(--line-strong);border-radius:var(--r-chip);background:var(--surface);color:var(--ink);font-weight:600;transition:background-color var(--dur-hover) var(--ease-enter),border-color var(--dur-hover) var(--ease-enter)}
.chip-toggle__input:checked+.chip-toggle__face{background:var(--brand-tint);border-color:var(--brand);box-shadow:inset 0 0 0 var(--border-w) var(--brand)}
/* A drawn check mark, so "selected" never depends on color alone. */
.chip-toggle__input:checked+.chip-toggle__face::before{content:"";width:.4em;height:.75em;margin-right:var(--space-1);border:solid var(--brand);border-width:0 .15em .15em 0;transform:translateY(-.1em) rotate(45deg)}

/* Choice cards: one radio each, 64 px minimum, icon before the text (section 6.2). */
.choice-list{display:grid;gap:var(--space-3)}
.choice__face{display:flex;gap:var(--space-3);align-items:center;min-height:var(--tile);padding:var(--space-3) var(--space-4);border:var(--border-w) solid var(--line-strong);border-radius:var(--r-card);background:var(--surface);color:var(--ink);transition:border-color var(--dur-hover) var(--ease-enter),background-color var(--dur-hover) var(--ease-enter)}
.choice__face::before{content:"";flex:none;width:22px;height:22px;border:2px solid var(--line-strong);border-radius:50%;background:var(--surface)}
.choice__input:checked+.choice__face{border-color:var(--brand);background:var(--brand-tint);box-shadow:inset 0 0 0 var(--border-w) var(--brand)}
.choice__input:checked+.choice__face::before{border-color:var(--brand);background:var(--brand);box-shadow:inset 0 0 0 4px var(--surface)}
.choice__text{display:grid;gap:var(--space-1)}
.choice__title{font-weight:600}
.choice__desc{font-size:var(--fs-meta);color:var(--ink-2)}
.choice__reveal{margin:var(--space-3) 0 0 var(--space-6)}

/* Segmented control: connected segments that wrap rather than overflow in Spanish. */
/* The container shows through 1 px gaps as the dividers, so wrapped rows stay ruled too. */
.segmented{display:flex;flex-wrap:wrap;gap:var(--border-w);border:var(--border-w) solid var(--line-strong);border-radius:var(--r-control);overflow:hidden;background:var(--line-strong)}
.segmented__item{flex:1 1 0;min-width:max-content}
.segmented__face{display:flex;align-items:center;justify-content:center;min-height:var(--tap-primary);padding:var(--space-2) var(--space-4);font-weight:600;color:var(--ink);background:var(--surface);transition:background-color var(--dur-hover) var(--ease-enter)}
.segmented__input:checked+.segmented__face{background:var(--brand);color:var(--on-brand)}
/* Review fix (mobile-design-17): on phones, groups of three or more wrapped unevenly (three
   segments and a full-width fourth; two and a full-width third). Under 480 px they become
   even rows: three options stack, four or more sit two per row (an odd last one spans). */
@media (max-width:479.98px){
  .segmented:has(>:nth-child(3)){display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}
  .segmented:has(>:nth-child(3)):not(:has(>:nth-child(4))){grid-template-columns:minmax(0,1fr)}
  .segmented:has(>:nth-child(5))>:last-child:nth-child(odd){grid-column:1 / -1}
  .segmented:has(>:nth-child(3))>.segmented__item{min-width:0}
  .segmented:has(>:nth-child(3)) .segmented__face{height:100%;text-align:center}
}

/* Stepper: 56 x 56 keys around a typed readout in mono. */
.stepper{display:inline-grid;grid-template-columns:var(--tap-primary) minmax(5rem,8rem) var(--tap-primary);gap:var(--space-2);align-items:stretch}
.stepper__btn{min-height:var(--tap-primary);border:var(--border-w) solid var(--line-strong);border-radius:var(--r-control);background:var(--surface);color:var(--ink);font:600 var(--fs-h3)/1 var(--font-body);cursor:pointer;touch-action:manipulation}
.stepper__btn:disabled{opacity:.55;cursor:not-allowed}
.stepper__input{min-height:var(--tap-primary);width:100%;text-align:center;font:600 var(--fs-h3)/1 var(--font-mono);font-variant-numeric:tabular-nums;color:var(--ink);background:var(--surface);border:var(--border-w) solid var(--line-strong);border-radius:var(--r-control)}

/* Photo tiles: square, two per row on the narrowest phones so the 48 px Move and Remove
   buttons fit, three per row from 400 px. */
.photo-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--space-3);list-style:none;margin:0;padding:0}
@media (min-width:400px){.photo-grid{grid-template-columns:repeat(3,minmax(0,1fr))}}
.photo-tile{display:grid;gap:var(--space-1)}
.photo-tile__frame{position:relative;aspect-ratio:1;border-radius:var(--r-control);overflow:hidden;background:var(--surface-2);border:var(--border-w) solid var(--line)}
.photo-tile__frame img{width:100%;height:100%;object-fit:cover;display:block}
/* Status page thumbnails get their blob URL only when the section scrolls into view, so
   until then the browser drew the alt text and a broken-image icon in each tile. The
   tile background shows instead; the link around it carries the accessible name. */
.photo-tile__frame img:not([src]){visibility:hidden}
.photo-tile__progress{position:absolute;left:0;right:0;bottom:0;width:100%;height:var(--space-1);appearance:none;border:0;background:var(--line)}
.photo-tile__progress::-webkit-progress-bar{background:var(--line)}
.photo-tile__progress::-webkit-progress-value{background:var(--brand)}
.photo-tile__progress::-moz-progress-bar{background:var(--brand)}
.photo-tile__status{font-size:var(--fs-meta);color:var(--ink-2)}
/* Review fix (accessibility-14): 8 px between the Move and Remove targets (DESIGN 11), not 4. */
.photo-tile__actions{display:grid;grid-template-columns:1fr 1fr;gap:var(--tap-gap)}
.photo-tile__actions .btn{min-height:var(--tap-min);padding-inline:var(--space-1)}
.photo-tile__actions .photo-tile__remove{grid-column:1 / -1}

/* Line-item row */
.line-item{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:var(--space-2) var(--space-4);align-items:start;padding:var(--space-4);border:var(--border-w) solid var(--line);border-radius:var(--r-card);background:var(--surface)}
.line-item__title{font-weight:600;margin:0}
.line-item__meta{grid-column:1 / -1;font-size:var(--fs-meta);color:var(--ink-2);margin:0}
.line-item__qty{font-family:var(--font-mono);font-variant-numeric:tabular-nums;white-space:nowrap}
.line-item__actions{grid-column:1 / -1;display:flex;flex-wrap:wrap;gap:var(--tap-gap)}

/* The load ticket (section 7): ruled header, real table, total, validity, timeline. */
.ticket{background:var(--surface);border:var(--border-w) solid var(--line);border-radius:var(--r-card);overflow:hidden}
.ticket__head{display:flex;flex-wrap:wrap;justify-content:space-between;gap:var(--space-2);padding:var(--space-3) var(--space-5);background:var(--surface-2);border-bottom:var(--border-w) solid var(--line-strong);font-family:var(--font-mono);font-variant-numeric:tabular-nums}
.ticket__ref{font-weight:600}
.ticket__time{font-weight:400}
/* Review fix (mobile-design-8): the weekday goes under 360 px so "Sent Oct 15, 1:28 PM" fits one line. */
@media (max-width:359.98px){.ticket__wd{display:none}}
.ticket__body{padding:var(--space-5)}
.ticket__merchant{color:var(--ink-2);margin:0 0 var(--space-4)}
.ticket__table{width:100%;border-collapse:collapse;font-variant-numeric:tabular-nums}
.ticket__table th{font-size:var(--fs-label);font-weight:600;text-align:left;padding:0 var(--space-2) var(--space-2) 0}
.ticket__table td{padding:var(--space-3) var(--space-2) var(--space-3) 0;border-top:var(--border-w) solid var(--line);vertical-align:top}
.ticket__table .num{text-align:right;white-space:nowrap;padding-right:0}
/* Integration fix: numeric cells had no left padding, so neighbouring right-aligned
   numbers touched ("$8.00$320.00"). With mixed units the price cell puts its unit on a
   second line in the body face (load.js), so a four-column estimate fits the 600 px
   column instead of pushing the total column out of the card. */
.ticket__table th+th,.ticket__table td+td{padding-left:var(--space-3)}
.ticket__table .ticket__unit{display:block;font-family:var(--font-body);font-size:var(--fs-meta);color:var(--ink-2);white-space:normal}
.ticket__table .ticket__excluded td{color:var(--ink-2);white-space:normal}
/* Under 480 px the four columns do not fit: each line becomes the product on its own row
   with quantity, price and total below it, each named by its data-label attribute. */
@media (max-width:479.98px){
  .ticket__body{padding:var(--space-4)}
  .ticket__table thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
  .ticket__table,.ticket__table tbody{display:block}
  .ticket__table tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));column-gap:var(--space-3);padding-block:var(--space-3);border-top:var(--border-w) solid var(--line)}
  .ticket__table td,.ticket__table td+td{display:block;border-top:0;padding:0}
  .ticket__table td:first-child{grid-column:1 / -1;margin-bottom:var(--space-2)}
  .ticket__table td[colspan]{grid-column:1 / -1;text-align:left}
  .ticket__table .num[data-label]::before{content:attr(data-label);display:block;font-family:var(--font-body);font-size:var(--fs-meta);color:var(--ink-2)}
}
.ticket__totals{margin-top:var(--space-4);padding-top:var(--space-4);border-top:var(--border-w) solid var(--line-strong);display:grid;gap:var(--space-2)}
.ticket__subtotal{display:flex;justify-content:space-between;font-family:var(--font-mono);font-variant-numeric:tabular-nums}
.ticket__total{display:flex;justify-content:space-between;align-items:baseline;gap:var(--space-3);flex-wrap:wrap}
.ticket__total-label{font-weight:600}
.ticket__total-value{font-family:var(--font-display);font-stretch:var(--font-display-stretch);font-weight:700;font-size:var(--fs-h2);line-height:var(--lh-h2);font-variant-numeric:tabular-nums}
.ticket__validity{margin-top:var(--space-4);font-weight:600}
.ticket__actions{display:grid;gap:var(--space-3);margin-top:var(--space-5)}
.ticket__legal{margin-top:var(--space-4);font-size:var(--fs-meta);color:var(--ink-2)}
/* The ticket print, the one orchestrated moment: the rule draws, then the total feeds
   out. Runs once when data-print is set; removed under reduced motion. */
.ticket__totals[data-print]{animation:ticket-rule var(--dur-hover) var(--ease-enter) both}
.ticket__totals[data-print] .ticket__total{animation:ticket-feed var(--dur-step) var(--ease-enter) var(--dur-hover) both}
@keyframes ticket-rule{from{border-top-color:transparent}to{border-top-color:var(--line-strong)}}
@keyframes ticket-feed{from{clip-path:inset(0 0 100% 0)}to{clip-path:inset(0)}}

/* Confirmation block on the status page right after sending (spec 6.10, DESIGN 6.2 step
   9). Review fix legal-product-1: it was never rendered. A brand-ruled card above the
   ticket; the REF is large mono so it can be read out over the phone. */
.received{margin-bottom:var(--space-6);padding:var(--space-5);border:var(--border-w) solid var(--brand);border-top-width:calc(var(--border-w) * 4);border-radius:var(--r-card);background:var(--surface)}
.received>*{margin:0}
.received>*+*{margin-top:var(--space-4)}
.received__title{font-size:var(--fs-h2);line-height:var(--lh-h2)}
.received__ref{font-family:var(--font-mono);font-weight:600;font-size:var(--fs-h2);line-height:var(--lh-h2);font-variant-numeric:tabular-nums}
.received>.received__save{margin-top:var(--space-5)}
.received .field{margin-bottom:0}
.received__done:empty{display:none}
.received__another{padding-inline:0}
@media (min-width:480px){.received__actions:has(>:nth-child(2)){grid-template-columns:1fr 1fr}}

/* Status timeline: vertical on phones, horizontal when the timeline itself has room.
   Integration fix: the switch was a 768 px media query, but the status page column is
   600 px, so six steps got about 92 px each and the labels ran together ("Reviewing
   Estimate sent"). A container query on the ticket body switches only at 720 px of
   real width; browsers without container queries keep the readable vertical layout. */
.ticket__body:has(>.timeline){container-type:inline-size}
.timeline{list-style:none;margin:var(--space-5) 0 0;padding:0;display:grid;gap:0}
.timeline__step{position:relative;display:grid;grid-template-columns:24px minmax(0,1fr);gap:var(--space-3);padding-bottom:var(--space-4);color:var(--ink-2)}
.timeline__step:not(:last-child)::after{content:"";position:absolute;left:11px;top:28px;bottom:2px;border-left:2px solid var(--line)}
.timeline__marker{width:24px;height:24px;border:2px solid var(--line-strong);border-radius:50%;background:var(--surface);display:grid;place-items:center}
.timeline__label{font-weight:600}
.timeline__time{display:block;font-family:var(--font-mono);font-size:var(--fs-meta);font-variant-numeric:tabular-nums;font-weight:400}
.timeline__step[data-state="done"]{color:var(--ink)}
.timeline__step[data-state="done"] .timeline__marker{border-color:var(--brand);background:var(--brand)}
.timeline__step[data-state="done"] .timeline__marker::before{content:"";width:6px;height:11px;border:solid var(--on-brand);border-width:0 2px 2px 0;transform:translateY(-1px) rotate(45deg)}
.timeline__step[data-state="done"]:not(:last-child)::after{border-left-color:var(--brand)}
.timeline__step[data-state="current"]{color:var(--ink)}
.timeline__step[data-state="current"] .timeline__marker{border-color:var(--ink)}
.timeline__step[data-state="current"] .timeline__marker::before{content:"";width:10px;height:10px;border-radius:50%;background:var(--ink)}
.timeline__step[data-state="closed"]{color:var(--ink)}
.timeline__step[data-state="closed"] .timeline__marker{border-color:var(--ink-2);border-radius:var(--r-chip)}
@container (min-width:720px){
  .timeline{grid-auto-flow:column;grid-auto-columns:minmax(0,1fr)}
  .timeline__step{grid-template-columns:none;grid-template-rows:24px auto;gap:var(--space-2);padding-bottom:0;padding-right:var(--space-2)}
  .timeline__step:not(:last-child)::after{left:30px;right:6px;top:11px;bottom:auto;border-left:0;border-top:2px solid var(--line)}
  .timeline__step[data-state="done"]:not(:last-child)::after{border-top-color:var(--brand)}
}

/* Notices, the site banner from settings, and the offline banner. */
.notice{padding:var(--space-4);border:var(--border-w) solid var(--info);border-radius:var(--r-card);background:var(--info-tint);color:var(--ink);margin-bottom:var(--space-5)}
.notice--warn{border-color:var(--warn);background:var(--warn-tint)}
.notice--danger{border-color:var(--danger);background:var(--danger-tint)}
.notice--brand{border-color:var(--brand);background:var(--brand-tint)}
.notice>:last-child{margin-bottom:0}
.site-banner{padding:var(--space-3) var(--gutter);background:var(--info-tint);border-bottom:var(--border-w) solid var(--info);color:var(--ink);font-weight:600}
.offline-banner{position:sticky;top:0;z-index:20;display:flex;flex-wrap:wrap;gap:var(--space-3);align-items:center;justify-content:space-between;padding:var(--space-3) var(--gutter);background:var(--warn-tint);color:var(--ink);border-bottom:var(--border-w) solid var(--warn)}
.offline-banner__text{margin:0;font-weight:600;display:flex;gap:var(--space-2);align-items:center}

/* Progress bar under the flow's step label: 4 px brand on line. */
.progress{display:block;width:100%;height:var(--space-1);appearance:none;border:0;background:var(--line)}
.progress::-webkit-progress-bar{background:var(--line)}
.progress::-webkit-progress-value{background:var(--brand)}
.progress::-moz-progress-bar{background:var(--brand)}

/* Sticky bottom bar. Pages that use it put .has-action-bar on <html> so focused fields
   scroll clear of it (scroll-padding) and the page ends above it. */
.action-bar{position:fixed;left:0;right:0;bottom:0;z-index:10;background:var(--surface);border-top:var(--border-w) solid var(--line);padding:var(--space-4) var(--gutter) calc(var(--space-4) + env(safe-area-inset-bottom))}
.action-bar__inner{max-width:600px;margin-inline:auto}
/* Review fix (mobile-design-9): the bar rendered 120 px (a 1.5em status line plus an 8 px
   gap) while --bar-h-status, which sizes the scroll padding, the body padding and the toast
   offset below, says 112 (DESIGN 6.2). The one-line "Saved 3:02 AM" status now takes 1.25em
   with a 4 px gap: 16 + 18.75 + 4 + 56 + 16 + 1 px border = 112. */
.action-bar__status{min-height:1.25em;line-height:1.25;margin:0 0 var(--space-1);font-size:var(--fs-meta);color:var(--ink-2);text-align:center}
.has-action-bar{scroll-padding-bottom:calc(var(--bar-h-status) + env(safe-area-inset-bottom))}
.has-action-bar body{padding-bottom:calc(var(--bar-h-status) + env(safe-area-inset-bottom))}

/* Toast (aria-live polite region) above the bar. */
/* Review fix (mobile-design-10): the toast always cleared the action bar, so on pages with no
   bar (the status page's undo toast) it floated 128 px up over the content. It sits 16 px
   above the bottom by default and clears the bar only where the page has one. */
.toast-region{position:fixed;left:var(--gutter);right:var(--gutter);bottom:calc(var(--space-4) + env(safe-area-inset-bottom));z-index:30;display:grid;justify-items:center;pointer-events:none}
.has-action-bar .toast-region{bottom:calc(var(--bar-h-status) + var(--space-4) + env(safe-area-inset-bottom))}
.toast{pointer-events:auto;display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-3);max-width:600px;min-height:var(--tap-min);padding:var(--space-2) var(--space-4);border:var(--border-w) solid var(--line-strong);border-radius:var(--r-card);background:var(--ink);color:var(--surface);font-weight:600;animation:toast-in var(--dur-step) var(--ease-enter) both}
.toast__action{min-height:var(--tap-min);padding-inline:var(--space-2);background:transparent;border:0;color:inherit;font:inherit;text-decoration:underline;cursor:pointer}
@keyframes toast-in{from{opacity:0;transform:translateY(var(--space-2))}to{opacity:1;transform:none}}

/* Confirm sheet: a <dialog> opened with showModal(); bottom sheet on phones, centered
   from 768 px. The only places the single shadow appears. */
.sheet{width:100%;max-width:600px;max-height:90dvh;margin:auto auto 0;padding:0;border:0;border-radius:var(--r-card) var(--r-card) 0 0;background:var(--surface);color:var(--ink);box-shadow:var(--shadow-sheet);overflow:auto}
/* Review fix (mobile-design-2): the backdrop was --ink at .45 opacity, and in dock mode --ink
   is the light text color, so every open sheet washed the dark page out to grey. --scrim
   always darkens. The fallback covers browsers whose ::backdrop does not inherit tokens. */
.sheet::backdrop{background:var(--scrim,rgb(13 27 30 / .45))}
.sheet__body{padding:var(--space-5) var(--gutter) calc(var(--space-5) + env(safe-area-inset-bottom))}
.sheet__title{font-size:var(--fs-h3);line-height:var(--lh-h3)}
.sheet__actions{display:grid;gap:var(--space-3);margin-top:var(--space-5)}
@media (min-width:768px){.sheet{margin:auto;border-radius:var(--r-card)}.sheet__body{padding:var(--space-6)}}

/* Tables (admin and confirmation). Rows stay 48 px tall for touch; under 768 px a table
   marked .table--cards turns each row into a card in the same reading order. */
.table-wrap{overflow-x:auto}
.table{width:100%;border-collapse:collapse;font-variant-numeric:tabular-nums;background:var(--surface)}
.table th{text-align:left;font-size:var(--fs-label);font-weight:600;background:var(--surface-2);padding:var(--space-3);border-bottom:var(--border-w) solid var(--line-strong)}
.table td{padding:var(--space-3);border-top:var(--border-w) solid var(--line);vertical-align:top}
/* A link that fills a table cell (firm names, REFs) is a 48 px target like any other. */
.table td>a{display:inline-flex;align-items:center;min-height:var(--tap-min)}
.table .num{text-align:right;font-family:var(--font-mono);white-space:nowrap}
.table__sort{display:inline-flex;align-items:center;gap:var(--space-1);min-height:var(--tap-min);min-width:var(--tap-min);padding:0;border:0;background:none;color:inherit;font:inherit;cursor:pointer}
@media (max-width:767.98px){
  .table--cards thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
  .table--cards,.table--cards tbody,.table--cards tr,.table--cards td{display:block;width:100%}
  .table--cards tr{margin-bottom:var(--space-3);border:var(--border-w) solid var(--line);border-radius:var(--r-card);background:var(--surface);overflow:hidden}
  .table--cards td{display:grid;grid-template-columns:minmax(6rem,40%) minmax(0,1fr);gap:var(--space-3);border-top:0;border-bottom:var(--border-w) solid var(--line);text-align:left}
  .table--cards td:last-child{border-bottom:0}
  /* The card label keeps the body face and wraps, even inside a .num (monospace, no-wrap) cell. */
  .table--cards td::before{content:attr(data-label);font-weight:600;color:var(--ink-2);font-family:var(--font-body);white-space:normal}
  .table--cards .num{text-align:left}
  /* An empty cell in a card would be a label with nothing beside it (review fix mobile-design-19). */
  .table--cards td.is-empty{display:none}
}

/* Review fix (mobile-design-18): six payment columns in the status page's 600 px column
   wrapped every cell into a stack of mono lines. .table--stack keeps the card layout at
   every width (the same reading order as the phone cards above). */
.table--stack thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
.table--stack,.table--stack tbody,.table--stack tr,.table--stack td{display:block;width:100%}
.table--stack tr{margin-bottom:var(--space-3);border:var(--border-w) solid var(--line);border-radius:var(--r-card);background:var(--surface);overflow:hidden}
.table--stack td{display:grid;grid-template-columns:minmax(6rem,40%) minmax(0,1fr);gap:var(--space-3);border-top:0;border-bottom:var(--border-w) solid var(--line);text-align:left}
.table--stack td:last-child{border-bottom:0}
.table--stack td::before{content:attr(data-label);font-weight:600;color:var(--ink-2);font-family:var(--font-body);white-space:normal}

/* KPI tile: a value in mono and a bar against a target line (section 6.3). The page sets
   --kpi-value and --kpi-target as percentages through element.style.setProperty. */
.kpi-strip{display:grid;grid-template-columns:repeat(auto-fit,minmax(13rem,1fr));gap:var(--space-3)}
.kpi{padding:var(--space-4);border:var(--border-w) solid var(--line);border-radius:var(--r-card);background:var(--surface)}
.kpi__label{margin:0 0 var(--space-1);font-size:var(--fs-meta);font-weight:600;color:var(--ink-2)}
.kpi__value{margin:0 0 var(--space-3);font-family:var(--font-mono);font-size:var(--fs-h3);font-variant-numeric:tabular-nums}
.kpi__bar{position:relative;height:var(--space-2);border:var(--border-w) solid var(--line-strong);background:var(--surface-2)}
.kpi__fill{position:absolute;left:0;top:0;bottom:0;width:var(--kpi-value,0%);max-width:100%;background:var(--brand)}
.kpi__target{position:absolute;top:calc(var(--space-1) * -1);bottom:calc(var(--space-1) * -1);left:var(--kpi-target,0%);border-left:2px solid var(--ink)}
.kpi__note{margin:var(--space-2) 0 0;font-size:var(--fs-meta);color:var(--ink-2)}

/* Windows high contrast replaces colors, so selected states keep a system-color outline. */
@media (forced-colors:active){
  .choice__input:checked+.choice__face,.chip-toggle__input:checked+.chip-toggle__face,.segmented__input:checked+.segmented__face{outline:3px solid Highlight;outline-offset:-3px}
  .timeline__step[data-state="done"] .timeline__marker{background:Highlight}
}

/* "Add Salida to your phone" card (js/install-card.js; PRODUCT_SPEC 18). A plain section in the brand tint:
   title, one line of body, the two iPhone taps or the Add button, and "Not now". */
.install-card{padding:var(--space-4);border:var(--border-w) solid var(--brand);border-radius:var(--r-card);background:var(--brand-tint);color:var(--ink)}
.install-card__title{margin:0 0 var(--space-1);font-weight:600}
.install-card__body,.install-card__steps{margin:0 0 var(--space-3);font-size:var(--fs-meta);color:var(--ink-2)}
.install-card__steps{color:var(--ink);font-weight:600}
.install-card .cluster{margin-top:var(--space-3)}
.install-card>:last-child{margin-bottom:0}
.received__install{margin-block:var(--space-4)}
@media print{.install-card{display:none}}

/* Reduced motion: motion is removed, never slowed (section 4). */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation:none!important;transition:none!important;scroll-behavior:auto!important}
}
