/* Qv2 base styles. EVERY value comes from a design token (a CSS custom property declared by the active theme in themes/<name>.css), so
   a theme is one swappable file and nothing here is hard-coded. Tints and shadows are DERIVED from the tokens with color-mix(), so a re-skin
   recolors them too. See the vault note "UI model: the current clinic portal and the v2 theme system" and v2/modules/frontend. */
*, *::before, *::after { box-sizing: border-box; }
html { color-scheme: light dark; -webkit-text-size-adjust: 100%; }
:root[data-mode="light"] { color-scheme: light; }
:root[data-mode="dark"] { color-scheme: dark; }
body {
  margin: 0; background: var(--color-bg); color: var(--color-text);
  font: var(--font-size-base)/1.55 var(--font-body); -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
}
h1, h2, h3 { font-family: var(--font-heading); font-weight: var(--font-weight-heading); line-height: 1.2; margin: 0 0 var(--space-3); letter-spacing: -.01em; }
h1 { font-size: var(--font-size-h1); } h2 { font-size: var(--font-size-h2); }
p { margin: 0 0 var(--space-3); }
a { color: var(--color-accent-text); text-underline-offset: .2em; }
h1, h2, h3, h4 { overflow-wrap: anywhere; }       /* a heading that names a record (billable_event.recorded) breaks inside the word rather than pushing the page sideways at 320 px (anywhere, not break-word: `.panel h2` is a flex row, and only anywhere lets its text item shrink) */
/* A link that stands on its own line beside a button (not inside a sentence) is a touch target of its own: at least 24 CSS pixels tall (WCAG 2.2 target size). */
a.tap { display: inline-block; padding-block: .35rem; }
button.as-link { background: none; border: 0; padding: 0; margin: 0; font: inherit; color: var(--color-accent-text); text-decoration: underline; text-underline-offset: .2em; cursor: pointer; }   /* a button that posts and reads as the link it replaced (a download whose filter holds a person's name: it cannot be in an address) */
:focus-visible { outline: 3px solid var(--color-focus); outline-offset: 2px; border-radius: var(--radius-sm); }
.muted { color: var(--color-text-muted); }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
[hidden] { display: none !important; }
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* ---- icons: line = the text color around it, second tone = the theme accent (see templates/_sprite.html) */
.icon { width: 1.4em; height: 1.4em; flex: none; vertical-align: middle; }
.ic { fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
.ic .tone { fill: var(--icon-tone); stroke: none; }
.icon.spin { animation: spin .8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ---- the brand lockup */
.brand { display: inline-flex; align-items: center; gap: var(--space-2); font: var(--font-weight-heading) 1.5rem/1 var(--font-heading); letter-spacing: -.02em; color: var(--color-text); white-space: nowrap; }
.brand-mark { width: 2rem; height: 2rem; flex: none; }
.mark { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.mark-ring { stroke: currentColor; stroke-width: 3; }
.mark-pulse { stroke: var(--color-accent); stroke-width: 2.2; }
.mark-tail { stroke: var(--color-accent); stroke-width: 3; }
.mark-dot { fill: var(--color-accent); stroke: none; }
.brand-b { color: var(--color-accent); }
.brand.large { font-size: clamp(2.4rem, 6vw, 3.6rem); gap: var(--space-3); }
.brand.large .brand-mark { width: 1.15em; height: 1.15em; }

/* ---- buttons */
.btn {
  appearance: none; border: 1px solid transparent; border-radius: var(--radius-md); cursor: pointer; position: relative;
  padding: .7rem var(--space-4); font: var(--font-weight-button) var(--font-size-base)/1.2 var(--font-button);
  background: var(--color-accent); color: var(--color-on-accent);
  box-shadow: 0 1px 0 color-mix(in srgb, var(--color-on-accent) 25%, transparent) inset, 0 6px 16px -8px color-mix(in srgb, var(--color-accent) 70%, transparent);
  transition: background var(--motion) var(--ease), transform var(--motion) var(--ease), box-shadow var(--motion) var(--ease);
}
.btn:hover { background: var(--color-accent-hover); }
.btn:active { transform: translateY(1px); }
.btn.secondary { background: transparent; color: var(--color-text); border-color: var(--color-border); box-shadow: none; }
.btn.secondary:hover { background: var(--color-surface-2); }
.btn.block { display: flex; justify-content: center; align-items: center; gap: var(--space-2); width: 100%; }
.btn[aria-busy="true"] { pointer-events: none; opacity: .85; }
.iconbtn {
  appearance: none; display: inline-grid; place-items: center; width: 2.5rem; height: 2.5rem; border-radius: var(--radius-md); cursor: pointer;
  border: 1px solid transparent; background: transparent; color: inherit; padding: 0; font-size: 1.1rem;
  transition: background var(--motion) var(--ease);
}
.iconbtn:hover { background: color-mix(in srgb, currentColor 12%, transparent); }
@media (max-width: 64rem) { .iconbtn { width: 2.75rem; height: 2.75rem; } }                                   /* UIFIX1 (UI1-PR-17): the menu button of the top bar was 40 px: 44 by 44 on a phone and an iPad */

/* ---- fields: a floating label (the label is always a real <label>) */
.field { display: block; position: relative; margin: 0 0 var(--space-4); }
.field > span { display: block; margin-bottom: var(--space-1); font-weight: 600; font-size: .9em; }
.field input, .field textarea {
  width: 100%; padding: .8rem var(--space-3); border-radius: var(--radius-md); border: 1px solid var(--color-border);
  background: var(--color-surface); color: var(--color-text); font: inherit;
  transition: border-color var(--motion) var(--ease), box-shadow var(--motion) var(--ease);
}
.field input::placeholder, .field textarea::placeholder { color: var(--color-text-muted); }
.field textarea { resize: vertical; min-height: 5.5rem; display: block; }
.field input:hover, .field textarea:hover { border-color: color-mix(in srgb, var(--color-text-muted) 60%, var(--color-border)); }
.field input:focus-visible, .field textarea:focus-visible { outline: none; border-color: var(--color-accent); box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-accent) 22%, transparent); }
.field.float > span {
  position: absolute; left: var(--space-3); top: .85rem; margin: 0; padding: 0 .25rem; pointer-events: none; font-weight: 500; font-size: 1em;
  color: var(--color-text-muted); background: transparent; transform-origin: left top;
  transition: transform var(--motion) var(--ease), color var(--motion) var(--ease), background var(--motion) var(--ease);
}
.field.float input::placeholder, .field.float textarea::placeholder { color: transparent; }
/* A select inside a floating label (lane TESTAXE): it is styled like the text fields, and its label is always in the raised place, because a select always shows a chosen option: with the label at rest it lay on top of that text, and over the
   browser's own unstyled select face (a grey of the host's choosing in dark mode, which axe measured at 2.6:1 on a Linux host). */
.field.float select {
  width: 100%; padding: .8rem var(--space-3); border-radius: var(--radius-md); border: 1px solid var(--color-border);
  background: var(--color-surface); color: var(--color-text); font: inherit;
  transition: border-color var(--motion) var(--ease), box-shadow var(--motion) var(--ease);
}
.field.float select:hover { border-color: color-mix(in srgb, var(--color-text-muted) 60%, var(--color-border)); }
.field.float select:focus-visible { outline: none; border-color: var(--color-accent); box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-accent) 22%, transparent); }
.field.float select ~ span { transform: translateY(-1.45rem) scale(.82); color: var(--color-accent-text); background: var(--color-surface); font-weight: 600; }
.field.float input:focus ~ span, .field.float input:not(:placeholder-shown) ~ span, .field.float textarea:focus ~ span, .field.float textarea:not(:placeholder-shown) ~ span {
  transform: translateY(-1.45rem) scale(.82); color: var(--color-accent-text); background: var(--color-surface); font-weight: 600;
}
.field .reveal { position: absolute; right: .3rem; top: .3rem; color: var(--color-text-muted); }
.field .reveal[hidden] + input { padding-right: var(--space-3); }
.field.has-reveal input { padding-right: 3rem; }
.hint { margin: calc(-1 * var(--space-2)) 0 var(--space-4); color: var(--color-text-muted); font-size: .9em; }
.notice { padding: var(--space-3) var(--space-4); border-radius: var(--radius-md); background: var(--color-danger-soft); color: var(--color-danger-text); margin: 0 0 var(--space-4); }
.key { display: block; padding: var(--space-3); border-radius: var(--radius-md); background: var(--color-surface-2); border: 1px solid var(--color-border); word-break: break-all; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .05em; user-select: all; }

