/* Orvia shared primitives.
 *
 * Every class here is `o-` prefixed and composes `design/tokens.css`. The prefix is the
 * point: the three applications already own class names like `.card`, `.btn` and `.tag`
 * with three different meanings, and a shared library that collides with them would have
 * to be adopted everywhere in one release. This can be adopted a screen at a time.
 *
 * The rule for what belongs here: if two applications need it, it is a primitive. If one
 * application needs it, it is composition and belongs in that application's CSS.
 */

/* ── typography ────────────────────────────────────────────────────────────
   Named by role, not by size. A screen asking for "18px semibold" has decided how
   something looks; a screen asking for a section title has decided what it is. */
.o-display { font: var(--type-display); letter-spacing: -.02em; margin: 0; }
.o-page-title { font: var(--type-page); letter-spacing: -.01em; margin: 0; }
.o-section-title { font: var(--type-section); margin: 0; }
.o-card-title { font: var(--type-card); margin: 0; }
.o-body { font: var(--type-body); margin: 0; }
.o-label { font: var(--type-label); color: var(--text-muted); }
.o-caption { font: var(--type-caption); color: var(--text-muted); }
/* Figures line up in a column or they are harder to compare than the same numbers
   written on paper. */
.o-kpi { font: var(--type-kpi); font-variant-numeric: tabular-nums; }
.o-num { font-variant-numeric: tabular-nums; }
/* A URL, a SKU or a JSON path is Latin however the interface reads. */
.o-ltr { direction: ltr; text-align: left; unicode-bidi: isolate; }
.o-mono { font-family: var(--font-mono); }

[dir="rtl"] .o-display, [dir="rtl"] .o-page-title, [dir="rtl"] .o-section-title,
[dir="rtl"] .o-card-title, [dir="rtl"] .o-body { font-family: var(--font-ar); }

/* ── buttons ───────────────────────────────────────────────────────────────
   Five intents and three sizes. Anything else is a screen inventing a hierarchy the
   product does not have. */
.o-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  height: var(--control-md); padding: 0 var(--sp-4);
  border: 1px solid transparent; border-radius: var(--radius-md);
  font: var(--type-body); font-weight: 600; cursor: pointer;
  background: var(--brand); color: var(--on-brand);
  transition: background var(--dur-fast), border-color var(--dur-fast), box-shadow var(--dur-fast);
}
.o-btn:hover { background: var(--brand-strong); }
.o-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.o-btn[disabled], .o-btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }

.o-btn--secondary { background: var(--surface); color: var(--text); border-color: var(--border); }
.o-btn--secondary:hover { background: var(--surface-2); }
.o-btn--tertiary { background: transparent; color: var(--brand-strong); border-color: transparent; }
.o-btn--tertiary:hover { background: var(--surface-2); }
/* Danger is a colour and a confirmation, never a colour alone: the dialog does the
   asking, this only says which button it is. */
.o-btn--danger { background: var(--danger); color: #fff; }
.o-btn--danger:hover { filter: brightness(.94); }
.o-btn--icon { width: var(--control-md); padding: 0; }
.o-btn--sm { height: var(--control-sm); padding: 0 var(--sp-3); font: var(--type-label); font-weight: 600; }
.o-btn--lg { height: var(--control-lg); padding: 0 var(--sp-6); font-size: 16px; }

/* Loading is a state of the button, not a replacement for it: the label stays so the
   width does not jump and the person can still read what they pressed. */
.o-btn[data-loading="true"] { position: relative; color: transparent; pointer-events: none; }
.o-btn[data-loading="true"]::after {
  content: ""; position: absolute; width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid currentColor; border-top-color: transparent;
  color: var(--on-brand); animation: o-spin .7s linear infinite;
}
.o-btn--secondary[data-loading="true"]::after { color: var(--text); }
@keyframes o-spin { to { transform: rotate(360deg); } }

/* ── fields ────────────────────────────────────────────────────────────── */
.o-field { display: grid; gap: var(--sp-1); }
.o-field > label { font: var(--type-label); color: var(--text-muted); }
.o-input, .o-select, .o-textarea {
  width: 100%; height: var(--control-md); padding: 0 var(--sp-3);
  font: var(--type-body); color: var(--text);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md);
  transition: border-color var(--dur-fast), box-shadow var(--dur-fast); outline: none;
}
.o-textarea { height: auto; min-height: 96px; padding: var(--sp-3); }
.o-input:focus, .o-select:focus, .o-textarea:focus {
  border-color: var(--brand); box-shadow: var(--focus-ring);
}
.o-input[readonly] { background: var(--surface-2); }
.o-input[disabled], .o-select[disabled] { opacity: .55; cursor: not-allowed; }
.o-field--error .o-input, .o-field--error .o-select { border-color: var(--danger); }
.o-field-help { font: var(--type-caption); color: var(--text-muted); }
/* The error replaces the help text rather than joining it: two lines under a field and
   the person reads the one that was already there. */
