/* ==========================================================================
   Riverview Vets - component layer

   Extracted at BLD-01 on 2026-09-16 from the Claude Design export:
   _ds/riverview-vets-design-system-4809c4a8-4feb-433c-b774-bbc7c3d26c7f/
     _ds_bundle.js

   Every declaration below is a React inline style translated one for one.
   Where the bundle drove a state in JavaScript (hover, press, focus, error)
   the CSS uses the equivalent selector instead, so the component keeps its
   behaviour with JavaScript disabled.

   Do not edit to taste. If a value looks wrong, check the bundle first.
   The working is in development_plan/BLD-01-components.md.
   ========================================================================== */


/* --- Button ---------------------------------------------------------------
   Source: components/core/Button.jsx
   Renders as <button> or <a>; the design uses both. Variants shipped here are
   primary, outline and ghost. The bundle also defines secondary and quiet and
   a size sm, none of which the design uses; they are recorded in the
   reference doc rather than carried as dead CSS.
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-sm);
  font-family: var(--font-sans);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  border: var(--border-width) solid transparent;
  border-radius: var(--radius-control);
  cursor: pointer;
  transition: var(--motion-hover);
  text-decoration: none;
  white-space: nowrap;
}

.btn--md { padding: 12px 24px; font-size: var(--fs-body); }
.btn--lg { padding: 16px 32px; font-size: var(--fs-lead); }

.btn--primary { background: var(--coral); color: var(--white); border-color: var(--coral); }
.btn--primary:hover { background: var(--coral-600); border-color: var(--coral-600); }

.btn--outline { background: transparent; color: var(--forest); border-color: var(--forest); }
.btn--outline:hover { background: var(--forest); color: var(--cream-100); }

.btn--ghost { background: transparent; color: var(--forest); border-color: transparent; }
.btn--ghost:hover { background: var(--cream-200); }

/* The bundle applied the press transform on mousedown. :active covers mouse,
   touch and keyboard activation, and --press-scale is already 1 under
   prefers-reduced-motion, so this needs no media query of its own. */
.btn:active { transform: scale(var(--press-scale)); }

.btn--full { width: 100%; }

/* An <a> cannot carry the disabled attribute, so aria-disabled carries the
   semantics for the link form and the same styling applies to both. */
.btn:disabled,
.btn[aria-disabled="true"] { opacity: .45; pointer-events: none; }


/* --- Field: Input and Select ----------------------------------------------
   Source: components/forms/Input.jsx and components/forms/Select.jsx
   The two carry byte-identical labelStyle, hintStyle and controlStyle in the
   bundle, so they share one class here.

   The error state keys on [aria-invalid="true"], which the design already
   sets on every validated field. One attribute drives both the styling and
   what a screen reader announces, and there is no second class to forget.
   -------------------------------------------------------------------------- */

.field { width: 100%; }

.field__label {
  display: block;
  font-family: var(--font-sans);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-body);
  color: var(--text-heading);
  margin-bottom: var(--sp-sm);
}

.field__control {
  width: 100%;
  font-family: var(--font-sans);
  font-weight: var(--fw-regular);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--text-body);
  background: var(--white);
  padding: 12px 16px;
  border-radius: var(--radius-input);
  border: var(--border-width) solid var(--border-hairline);
  box-shadow: none;
  outline: none;
  transition: var(--motion-hover);
}

/* The control already carries `transition: var(--motion-hover)` and had no
   hover state to spend it on, so on a mouse there was nothing between resting
   and focused. A one-step border darkening is the affordance, in a warm
   neutral because tokens.css rules out blue-grey. Deliberately above :focus
   and [aria-invalid] - all three are (0,2,0), so the later rule wins and a
   hover can never mask a focus ring or an error border. A media query does
   not touch specificity or source position, so the gate below is free.

   GATED TO A REAL MOUSE. On a touch screen :hover latches after a tap and
   only clears on the next one, which would leave a field looking hovered
   with nothing hovering it. `pointer: fine` is the half that matters -
   `hover: hover` alone still matches a stylus and some hybrids. */
@media (hover: hover) and (pointer: fine) {
  .field__control:hover { border-color: var(--ink-300); }
}