/* ---- the skip link: first thing a keyboard user reaches */
.skip { position: absolute; left: var(--space-3); top: -4rem; z-index: 50; padding: var(--space-2) var(--space-4); border-radius: var(--radius-md); background: var(--color-accent); color: var(--color-on-accent); transition: top var(--motion) var(--ease); }
.skip:focus { top: var(--space-3); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; scroll-behavior: auto !important; }
  .icon.spin { animation: none; }
}

.notice.ok { background: var(--color-success-soft, var(--color-surface-2)); color: var(--color-success-text, var(--color-text)); }
.forgot { margin: var(--space-3) 0 0; text-align: center; }

/* ---- the environment banner (templates/_env_banner.html): a very thin red strip fixed to the foot of the window on every page of a system that is not production.
   The page keeps room for it (--env-banner-h: the stylesheet's estimate until env-banner.js measures it), so nothing is hidden behind it: the menu and the full-window
   layouts end above it, the page has padding below its last line, and a toast sits over it. In production none of this applies: there is no element and no attribute. */
html[data-env-banner] { --env-banner-h: 1.7rem; }
html[data-env-banner] body { padding-bottom: var(--env-banner-h); }
.env-banner {
  position: fixed; inset: auto 0 0 0; z-index: 100; display: flex; flex-wrap: wrap; align-items: center; gap: .1rem var(--space-3); padding: .2rem var(--space-3);
  background: var(--color-env-bg); color: var(--color-env-text); font-size: .72rem; line-height: 1.35;
}
.env-name { font-weight: 800; letter-spacing: .06em; white-space: nowrap; }
.env-warn { font-weight: 800; background: var(--color-env-text); color: var(--color-env-bg); padding: 0 .45rem; border-radius: 3px; letter-spacing: .02em; }      /* a standing warning in words: the colors swapped so it is the first thing the eye finds (the same contrast pair) */
@media (min-width: 40.01rem) { .env-warn { white-space: nowrap; flex: none; } }
.env-facts { display: flex; flex-wrap: wrap; gap: 0 var(--space-2); margin: 0; padding: 0; list-style: none; }
.env-k { opacity: .85; }
.env-k::after { content: ":"; }
.env-v { font-weight: 600; }
/* The Details disclosure (BANNER2): a plain <details> in the strip. Opened, its list is a panel standing on the strip (above it, never over the strip's own text), as wide as the facts need and never wider than the window, scrolling inside itself when
   there are more facts than the window is tall. Same colors as the strip, so the contrast pair is the one the tests already pair. Without the script it still opens and closes; the script only adds Escape and a click elsewhere. */