.o-field-error { font: var(--type-caption); color: var(--danger-text); }   /* 10.89.0: text, so the text token (4.5:1) */

/* ── controls that are not buttons ─────────────────────────────────────────
   Tabs navigate, a segmented control switches one mode, a chip filters, a badge is
   read-only state. Styling all four as rounded pills is why people click a status. */
.o-tabs { display: flex; gap: var(--sp-4); border-bottom: 1px solid var(--border); }
.o-tab {
  background: none; border: 0; cursor: pointer; padding: var(--sp-3) 0;
  font: var(--type-body); font-weight: 600; color: var(--text-muted);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.o-tab[aria-selected="true"] { color: var(--text); border-bottom-color: var(--brand); }
/* 3.73.0: a tab can be a link (routable record tabs) — it must not look like one. */
a.o-tab { display: inline-flex; align-items: center; text-decoration: none; }
.o-tab:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: var(--radius-sm); }

/* FOUND IN A BROWSER, NOT IN A STYLESHEET (7.21). `inline-flex` with fixed padding does not
   shrink, so on a 390px screen this ran 57px past the edge — in the admin sidebar footer, where
   the appearance control lives, not only in the showcase.
   It shrinks now: the segments give up padding before the control gives up the screen, and the
   labels are short enough that they still read. */
.o-segmented { display: inline-flex; max-inline-size: 100%; border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden; }
.o-segmented > button { flex: 0 1 auto; min-width: 0; white-space: nowrap;
  padding-inline: clamp(var(--sp-2), 2vw, var(--sp-4)); }
.o-segmented > button {
  background: var(--surface); border: 0; border-inline-end: 1px solid var(--border);
  padding: 0 var(--sp-4); height: var(--control-sm); cursor: pointer;
  font: var(--type-label); font-weight: 600; color: var(--text-muted);
}
.o-segmented > button:last-child { border-inline-end: 0; }
/* Selected, not branded.
   A saturated fill on every selected segment, chip and tab is how a brand colour ends up
   covering a third of the screen. Selection reads from weight and a quiet brand tint; the
   fill is kept for the one primary action on the page. */
.o-segmented > button[aria-pressed="true"] { background: var(--brand-soft); color: var(--brand-strong); }

.o-chip {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  height: var(--control-sm); padding: 0 var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  background: var(--surface); color: var(--text-muted);
  font: var(--type-label); font-weight: 600; cursor: pointer;
}
.o-chip[aria-pressed="true"] { background: var(--brand-soft); color: var(--brand-strong); border-color: color-mix(in srgb, var(--brand) 35%, var(--border)); }

/* Status is read, not pressed, so it is square-ish and has no hover. */
.o-badge {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  padding: 2px var(--sp-2); border-radius: var(--radius-sm);
  font: var(--type-caption); font-weight: 600;
  background: var(--surface-2); color: var(--text-muted); border: 1px solid var(--border);
}
.o-badge--success { color: var(--success-text); border-color: color-mix(in srgb, var(--success) 40%, var(--border)); background: color-mix(in srgb, var(--success) 10%, transparent); }
.o-badge--warning { color: var(--warning-text); border-color: color-mix(in srgb, var(--warning) 45%, var(--border)); background: color-mix(in srgb, var(--warning) 12%, transparent); }
.o-badge--danger  { color: var(--danger-text);  border-color: color-mix(in srgb, var(--danger) 40%, var(--border));  background: color-mix(in srgb, var(--danger) 10%, transparent); }
.o-badge--info    { color: var(--info-text);    border-color: color-mix(in srgb, var(--info) 40%, var(--border));    background: color-mix(in srgb, var(--info) 10%, transparent); }

