@layer infine.components {

/* ── Field-state colour tokens ─────────────────────────────────────────
   Single source of truth for what the input wrapper background, overlay
   (the "wash" painted on top of the bg), pulse (drain animation colour),
   and input error border resolve to at any given moment. The cljs side
   stamps `data-state="..."` / `data-just-completed` / `data-error-cleared`
   on the block; the rules below rebind these vars per state. Consumers
   (wrapper, input border, drain keyframes) read the vars and never have
   to know which state they're in.

   Replaces 4 of the 6 `:not(.connect-composite-row *)` carve-outs the file
   used to carry: composite repeating-row fields (ingredient-origins)
   reset the tokens to `transparent` here in one place, and the wash,
   drain, error background, and error-cleared rules read those vars
   instead of nesting in long descendant chains.

   Two carve-outs remain — error input border and shake — because they
   don't reduce to a var: there's no shared default to "reset to" when
   the state ends; Mantine's default border is applied by cascade, not
   by a property we own. Keeping them explicit at the rule level is
   cheaper than inventing a sentinel value. */
.connect-field-block {
    position: relative;

    --field-bg:      var(--infine-blue-bg-tint);
    --field-overlay: transparent;
    --pulse-bg:      var(--infine-yellow-bg);
}

.connect-field-block[data-state="required-empty"] {
    --field-overlay: var(--infine-yellow-bg);
}

.connect-field-block[data-state="error"] {
    --field-bg: var(--infine-red-bg);
}

/* When the user just cleared an error, the drain plays in red — "the red
   is leaving" rather than the yellow "required satisfied" cue. */
.connect-field-block[data-error-cleared="true"] {
    --pulse-bg: var(--infine-red-bg);
    --pulse-border: var(--infine-red-strong);
}

/* Composite repeating-row fields (ingredient-details, weight-list) own their
   own chromeless visual treatment. Resetting the tokens makes the field-block's
   wash, drain, and error paint a no-op inside them — no descendant carve-out
   needed in the rules below. */
.connect-composite-row {
    --field-bg:      transparent;
    --field-overlay: transparent;
    --pulse-bg:      transparent;
}

/* Soft reveal animation for fields whose visibility depends on another
   field (e.g. CO₂ calculation method fields gated by carbon-footprint).
   Fades + slides up over ~280ms when React mounts the element. Each
   subsequent reveal-tagged sibling in the same group gets a small
   stagger via nth-of-type so the four CO₂ follow-ups don't all
   snap-in at the same moment. */
@keyframes connect-field-reveal {
    from {
        opacity: 0;
        transform: translateY(6px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.connect-field-block--reveal {
    animation: connect-field-reveal 320ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

.connect-field-block--reveal:nth-of-type(1) { animation-delay: 0ms; }
.connect-field-block--reveal:nth-of-type(2) { animation-delay: 50ms; }
.connect-field-block--reveal:nth-of-type(3) { animation-delay: 100ms; }
.connect-field-block--reveal:nth-of-type(4) { animation-delay: 150ms; }
.connect-field-block--reveal:nth-of-type(n+5) { animation-delay: 200ms; }

/* ── Wash + drain — where the state tokens land visually ──────────────
   The wash + drain are layered on the `.mantine-Input-wrapper` (a plain
   div Mantine renders around every input), not on the input element
   itself. Why: Mantine's `NumberInput` is internally wrapped by
   react-number-format, which re-applies `name` / `type` / `value`
   attributes on the inner `<input>` on every render — when the wash +
   drain ran on the input directly, those attribute writes coincided
   with the animation start and the bg-drain on numeric fields would
   skip ahead / appear to play much faster than on text inputs and
   selects. The wrapper is a stable DOM node no Mantine internal
   touches, so the animation plays cleanly there for every field type.

   Trade-off: the input itself must be transparent so the wrapper's bg
   shows through. The wrapper holds the bg-color (carrier) and bg-image
   (overlay/wash) with matching border-radius. Borders, focus glow,
   error border, and the shake animation all stay on the input. */

/* Input is transparent so the wrapper's bg + wash show through. */
.connect-field-block .mantine-Input-input,
.connect-field-block input,
.connect-field-block select,
.connect-field-block textarea {
    background-color: transparent;
    background-image: none;
    transition:
        border-color 380ms cubic-bezier(0.22, 1, 0.36, 1),
        box-shadow 380ms cubic-bezier(0.22, 1, 0.36, 1),
        color 380ms cubic-bezier(0.22, 1, 0.36, 1);
}
/* Focus stays transparent — the wrapper's bg supplies the visible bg. */
.connect-field-section .mantine-Input-input:focus,
.connect-field-section .mantine-Input-input:focus-within {
    background-color: transparent;
}

/* The wrapper is the bg carrier. Reads --field-bg (the persistent
   surface colour) and --field-overlay (the washed state colour, painted
   as a linear-gradient so the drain animation can scale it). */
.connect-field-block .mantine-Input-wrapper {
    background-color: var(--field-bg);
    background-image: linear-gradient(var(--field-overlay), var(--field-overlay));
    background-repeat: no-repeat;
    background-position: bottom;
    background-size: 100% 100%;
    border-radius: var(--mantine-radius-default, 4px);
    transition: background-color 380ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Bottom border on the input echoes the yellow wash with the brighter accent.
   Paired with the wrapper bg above — both leave together via the drain
   keyframes below. Recolours Mantine's existing 1 px input border-bottom
   rather than adding a new border, so layout stays untouched. Origin rows
   opt out via :not(.connect-composite-row *) — composite repeating-row fields
   handle their own visual on the row, not on individual cell inputs. */
.connect-field-block[data-state="required-empty"] .mantine-Input-input:not(.connect-composite-row *):not(.connect-seafood-subrow *),
.connect-field-block[data-state="required-empty"] input:not(.connect-composite-row *):not(.connect-seafood-subrow *):not([type="checkbox"]):not([type="radio"]),
.connect-field-block[data-state="required-empty"] select:not(.connect-composite-row *):not(.connect-seafood-subrow *),
.connect-field-block[data-state="required-empty"] textarea:not(.connect-composite-row *):not(.connect-seafood-subrow *) {
    border-bottom-color: var(--infine-yellow-bright);
}

/* Checkboxes/radios are too small to host the drain — use bg-color directly
   for their required-empty wash. The `:not(.connect-composite-row *)` carve-out
   keeps the per-ingredient Seafood checkboxes (which live inside an origin row)
   from inheriting the whole field's required-empty state — a single row's
   missing species must not light up every other row's unticked checkbox. */
.connect-field-block[data-state="required-empty"] .mantine-Checkbox-input:not(:checked):not(.connect-composite-row *),
.connect-field-block[data-state="required-empty"] .mantine-Radio-radio:not(:checked) {
    background-color: var(--infine-yellow-bg);
    border-color: var(--infine-yellow-bright);
    transition:
        background-color 420ms cubic-bezier(0.22, 1, 0.36, 1),
        border-color 360ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Empty repeating-table placeholder (e.g. ingredient-details with no rows).
   When the parent field is required but has no rows yet, surface that
   required-empty state via the same yellow wash + dashed border — reusing
   the field-state vocabulary so the placeholder reads consistently with
   single-input fields. */
.connect-field-block .connect-empty-table {
    border: 1px dashed transparent;
    border-radius: 8px;
    transition:
        background-color 420ms cubic-bezier(0.22, 1, 0.36, 1),
        border-color 360ms cubic-bezier(0.22, 1, 0.36, 1);
}

.connect-field-block[data-state="required-empty"] .connect-empty-table {
    background-color: var(--infine-yellow-bg);
    border-color: var(--infine-yellow-bright);
    color: var(--infine-text);
}

/* Drain: on the moment a required field flips to filled, the wrapper's
   wash drains downward via background-size scaleY-equivalent. The keyframe
   reads --pulse-bg so the same animation can theme to red when an error
   is cleared. End-state is a 0-height gradient, so initial-mount play is
   silent on already-completed fields. */
@keyframes connect-field-bg-drain {
    0%   { background-image: linear-gradient(var(--pulse-bg), var(--pulse-bg));
           background-size: 100% 100%; }
    100% { background-image: linear-gradient(var(--pulse-bg), var(--pulse-bg));
           background-size: 100% 0%; }
}

/* The drain animation is gated on `data-just-completed`, set by cljs only
   when a field's state has actually transitioned into filled / optional-filled.
   This prevents the animation from replaying every time a collapsed section
   re-mounts and exposes already-filled inputs. Origin-row wrappers receive
   the animation too, but with --pulse-bg=transparent it paints nothing. */
.connect-field-block[data-just-completed="true"] .mantine-Input-wrapper {
    animation: connect-field-bg-drain 460ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
}

/* Input border-bottom drains in lockstep with the wrapper bg above. Same
   duration / easing / gating so the wash and the border leave together; if
   they were timed independently one signal would pop off ahead of the other.
   End-state is the resting input border colour (--infine-blue-light-fade,
   see connect-field-section input rules) so the field doesn't flash to a
   different colour after the animation lifts. --pulse-border mirrors
   --pulse-bg: the [data-error-cleared] block above sets it to red so the
   "red is leaving" flow drains its border the same way. */
@keyframes connect-field-input-border-drain {
    0%   { border-bottom-color: var(--pulse-border, var(--infine-yellow-bright)); }
    100% { border-bottom-color: var(--infine-blue-light-fade); }
}
.connect-field-block[data-just-completed="true"] .mantine-Input-input:not(.connect-composite-row *),
.connect-field-block[data-just-completed="true"] input:not(.connect-composite-row *):not([type="checkbox"]):not([type="radio"]),
.connect-field-block[data-just-completed="true"] select:not(.connect-composite-row *),
.connect-field-block[data-just-completed="true"] textarea:not(.connect-composite-row *) {
    animation: connect-field-input-border-drain 460ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
}

/* Error escalation: red border on the input. Errors are rare, so it's
   fine to spend a little colour budget here. The `:not(.connect-composite-row *)`
   carve-out keeps composite repeating-row fields from auto-painting every
   child input red when one row violates — origin rows opt in per-row via
   `.connect-origin-row--error` in connect-origins-table.css. (The wash + bg
   were eliminated as carve-outs by the var-token approach above; this border
   override doesn't reduce to a var because there's no shared default to
   "reset to" when the state ends — Mantine's default is reapplied via
   cascade, not via a property we own.) */
.connect-field-block[data-state="error"] .mantine-Input-input:not(.connect-composite-row *):not(.connect-seafood-subrow *),
.connect-field-block[data-state="error"] input:not(.connect-composite-row *):not(.connect-seafood-subrow *),
.connect-field-block[data-state="error"] select:not(.connect-composite-row *):not(.connect-seafood-subrow *),
.connect-field-block[data-state="error"] textarea:not(.connect-composite-row *):not(.connect-seafood-subrow *) {
    border-color: var(--infine-red-strong);
}

/* Shake on the moment a field enters :error. Small horizontal nudge — two
   beats either side, settles back to centre — is the standard "something's
   wrong here" cue. Amplitude kept tight (3 px) so it reads as a flinch, not
   a thrash. Gated by `data-just-errored` so it fires only on the transition,
   not while the field sits in :error. Same carve-out as the error border:
   origin rows have their own per-row shake on the row, not the input. */
@keyframes connect-field-shake {
    0%, 100% { transform: translateX(0); }
    15%      { transform: translateX(-3px); }
    35%      { transform: translateX(3px); }
    55%      { transform: translateX(-2px); }
    75%      { transform: translateX(2px); }
    90%      { transform: translateX(-1px); }
}
.connect-field-block[data-just-errored="true"] .mantine-Input-input:not(.connect-composite-row *),
.connect-field-block[data-just-errored="true"] input:not(.connect-composite-row *),
.connect-field-block[data-just-errored="true"] select:not(.connect-composite-row *),
.connect-field-block[data-just-errored="true"] textarea:not(.connect-composite-row *) {
    animation: connect-field-shake 420ms cubic-bezier(0.36, 0.07, 0.19, 0.97);
}

.connect-field-error {
    margin-top: var(--infine-spacing-xxs);
    font-size: var(--mantine-font-size-xs);
    font-weight: 600;
    color: var(--infine-red-bright);
}

/* Marginalia anchor marker on the field-block label. Renders only when the
   field carries :help (the cljs side gates the element); colour follows the
   field-block's data-state so required-empty markers pick up the same orange
   the stripe uses, and errors echo in red. Filled / optional fields use a
   quiet neutral. */
/* Numbered footnote chip beside the label of helped fields. Mirrors the
   marginalia entry's marker pixel-for-pixel so the pairing reads as one
   shared affordance, not two coincidentally similar tags. Neutral by
   default — every state colour the form needs already lives on the
   stripe and pills. Rounded-rectangle shape (not circle) and Open Sans
   numerals — Raleway's figure kerning looks pinched inside a small
   round badge; Open Sans renders cleaner at this size. */
.connect-field-help-marker {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    min-width: 18px;
    height: 16px;
    padding: 0 5px;
    flex: 0 0 auto;
    margin-left: 2px;
    border-radius: 4px;
    background: var(--mantine-color-gray-1);
    color: var(--mantine-color-gray-7);
    font-family: var(--mantine-font-family);
    font-size: 11px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    line-height: 1;
    transition: background-color 0.18s ease,
                color 0.18s ease,
                box-shadow 0.18s ease;
}

/* Coupled state — hover or focus on this field-block (or on its matching
   marginalia entry) lights the chip up so the pairing reads as a positive
   confirmation while the user is interacting with either side. */
.connect-field-block:hover > .connect-field-label .connect-field-help-marker,
.connect-field-block:focus-within > .connect-field-label .connect-field-help-marker,
.connect-field-block[data-coupled="true"] > .connect-field-label .connect-field-help-marker {
    background: var(--infine-text, var(--mantine-color-gray-9));
    color: white;
    box-shadow: 0 0 0 2px var(--mantine-color-gray-0);
}

/* Field that doesn't apply to the current product (e.g. egg method on a
   non-egg product). We still render it during the design-evaluation phase
   so the form's shape is visible, but dim the label/source-line and
   disable the input. The N/A tag replaces the REQUIRED/OPTIONAL badge. */
.connect-field-block--inapplicable .connect-field-label {
    color: var(--mantine-color-gray-6);
}

.connect-field-block--inapplicable .connect-source-line {
    opacity: 0.55;
}

.connect-field-label .connect-field-na {
    margin-left: auto;
    padding: 1px 6px;
    border-radius: 4px;
    font-size: var(--mantine-font-size-xs);
    font-weight: 600;
    color: var(--mantine-color-gray-6);
    background: var(--mantine-color-gray-1);
    letter-spacing: 0;
}

/* <fieldset> wrapper used to disable composite renderers (ingredient-list,
   ingredient-origins, nutrition, weight-list) when the field is N/A.
   Strip default fieldset chrome; reuse the disabled styling on inputs. */
.connect-field-fieldset {
    border: 0;
    margin: 0;
    padding: 0;
    min-width: 0;
}

.connect-field-fieldset[disabled] {
    opacity: 0.55;
    pointer-events: none;
}
}