.env-more { flex: none; }
.env-more > summary { cursor: pointer; font-weight: 700; text-decoration: underline; text-underline-offset: .15em; list-style: none; padding: 0 .35rem; border-radius: 3px; }
.env-more > summary::-webkit-details-marker { display: none; }
.env-more > summary:focus-visible { outline: 2px solid currentcolor; outline-offset: 1px; }
.env-more-list { position: absolute; right: 0; bottom: 100%; box-sizing: border-box; width: max-content; max-width: 100%; max-height: min(70vh, 28rem); overflow-y: auto; margin: 0; padding: var(--space-2) var(--space-3); list-style: none;
  background: var(--color-env-bg); color: var(--color-env-text); border-top: 1px solid currentcolor; display: grid; gap: .15rem; }
.env-more-list li { overflow-wrap: anywhere; }
@media (max-width: 40rem) {                                                   /* a phone: the name and the first three facts (database, sign-in, build), on two or three lines */
  html[data-env-banner] { --env-banner-h: 3.6rem; }
  .env-banner { font-size: .7rem; gap: 0 var(--space-2); }
  .env-facts { gap: 0 var(--space-2); }
  .env-facts li:nth-child(n+4) { display: none; }
}
@media (min-width: 40.01rem) {                                                /* a desktop: ONE line, always. The person's own line comes first and is never cut; the facts after it fade out at the right edge when the window is too narrow for all of them (the whole list is in the strip's title) */
  .env-banner { flex-wrap: nowrap; }
  .env-facts { flex-wrap: nowrap; flex: 1 1 0; min-width: 0; overflow: hidden; white-space: nowrap; -webkit-mask-image: linear-gradient(to right, currentcolor calc(100% - 2.5rem), transparent); mask-image: linear-gradient(to right, currentcolor calc(100% - 2.5rem), transparent); }
  .env-facts li { flex: none; }
}
@media print { .env-banner { display: none; } html[data-env-banner] body { padding-bottom: 0; } }