/* A tag is metadata — Bakery, Egypt, Wholesale — and carries no state at all. */
.o-tag {
  display: inline-flex; padding: 2px var(--sp-2); border-radius: var(--radius-pill);
  background: var(--surface-2); color: var(--text-muted); font: var(--type-caption);
}

/* ── surfaces ──────────────────────────────────────────────────────────────
   A card is an object or a group, not the default wrapper for every section. Sections
   inside a card are separated by a rule. */
.o-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: var(--sp-4);
}
.o-card--flush { padding: 0; }
.o-card__head { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-4); border-bottom: 1px solid var(--border); }
.o-card__body { padding: var(--sp-4); }
.o-divider { height: 1px; background: var(--border); border: 0; margin: var(--sp-4) 0; }

.o-page-header { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-2) var(--sp-4); margin-bottom: var(--sp-4); }
.o-page-header .o-scope { font: var(--type-body); color: var(--text-muted); }
.o-page-header .o-actions { margin-inline-start: auto; display: flex; gap: var(--sp-2); }

/* ── tables ────────────────────────────────────────────────────────────────
   Desktop is a table. Mobile is not the same table narrower — each row becomes a
   stacked record, which is a different component wearing the same class. */
.o-table { width: 100%; border-collapse: collapse; font: var(--type-body); }
.o-table th, .o-table td { text-align: start; padding: var(--sp-3) var(--sp-2); border-bottom: 1px solid var(--border); }
.o-table th { font: var(--type-label); color: var(--text-muted); font-weight: 600; }
.o-table td.o-num, .o-table th.o-num { text-align: end; font-variant-numeric: tabular-nums; }

@media (max-width: 720px) {
  .o-table, .o-table tbody, .o-table tr, .o-table td { display: block; width: 100%; }
  .o-table thead { display: none; }
  .o-table tr { padding: var(--sp-3) 0; border-bottom: 1px solid var(--border); }
  .o-table td { border: 0; padding: 2px 0; }
  /* The header the cell lost, carried on the cell itself. */
  .o-table td[data-th]::before { content: attr(data-th) " "; color: var(--text-muted); font: var(--type-caption); }
}

/* ── states ────────────────────────────────────────────────────────────────
   Empty is an invitation, not an apology, so it gets an action. */
.o-empty { padding: var(--sp-8) var(--sp-6); text-align: center; color: var(--text-muted); }
.o-empty b { display: block; color: var(--text); font: var(--type-card); margin-bottom: var(--sp-2); }
.o-empty .o-btn { margin-top: var(--sp-4); }

.o-skeleton {
  background: linear-gradient(90deg, var(--surface-2), var(--border), var(--surface-2));
  background-size: 200% 100%; border-radius: var(--radius-sm);
  animation: o-shimmer 1.2s linear infinite; min-height: 12px;
}
@keyframes o-shimmer { to { background-position: -200% 0; } }

.o-alert { padding: var(--sp-3) var(--sp-4); border-radius: var(--radius-md); border: 1px solid var(--border);
  border-inline-start-width: 3px; font: var(--type-body); background: var(--surface-2); }
.o-alert--success { border-inline-start-color: var(--success); }
.o-alert--warning { border-inline-start-color: var(--warning); }
.o-alert--danger  { border-inline-start-color: var(--danger); }
.o-alert--info    { border-inline-start-color: var(--info); }

/* Offline is its own state and not an error: nothing has failed, and the thing to say
   is what will happen when the connection returns. */
.o-offline { display: inline-flex; align-items: center; gap: var(--sp-2); font: var(--type-label); color: var(--warning); }