.field__control:focus { border-color: var(--teal); box-shadow: var(--shadow-focus); }

/* Error beats focus, exactly as the ternary in the bundle does. Equal
   specificity to the rule above, so source order decides and this wins. */
.field__control[aria-invalid="true"] { border-color: var(--status-danger); }

.field__hint {
  font-family: var(--font-sans);
  font-weight: var(--fw-regular);
  font-size: var(--fs-caption);
  line-height: var(--lh-body);
  color: var(--text-muted);
  margin-top: var(--sp-sm);
}

.field__hint--error { color: var(--status-danger); }

/* Select only. The wrapper exists to position the caret. */
.field__select { position: relative; }

/* The prefixes are an addition, not an extraction: React shipped bare
   appearance, and without -webkit- Safari keeps its native chevron and draws
   it on top of the caret below. */
.field__control--select {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  padding-right: 44px;
  cursor: pointer;
}

.field__caret {
  position: absolute;
  right: 16px;
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
  color: var(--forest);
  font-size: 12px;
}


/* --- Icon -----------------------------------------------------------------
   Source: components/brand/Icon.jsx

   The bundle's Icon is a <span> whose background is currentColor, masked by
   an SVG fetched from a third-party CDN at paint time (the host is named in
   BLD-01-components.md, not here, so it stays out of an audit grep of src/).
   That request does not ship.
   The five icons the design uses are inlined as <svg> instead: no request,
   and currentColor works natively on a stroked path.

   Artwork: lucide-static v0.544.0, ISC licence.
   Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as
   part of Feather (MIT). All other copyright (c) for Lucide are held by
   Lucide Contributors 2022.
   -------------------------------------------------------------------------- */

.icon {
  display: inline-block;
  flex: 0 0 auto;
}


/* --- BLD-09 --------------------------------------------------------------- */

/* BLD-01 finding 3, decided here. `controlStyle` in the bundle sets
   `outline: none` and replaces the focus indicator with `--shadow-focus`, a
   3px teal at 35% alpha. BLD-01 read that as about #C2D8D7 and 1.4:1;
   composited properly it is #B8D1D0 and 1.61:1 over `--white`, and #B1C9C4 and
   1.56:1 over `--cream-100`. The estimate was low and the conclusion is
   unchanged - WCAG 2.2 SC 1.4.11 wants 3:1 for a focus indicator and the halo
   is not close to it on either background.

   BLD-01 offered two ways out - raise the alpha, or put the outline back for
   `:focus-visible` only. This does the second thing but on `:focus`, and the
   difference is worth the paragraph, because `:focus-visible` is what the
   note says and what anyone reading this would expect.

   MEASURED, in Chrome 153, with real mouse input rather than el.focus():
   clicking into `#rv-name` matches :focus-visible, and so does clicking into
   the `#rv-animal` select. That is not a quirk - the spec has any element that
   expects keyboard input always match - so on the controls this site actually
   has, `:focus-visible` and `:focus` select exactly the same moments and paint
   exactly the same ring. The design idea behind BLD-01's suggestion, that a
   mouse user would keep the soft ring alone, does not survive contact with a
   real click. It was never available to choose.

   Given they are identical today, `:focus` is the one that stays correct. The
   day someone adds a control to this form that does NOT expect keyboard input
   - a checkbox, a radio, a button wearing `.field__control` - `:focus-visible`
   stops matching on a click and that control silently falls back to the 1.6:1
   halo as its only indicator. `:focus` has no such edge, and it also covers
   the programmatic focus main.js performs when it sends a visitor to the first
   field they got wrong, which is precisely a moment to show them where they
   have been put.

   THE RULE HAS TO BE WRITTEN OUT RATHER THAN INHERITED. base.css's global
   `:focus-visible` is (0,1,0) and `.field__control` is (0,1,0) too, and
   components.css is loaded after base.css, so source order gives `outline:
   none` the win. `.field__control:focus` is (0,2,0) and settles it by
   specificity rather than by load order, which is what stops a future
   reshuffle of the six <link>s from silently deleting it.

   The soft ring is NOT removed. It still paints, under the outline, so the
   design's own look survives and what is added is the part that is legible:
   teal #347C7A is 4.87:1 against the field's white interior and 4.36:1
   against --cream-100 behind it. check-a11y.mjs computes both from the live
   token, so changing `--focus-ring` fails the build rather than a visitor. */