/* The authenticator's QR code (lane MFAQR): black on white always (a scanner needs the contrast), never wider than its box, beside the setup key where there is room and above it on a phone. */
.qr-row { display: flex; flex-wrap: wrap; gap: var(--space-3, 1rem); align-items: flex-start; }
.qr-side { flex: 1 1 14rem; min-width: 0; }
.qr-box { display: inline-block; max-width: 100%; line-height: 0; margin: .5rem 0; background: white; border: 1px solid var(--color-border); border-radius: var(--radius-sm); }
.qr-box .qr { display: block; max-width: 100%; height: auto; }

/* ---- form errors tied to their fields (lane A11YPASS): the sentence under a field, and the one-line refusal at the top of a form (a focusable alert: static/form-errors.js moves focus to it after a refused submit) */
.field-error { margin: .25rem 0 0; color: var(--color-danger-text); font-size: .85rem; }
.field-error::before { content: "\26A0\FE0E\00a0"; }                                   /* a mark as well as a color: an error is never told by color alone */
input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"] { border-color: var(--color-danger-text); }
.notice.error-line, .notice.error-summary { padding: var(--space-2) var(--space-3); margin: 0 0 var(--space-3); font-size: .92em; line-height: 1.4; }
.notice.error-line a, .notice.error-summary a { color: inherit; }
.error-summary-title { margin: 0 0 .15rem; font-weight: 650; }
.error-summary ul { margin: 0; padding-left: 1.2rem; }
.error-summary li { margin: 0; }
.notice[data-error-summary]:focus-visible { outline: 3px solid var(--color-focus); outline-offset: 2px; }

/* ---- touch targets (lane A11YPASS, Brent round 3 X4: phone controls were 13 to 21 px): on a phone every control a finger presses is at least 44 by 44 CSS px. ONE token, applied to the shared control styles, so every screen gets it;
   the desktop sizes are untouched. A link inside a sentence is exempt (WCAG 2.2 target size, inline); a link that stands alone (beside a button, in a table cell, in a list of links) is a target of its own. */