/* ── layout widths ─────────────────────────────────────────────────────── */
.o-page { padding: var(--sp-4); }
@media (min-width: 900px) { .o-page { padding: var(--sp-6) var(--sp-8); } }
.o-width-settings { max-width: var(--width-settings); }
.o-width-form { max-width: var(--width-form); }

/* Focus is never removed for looks. Somebody navigating by keyboard is somebody who
   cannot use the product without it. */
:where(.o-btn, .o-tab, .o-chip, .o-input, .o-select, .o-textarea):focus-visible {
  outline: none; box-shadow: var(--focus-ring);
}

@media (prefers-reduced-motion: reduce) {
  .o-skeleton, .o-btn[data-loading="true"]::after { animation: none; }
  .o-btn, .o-input, .o-select, .o-textarea { transition: none; }
}

/* ── the build stamp ───────────────────────────────────────────────────────
   At the foot of the menu in all three applications. Quiet on purpose: it is the first
   thing anyone reporting a bug is asked for and the last thing anyone using the product
   needs to read, so it sits below the actions in caption type and muted colour. */
.o-version {
  margin-top: var(--sp-3); padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
  font: var(--type-caption); color: var(--text-muted);
  text-align: center; letter-spacing: .02em;
  font-variant-numeric: tabular-nums;
}
.o-version:empty { display: none; border-top: 0; margin: 0; padding: 0; }

/* The appearance control, at the foot of the menu beside the build stamp. */
.o-appearance { margin-top: var(--sp-3); display: flex; justify-content: center; }
.o-appearance .o-segmented > button { font-size: 11.5px; padding: 0 var(--sp-3); }
/* A locked surface states its one appearance rather than offering a switch. */
.o-appearance-locked {
  font: var(--type-caption); color: var(--text-muted);
  border: 1px dashed var(--border); border-radius: var(--radius-pill);
  padding: 3px var(--sp-3);
}

/* ======================================================================== 3.73.0
   Experience System — the UI primitives the application components are built from. Tokens
   only; `check-design-drift` holds public/design/components.* and this block to zero raw
   values. Nothing here is used by an existing screen yet: screens migrate onto it phase by
   phase, and a screen with two visual systems on it is the thing that rule prevents. */

/* Status: one component, five intents. Modules supply the meaning; this supplies the look. */
.o-badge--neutral { color: var(--status-neutral); }
.o-status { display: inline-flex; align-items: center; gap: var(--sp-1); white-space: nowrap;
  padding: 0 var(--sp-2); height: calc(var(--control-sm) - var(--sp-2)); border-radius: var(--radius-sm);
  font: var(--type-caption); font-weight: var(--fw-semibold); letter-spacing: .02em; text-transform: uppercase;
  color: var(--status-neutral); background: var(--surface-subtle); }
.o-status::before { content: ""; width: var(--sp-1); height: var(--sp-1); border-radius: var(--radius-pill); background: currentColor; }
.o-status--info    { color: var(--info-text);    background: color-mix(in srgb, var(--info) 10%, transparent); }
.o-status--success { color: var(--success-text); background: color-mix(in srgb, var(--success) 10%, transparent); }
.o-status--warning { color: var(--warning-text); background: color-mix(in srgb, var(--warning) 12%, transparent); }
.o-status--danger  { color: var(--danger-text);  background: color-mix(in srgb, var(--danger) 10%, transparent); }

/* Checkbox: the native control, sized and tinted — a custom box is a keyboard bug waiting. */
.o-check { display: inline-flex; align-items: center; gap: var(--sp-2); cursor: pointer; font: var(--type-body);
  min-block-size: var(--sp-6); }   /* 11.43.0 (DW4, measured): the whole label is the target, at least 24 px tall (WCAG 2.2) */
.o-check > input { width: var(--icon-sm); height: var(--icon-sm); margin: 0; accent-color: var(--brand); cursor: pointer; }