.field__control:focus {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* The send-failure line, which is real markup as of BLD-09 rather than an
   element JavaScript invents on a failed POST.

   It ships empty and it is never `hidden`, for the live-region reason in
   `.vh`: `role="alert"` on something that is not rendered can be announced by
   nothing. Empty, `.field__hint`'s `margin-top` would still reserve space
   under the submit button for a message that does not exist, so `:empty`
   takes the margin off. Text arrives, the margin comes back, and nothing
   toggles a style attribute - which `style-src 'self'` would refuse anyway. */
.field__hint--error:empty { margin-top: 0; }



/* --- The mobile vet opt-in -------------------------------------------------
   The client's second design, supplied 2026-09-23, replacing their Yes/Undo
   button design the same day: a checkbox and its sentence inside a
   full-width grape-bordered box. The box is the <label>, so the whole box is
   the click target. Figma component: file f9S4O4lKqcu8HZ8oMqSbiO, node 170:22.

   VALUES ARE FIGMA'S, read with get_design_context, not sampled from a
   screenshot. The fill is rgba(175, 70, 216, .1), written here as the solid
   colour it makes over white (#F7ECFB) so the box looks the same on the
   modal's white and the inline form's cream. Text is #334438 at 14px medium
   with normal line-height; the file has no token for #334438 or the lilac,
   so both are literals. Figma names Manrope, which this site does not ship;
   Figtree at the same size and weight is used instead of adding a font.

   The checkbox is drawn rather than native, because a native one ignores
   width and height in some browsers. The tick is a rotated border on
   ::after, not an SVG background: `img-src 'self'` refuses data: URIs.

   THE UNTICKED CHECKBOX EDGE IS THE DESIGN'S FAINT ONE, about 1.2:1 against the lilac, and that
   is a decision, not an oversight. SC 1.4.11 is met by the control as a
   whole: the box a visitor clicks is bounded by --grape at about 5.7:1 against
   the lilac, and the ticked square fills --grape, 5.7:1 against the lilac,
   with a white tick at 6.6:1 on it. check-a11y.mjs measures both. If an audit reads the 19px square as
   the control on its own, darken its border to #9E7EAA (3.05:1) and nothing
   else changes.
   -------------------------------------------------------------------------- */

.optin {
  display: flex;
  align-items: center;
  gap: 10px;
  border: var(--border-width) solid var(--grape);
  border-radius: var(--radius-input);
  background: #F7ECFB;
  padding: 12px 12px 12px 16px;
  cursor: pointer;
}

.optin__check {
  appearance: none;
  -webkit-appearance: none;
  flex: 0 0 auto;
  position: relative;
  width: 19px;
  height: 19px;
  margin: 0;
  background: var(--white);
  border: var(--border-width) solid rgba(120, 76, 137, .2);
  border-radius: var(--radius-control);
  cursor: pointer;
  transition: var(--motion-hover);
}

.optin__check:checked { background: var(--grape); border-color: var(--grape); }

/* Centred from the middle of the square rather than by pixel offsets, so it
   stays centred if the size changes. The -60% lifts it slightly: a tick's
   weight sits in its lower corner, so a mathematically centred one reads as
   low. */
.optin__check:checked::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 6px;
  height: 11px;
  border: solid var(--white);
  border-width: 0 2px 2px 0;
  transform: translate(-50%, -60%) rotate(45deg);
}

.optin__check:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

@media (hover: hover) and (pointer: fine) {
  .optin:hover .optin__check:not(:checked) { border-color: var(--grape); }
}

/* `text-wrap` is what stops the sentence leaving one word alone on the last
   line when it wraps, the defect seen in the modal at 2026-09-23. `pretty`
   where the browser has it; Firefox did not when this was written, so it
   takes `balance` from the line before, which it does have. */
.optin__text {
  flex: 1 1 0;
  min-width: 0;
  font-family: var(--font-sans);
  font-weight: var(--fw-medium);
  font-size: var(--fs-body-sm);
  line-height: 1.4;
  color: #334438;
  text-wrap: balance;
  text-wrap: pretty;
}