:root { --touch-min: 2.75rem; --tap-min: 24px; }                                             /* --tap-min: the least height of a stand-alone link or tab (24 px on a desktop, WCAG 2.2) */
@media (max-width: 40rem) {
  :root:not([data-look="old"]) { --tap-min: var(--touch-min); }
  :root:not([data-look="old"]) .btn, :root:not([data-look="old"]) .btn.small, :root:not([data-look="old"]) .credit-resolve, :root:not([data-look="old"]) .credit-banner-review a, :root:not([data-look="old"]) .access-row > a, :root:not([data-look="old"]) h2 > a.small, :root:not([data-look="old"]) a.filter-clear, :root:not([data-look="old"]) .anchor-nav a, :root:not([data-look="old"]) .access-head a, :root:not([data-look="old"]) .pt-brand, :root:not([data-look="old"]) .pt-out button, :root:not([data-look="old"]) a.chip, :root:not([data-look="old"]) .rowlink, :root:not([data-look="old"]) .tcard .tc-name, :root:not([data-look="old"]) button.cell-btn, :root:not([data-look="old"]) .iconbtn, :root:not([data-look="old"]) .chip-link, :root:not([data-look="old"]) .linkbtn, :root:not([data-look="old"]) .pn-add, :root:not([data-look="old"]) .pn-trash, :root:not([data-look="old"]) a.tap, :root:not([data-look="old"]) a.tapp, :root:not([data-look="old"]) .crumbs a, :root:not([data-look="old"]) .forgot a, :root:not([data-look="old"]) td > a:only-child, :root:not([data-look="old"]) li > a:only-child:not([class]):not(.pt-tabbar a), :root:not([data-look="old"]) th > a:only-child, :root:not([data-look="old"]) .reveal { display: inline-flex; align-items: center; justify-content: center; min-height: var(--touch-min); min-width: var(--touch-min); box-sizing: border-box; }
  :root:not([data-look="old"]) .btn.block { display: flex; }
  :root:not([data-look="old"]) .iconbtn { width: var(--touch-min); height: var(--touch-min); }
  :root:not([data-look="old"]) a.tap, :root:not([data-look="old"]) a.tapp, :root:not([data-look="old"]) .crumbs a, :root:not([data-look="old"]) .forgot a, :root:not([data-look="old"]) td > a:only-child, :root:not([data-look="old"]) th > a:only-child, :root:not([data-look="old"]) li > a:only-child:not([class]) { justify-content: flex-start; padding-block: 0; }
  :root:not([data-look="old"]) td > input[type=checkbox] { width: 1.6rem; height: 1.6rem; }                                   /* a checkbox with no label of its own (a row's pick box) cannot take padding: as large as a cell allows, never under the 24 px floor */
  html { scroll-padding-top: 4.5rem; }                                                              /* WCAG 2.4.11: what is scrolled or focused into view is never under the sticky top bar of a phone */
  :root:not([data-look="old"]) .tablist [role="tab"] { min-height: var(--touch-min); }
  :root:not([data-look="old"]) .seg-options label { min-height: var(--touch-min); }
  :root:not([data-look="old"]) input:not([type=checkbox]):not([type=radio]):not([type=hidden]):not([type=range]), :root:not([data-look="old"]) select, :root:not([data-look="old"]) textarea { min-height: var(--touch-min); }
  :root:not([data-look="old"]) summary:not(.menu-link):not(.env-more > summary) { min-height: var(--touch-min); box-sizing: border-box; padding-block: .6rem; }      /* (not the test-system strip's own Details: it is not part of the product) */
  :root:not([data-look="old"]) label:has(> input[type=checkbox]), :root:not([data-look="old"]) label:has(> input[type=radio]), :root:not([data-look="old"]) .check, :root:not([data-look="old"]) .notice-choice { display: inline-flex; align-items: baseline; gap: .5rem; box-sizing: border-box; padding-block: .55rem; min-height: var(--touch-min); min-width: var(--touch-min); }      /* the box stands on the first line of its words (a long sentence that wraps keeps the box beside its first line, not centered beside the middle of it) */
  :root:not([data-look="old"]) .pn-choice label, :root:not([data-look="old"]) .notice-choice label { display: inline-flex; align-items: center; min-height: var(--touch-min); min-width: var(--touch-min); }
  :root:not([data-look="old"]) .btn.small { padding-block: .2rem; }                                          /* (the :root prefix also out-ranks shell.css, which sets the small button's own height later) */
  :root:not([data-look="old"]) .notice-choice input, :root:not([data-look="old"]) label > input[type=checkbox], :root:not([data-look="old"]) label > input[type=radio] { flex: none; width: auto; }
}

/* ---- ONE filter bar (lane A11YPASS, Brent round 3 X4): every live-search form of the new look (templates/_live.html's `bar`, and the few forms written by hand) is a wrapping row with one gap; a field never shrinks under a finger's width,
   and on a phone the bar is one column of full-width fields. A screen's own class keeps only what is its own (a width, a column). */
.filter-bar { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-3); align-items: flex-end; margin: var(--space-2) 0 var(--space-3); }
.filter-bar > .field, .filter-bar > .select-field, .filter-bar > .fld, .filter-bar > label { flex: 1 1 11rem; min-width: min(9rem, 100%); margin: 0; }
.filter-bar > .grow { flex: 2 1 16rem; }
.filter-bar input, .filter-bar select { max-width: 100%; }
.filter-bar .filter-clear { align-self: center; }
@media (max-width: 40rem) {
  .filter-bar { flex-direction: column; align-items: stretch; }
  .filter-bar > .field, .filter-bar > .select-field, .filter-bar > .fld, .filter-bar > label, .filter-bar > .grow { flex: 0 0 auto; width: 100%; min-width: 0; }
}
.field.grow { flex: 1 1 14rem; min-width: min(14rem, 100%); }        /* a field that takes the room that is left in a flex row (a reason box beside its button): never a sliver */

/* ---- calmer nudges (lane A11YPASS, Brent round 3 X4): a nudge is one quiet line, in the warning tint and not the error red; the rest of its sentence opens under it. The new look only (the old look keeps its markup). */
.calm-nudge { margin: 0 var(--space-3) var(--space-2); padding: 0 var(--space-3); font-size: .88rem; line-height: 1.4; }
.calm-nudge > summary { cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; list-style-position: inside; min-height: 1.6rem; }
.calm-nudge[open] > summary { white-space: normal; }
.calm-nudge > p { margin: .2rem 0 .1rem; }
.calm-nudge a { color: inherit; }