/* Icon: one family (components.js), sized by token, inheriting colour. */
.o-icon { width: var(--icon-md); height: var(--icon-md); flex: none; stroke: currentColor; fill: none;
  stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
.o-icon--sm { width: var(--icon-sm); height: var(--icon-sm); }
[dir="rtl"] .o-icon--flip { transform: scaleX(-1); }
/* The frozen-column seam falls on the other side of the row in Arabic. */
[dir="rtl"] { --seam-pin: -8px 0 8px -8px color-mix(in srgb, var(--text) 22%, transparent); }

/* Tooltip: CSS only, from aria-label, for icon buttons whose meaning is not in words. */
.o-tip { position: relative; }
.o-tip:is(:hover, :focus-visible)::after { content: attr(aria-label); position: absolute; inset-block-start: calc(100% + var(--sp-1));
  inset-inline-start: 50%; transform: translateX(-50%); z-index: var(--z-menu); pointer-events: none; white-space: nowrap;
  padding: var(--sp-1) var(--sp-2); border-radius: var(--radius-sm); font: var(--type-caption);
  background: var(--inverse-surface); color: var(--inverse-text); }

/* Menu: a popover list. Fade plus a small move, from the token — no bounce. */
.o-menu { position: absolute; z-index: var(--z-menu); min-width: calc(var(--sidebar-collapsed) * 3); padding: var(--sp-1);
  background: var(--surface-raised); border: 1px solid var(--border); border-radius: var(--radius-md); box-shadow: var(--elev-2);
  opacity: 0; transform: translateY(calc(var(--move-menu) * -1)); pointer-events: none;
  transition: opacity var(--dur-menu) var(--ease-standard), transform var(--dur-menu) var(--ease-standard); }
.o-menu[data-open="true"] { opacity: 1; transform: none; pointer-events: auto; }
.o-menu__item { display: flex; align-items: center; gap: var(--sp-2); width: 100%; min-height: var(--control-sm);
  padding: 0 var(--sp-3); border: 0; border-radius: var(--radius-sm); background: none; cursor: pointer;
  font: var(--type-body); color: var(--text); text-align: start; }
.o-menu__item:is(:hover, :focus-visible) { background: var(--surface-subtle); outline: none; }
.o-menu__item--danger { color: var(--danger-text); }
.o-menu__sep { height: 1px; margin: var(--sp-1) 0; background: var(--border); }
.o-menu__label { padding: var(--sp-1) var(--sp-3); font: var(--type-label); color: var(--text-muted); }

/* Drawer: slides in from the right edge, in both directions (10.89.0; see the rtl rule below). */
.o-drawer-scrim { position: fixed; inset: 0; z-index: calc(var(--z-sheet) - 1); background: color-mix(in srgb, var(--inverse-surface) 40%, transparent);
  opacity: 0; transition: opacity var(--dur-menu) var(--ease-standard); }
/* THE KEYBOARD IS PART OF THE VIEWPORT AND `vh` DOES NOT KNOW IT (6.9).
 *
 * `inset-block: 0` makes a sheet as tall as the WINDOW, which on iOS does not shrink when the
 * keyboard opens: the footer — with Add, or Save — sits behind the keyboard, and somebody who
 * starts typing loses the button they were about to press. Pinching out to reach it is what
 * people do instead, which is a bug report shaped like a workaround.
 *
 * `--vvh` is the visual viewport's height in pixels, set by `app.js` from the `visualViewport`
 * API on every resize — the only thing that reports the keyboard. It falls back to `100dvh` on a
 * browser that has neither, which is the same as before. */
.o-drawer { position: fixed; inset-block-start: 0; inset-inline-end: 0; z-index: var(--z-sheet);
  height: var(--vvh, 100dvh); width: min(var(--dialog-md), 100vw);
  display: flex; flex-direction: column; background: var(--surface-raised); box-shadow: var(--elev-3);
  transform: translateX(100%); transition: transform var(--dur-menu) var(--ease-standard); }
/* 10.89.0 (register ORV-P2-ARABIC-REQUEST-DRAWER-SIDE-001, QA plan A-RTL01): in Arabic the drawer also opens on the RIGHT.
   Mirrored, it opened from the left edge — away from the right-hand navigation and from where an Arabic reader's eye
   starts. Inline-START under rtl is the right edge, so this stays a logical rule. */
[dir="rtl"] .o-drawer { inset-inline-end: auto; inset-inline-start: 0; transform: translateX(100%); }
.o-drawer[data-open="true"] { transform: none; }
.o-drawer-scrim[data-open="true"] { opacity: 1; }
/* Below the status bar, not under it: the title was sharing pixels with the clock. */
.o-drawer__head { display: flex; align-items: center; gap: var(--sp-2);
  padding: calc(var(--sp-4) + env(safe-area-inset-top, 0px)) var(--sp-4) var(--sp-4);
  border-bottom: 1px solid var(--border); }
.o-drawer__title { font: var(--type-section); margin: 0; flex: 1; min-width: 0; overflow-wrap: anywhere; }
/* Nothing may push the drawer sideways (6.45). A field is 100% of the body and the body has
   padding, so any control with its own width — a number input the browser sizes to its content,
   a long code — made the whole sheet scroll horizontally, and the close button and the primary
   action went off the right edge. `min-width: 0` lets a flex child shrink; without it the child's
   content width wins and the parent gives way. */
.o-drawer__body { flex: 1; min-width: 0; overflow-y: auto; overflow-x: hidden; padding: var(--sp-4); }
.o-drawer__body > * { max-width: 100%; }
.o-drawer__body input, .o-drawer__body select, .o-drawer__body textarea { max-width: 100%; }
/* The footer stays put while the body scrolls, and clears the home indicator. */
.o-drawer__foot { display: flex; justify-content: flex-end; gap: var(--sp-2); flex: 0 0 auto;
  padding: var(--sp-4) var(--sp-4) calc(var(--sp-4) + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--border); background: var(--surface-raised); 
  /* Lifted clear of the keyboard, and of the home indicator. A drawer's footer is where Save
     lives, so this is the one that matters most (7.02). */
  padding-bottom: calc(var(--sp-3) + env(safe-area-inset-bottom, 0px) + var(--kb));
}

@media (prefers-reduced-motion: reduce) {
  .o-menu, .o-drawer, .o-drawer-scrim { transition: none; }
}

/* Controls size to their container including padding and border. Without this an input at
   width:100% overflowed its drawer by its own padding while a select beside it fit — the
   browser defaults differ, and the difference showed. */
.o-input, .o-select, .o-textarea, .o-drawer, .o-menu { box-sizing: border-box; }
.o-menu__item[disabled] { color: var(--text-disabled); cursor: not-allowed; background: none; }
/* A link styled as a button (the sidebar's POS link) must not keep the link underline. */
a.o-btn { text-decoration: none; }

/* A TAPPED BUTTON IS NOT HOVERED (8.25). Safari keeps :hover on whatever was last touched, so on
   the iPad "Expand menu" stayed on screen over the first tab until the next tap somewhere else.
   A pointer that cannot hover gets the tooltip on keyboard focus only. */
@media (hover: none) {
  .o-tip:hover:not(:focus-visible)::after,
  .o-sidebar[data-collapsed="true"] .o-tip:hover:not(:focus-visible)::after { display: none !important; }
}

/* 9.99.2 (QA 9.90.3 P2): TOUCH TARGETS ARE 44px. On a touch screen or a phone-width window every control a finger
   presses — buttons, icon buttons, inputs, selects (the header's company and branch included), the toggles of the
   older screens — is at least 44px tall; an icon button is 44px square. Desktop with a mouse keeps its denser sizes. */
@media (pointer: coarse), (max-width: 760px) {
  .o-btn, .o-input, .o-select, select, .btn, .tog, button.o-sections__item, .o-menu__item,
  header select, header button { min-block-size: 44px; }
  .o-btn--icon { min-inline-size: 44px; }
}

/* 11.72.0 (A11Y-09): a real label above a field that used to be named only by its placeholder (dialogs.js labelFields). */
.o-flabel { display: block; margin: var(--sp-2) 0 var(--sp-1); font: var(--type-small); font-weight: 600; color: var(--text); text-align: start; }
/* 11.72.0: a label above its field inside a two-column row (console: Create a business, the demo sheet) */
.o-fcol { display: flex; flex-direction: column; min-inline-size: 0; }
[aria-invalid="true"] { border-color: var(--danger) !important; }
