@layer infine.components {
/* Ingredient origins editor */
.connect-origins-table {
    width: 100%;
    border-collapse: collapse;
}

/* Per-row error scoping for ingredient-origins. The field-block itself still
   gets `data-state="error"` so the field-level message shows below the whole
   field, but ONLY the offending row's country MultiSelect gets the red ring —
   the ingredient Select stays neutral so the user knows the problem is the
   country choice, not the ingredient. The row carries `data-error="true"` on
   the countries div, set at render time from the computed `bad` set. */
.connect-origin-row--error [data-col="countries"] .mantine-MultiSelect-root .mantine-Input-input,
.connect-origin-row--error [data-col="countries"] .mantine-MultiSelect-root .mantine-PillsInput-input {
    border-color: var(--infine-red-bright);
    box-shadow: 0 0 0 1px var(--infine-red-bright);
}

.connect-origin-row--error [data-col="countries"] .mantine-MultiSelect-root .mantine-Input-input:focus-within,
.connect-origin-row--error [data-col="countries"] .mantine-MultiSelect-root .mantine-PillsInput-input:focus-within {
    border-color: var(--infine-red-bright);
    box-shadow: 0 0 0 2px var(--infine-red-fade);
}

/* The specific offending country pill(s) get a red tint, so the user sees
   *which* country is implausible rather than just that the row is wrong. The
   class is toggled from JS (IngredientDetailRow) because Mantine's MultiSelect
   exposes no per-pill class API — see that component for the DOM walk. */
.connect-origin-bad-pill {
    background-color: var(--infine-red-fade) !important;
    color: var(--infine-red-strong) !important;
}

.connect-origin-bad-pill .mantine-Pill-remove,
.connect-origin-bad-pill button {
    color: var(--infine-red-strong) !important;
}

/* Per-row shake when an origin row's countries just transitioned into a
   violating set. Re-uses the standalone-field shake keyframes so the motion
   reads identical to the rest of the form's error cue. Scoped to the row's
   country MultiSelect — same scope as the red border above — so a single
   bad row's shake doesn't disturb sibling rows. */
.connect-origin-row[data-just-errored="true"] [data-col="countries"] .mantine-MultiSelect-root {
    animation: connect-field-shake 420ms cubic-bezier(0.36, 0.07, 0.19, 0.97);
}

/* Dotted leader between a nutrition-row label and its value. On wide windows
   the 1fr label column gets stretched far from the fixed 90 px input; the
   leader visually re-couples them. translate-Y nudges the dots up onto the
   text baseline rather than the descender line. */
.connect-nutrition-leader {
    flex: 1;
    min-width: 12px;
    border-bottom: 1px dotted var(--mantine-color-gray-4);
    transform: translateY(-3px);
}

.connect-origins-table th,
.connect-origins-table td {
    padding: 6px 8px;
    border-bottom: 1px solid var(--mantine-color-gray-2);
    text-align: left;
    vertical-align: middle;
    font-size: var(--mantine-font-size-sm);
}

.connect-origins-table th {
    font-weight: 600;
    color: var(--infine-blue);
    background: var(--infine-blue-bg);
}

/* ─────────────────────────────────────────────────────────────────────
   Chromeless composite-multi rows (shared by :ingredient-details and
   :weight-list, anchored on `.connect-composite-row`).

   Each row shows a few cells (ingredient/material + sub-values) plus a
   remove button. To stop the block from reading as a wall of bordered
   inputs, we strip the Mantine input chrome inside `.connect-composite-row`:
   no visible border, no background, no big padding. Pills carry their own X
   for removal; the residual "＋ Add …" placeholder is the affordance to add
   more.

   Variable pill counts no longer create a ragged grid because there are
   no bordered boxes to be uneven — top-aligned pill rows just have
   uneven trailing whitespace, which the eye accepts. Hover and focus
   bring a faint tint back so the cell still reads as editable.
   ──────────────────────────────────────────────────────────────────── */
.connect-composite-row .mantine-Input-input,
.connect-composite-row .mantine-PillsInput-input {
    --input-bg: transparent;
    --_input-bd: transparent;
    background-color: transparent;
    border-color: transparent;
    box-shadow: none;
    padding-inline: 6px;
    transition:
        background-color 120ms ease,
        border-color 120ms ease;
}

/* The bg-drain variant paints every .mantine-Input-wrapper inside a
   field-block with --infine-blue-bg-tint (so the required/error washes
   have something to drain on). That blue wash bleeds through here even
   though the inner .mantine-Input-input is transparent — kill it on the
   origin-row wrappers explicitly. Hover/focus cues live on the inner
   input, so dropping the wrapper bg doesn't lose anything. */
.connect-composite-row .mantine-Input-wrapper {
    background-color: transparent;
    background-image: none;
}

/* Subtle resting cue: the Mantine chevron on the single ingredient
   Select stays visible but quieter, so the cell reads as editable text
   rather than a heavy dropdown. */
.connect-composite-row [data-col="ingredient"] .mantine-Select-input {
    font-weight: 500;
    color: var(--infine-text);
}

.connect-composite-row .mantine-Combobox-chevron {
    color: var(--mantine-color-gray-5);
    opacity: 0.6;
    transition: opacity 120ms ease;
}

.connect-composite-row .mantine-Input-wrapper:hover .mantine-Combobox-chevron,
.connect-composite-row .mantine-Input-wrapper:focus-within .mantine-Combobox-chevron {
    opacity: 1;
}

/* Hover: faint background tint just to telegraph "this is interactive". */
.connect-composite-row .mantine-Input-input:hover,
.connect-composite-row .mantine-PillsInput-input:hover {
    background: var(--mantine-color-gray-1);
}

/* Focus-within: slightly denser tint + 1 px border. We use border-color
   so the row height doesn't jump on focus (border is already present, just
   transparent at rest). */
.connect-composite-row .mantine-Input-input:focus-within,
.connect-composite-row .mantine-PillsInput-input:focus-within {
    background: var(--mantine-color-gray-0);
    border-color: var(--mantine-color-gray-3);
}

/* "＋ Country" / "＋ Certification" placeholders are subordinate
   information — they shouldn't shout. Render at xs size and gray-5 so
   the pills and the row's data carry the primary visual weight. */
.connect-composite-row .mantine-Input-placeholder,
.connect-composite-row input::placeholder {
    color: var(--mantine-color-gray-5);
    font-weight: 400;
    font-size: var(--mantine-font-size-xs);
}

/* Keep the MultiSelect search input wide enough that the placeholder
   never gets clipped. Mantine's PillsInputField-field defaults to
   ~100 px min-width — too tight for "＋ Certification". Bump it so the
   input either fits inline with room for the placeholder or wraps to
   the next line cleanly (Cocoa-mass-with-two-pills case). */
.connect-composite-row .mantine-PillsInputField-field,
.connect-composite-row .mantine-MultiSelect-inputField {
    min-width: 105px;
}

/* Keep the placeholder visible in full — Mantine's input has no built-in
   ellipsis, but the parent PillsInput-input has `data-no-overflow` and a
   default text-overflow rule. Override so the placeholder either fits or
   the input wraps to the next line; never an ellipsis on placeholder. */
.connect-composite-row .mantine-PillsInputField-field {
    text-overflow: clip;
}

/* The remove trash icon sits at the row's right edge. Quiet at rest,
   appears on row hover/focus. Keeps the row visually clean when not
   being interacted with. */
.connect-composite-row .connect-composite-row-remove {
    opacity: 0;
    transition: opacity 120ms ease;
}

/* Ingredient rows are top-aligned (cells grow when country/cert pills wrap),
   so the icon is nudged down onto the row's first input line — matching the
   seafood checkbox's vertical position in the next column. Weight-list rows
   are centre-aligned (single-line cells) and need no nudge. */
.connect-origin-row .connect-composite-row-remove {
    margin-top: 6px;
}

.connect-composite-row:hover .connect-composite-row-remove,
.connect-composite-row:focus-within .connect-composite-row-remove {
    opacity: 0.65;
}

.connect-composite-row .connect-composite-row-remove:hover,
.connect-composite-row .connect-composite-row-remove:focus-visible {
    opacity: 1;
}

/* Hairline divider under every row, including the last one — closes the
   table's bottom edge so the block reads as a contained dataset. No
   per-cell borders so the cells still scan as one continuous row. */
.connect-composite-row {
    padding-block: 6px;
    border-bottom: 1px solid var(--mantine-color-gray-2);
}

/* Match the header row's column gutters/padding so it aligns above the
   first row's divider, and give it the same hairline so it reads as the
   table's top edge. */
.connect-composite-headers {
    border-bottom: 1px solid var(--mantine-color-gray-3);
    padding-block-end: 6px !important;
}

/* ─────────────────────────────────────────────────────────────────────
   Seafood: per-ingredient toggle + revealed detail sub-row.

   The "Seafood" checkbox is a 6th column in the ingredient-details grid.
   Ticking it reveals `.connect-seafood-subrow` — an inset detail band
   tucked under the row (deliberately NOT aligned to the table columns)
   that captures the fish species (and, when enabled, production method).
   A required-but-empty species shows the yellow required-empty treatment,
   matching the field-block vocabulary — never red (red is errors only).
   ──────────────────────────────────────────────────────────────────── */

/* Centre the checkbox in its column, nudged down onto the row's input
   baseline (the grid cells are top-aligned). */
.connect-origin-seafood-cell {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding-top: 6px;
}

/* The revealed detail band is a drawer that grows out from under its row.

   Three nested elements:
   1. `.connect-seafood-subrow-anim` — a CSS grid whose single row animates
      grid-template-rows 0fr→1fr on mount, expanding the drawer from zero height
      and PUSHING the rows below it down to make room. Enter-only, matching the
      connect-field-reveal house pattern (React unmounts the sub-row on untick,
      so there is no exit transition). The block's bottom spacing lives here.
   2. `.connect-seafood-subrow-clip` — a box-less viewport with min-height:0 so
      the 0fr track can collapse it to TRUE zero (the drawer's own padding+border
      would otherwise leave a ~26px residual under box-sizing:border-box).
      overflow:hidden is applied ONLY during the reveal (keyframe, default
      fill-mode), so at rest it is visible — the grid-popup comboboxes render
      inline (withinPortal false) and a permanent overflow:hidden would clip
      their dropdowns.
   3. `.connect-seafood-subrow` — the visible drawer: a full light border minus
      the TOP edge (open at the top so it reads as a drawer pulled out from under
      the row above; rounded bottom corners), faint blue fill. */
.connect-seafood-subrow-anim {
    display: grid;
    grid-template-rows: 1fr;
    margin: 0 0 10px 0;
    animation: connect-seafood-reveal 280ms cubic-bezier(0.22, 1, 0.36, 1);
}

@keyframes connect-seafood-reveal {
    from { grid-template-rows: 0fr; }
    to   { grid-template-rows: 1fr; }
}

.connect-seafood-subrow-clip {
    min-height: 0;
    animation: connect-seafood-reveal-clip 280ms cubic-bezier(0.22, 1, 0.36, 1);
}

@keyframes connect-seafood-reveal-clip {
    from { overflow: hidden; opacity: 0; }
    to   { overflow: hidden; opacity: 1; }
}

.connect-seafood-subrow {
    margin: 0 12px;
    padding: 12px 12px;
    border: 2px solid var(--infine-blue-light-fade);
    border-top: none;
    border-radius: 0 0 var(--mantine-radius-default, 4px) var(--mantine-radius-default, 4px);
    background: var(--infine-blue-bg);
    display: flex;
    flex-direction: column;
    gap: 0;
}

/* One small group label for the whole band — the dropdowns below use their
   placeholder as the field label, so this caption is the only label needed. The
   drawer's flex gap is 0, so the eyebrow's own margin sets the label↔fields gap. */
.connect-seafood-subrow-eyebrow {
    font-size: var(--mantine-font-size-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--infine-blue);
    font-weight: 600;
    margin-bottom: 6px;
}

.connect-seafood-subrow-fields {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
}

.connect-seafood-field {
    flex: 1 1 220px;
    min-width: 180px;
    max-width: 320px;
}

/* The field-block bg-drain wash paints every wrapper inside the block; kill
   it on the sub-row so the per-cell required-empty signal below is the sole
   yellow here (same reasoning as the chromeless origin rows above). */
.connect-seafood-subrow .mantine-Input-wrapper {
    background-image: none;
}

/* Required-but-empty species (or production method): yellow wash + yellow
   bottom border — the same required-empty vocabulary the field-blocks use
   (bottom edge only, like every other dropdown). Scoped per-cell via
   `data-required-empty` so only the outstanding control is flagged; the
   block-level required-empty rules carve out `.connect-seafood-subrow *`, so
   a filled species in one row never lights up because a sibling row is empty. */
.connect-seafood-field[data-required-empty="true"] .mantine-Input-input {
    background-color: var(--infine-yellow-bg);
    border-bottom-color: var(--infine-yellow-bright);
}

/* Missing origin country on a risk-commodity row gets the same required-empty
   treatment as the seafood sub-fields and the standalone dropdowns: a light
   yellow wash + an orange bottom border, and no other borders so the chromeless
   cell keeps its minimal in-table look. Scoped to required AND empty, so a
   filled origin or a non-risk row stays neutral. */
.connect-origin-row [data-col="countries"][data-required="true"][data-empty="true"] .mantine-Input-input,
.connect-origin-row [data-col="countries"][data-required="true"][data-empty="true"] .mantine-PillsInput-input {
    background-color: var(--infine-yellow-bg);
    border-bottom-color: var(--infine-yellow-bright);
}

/* ─────────────────────────────────────────────────────────────────────
   Per-row required marker.

   Origin country is required on risk-commodity rows; the seafood species +
   production method are required while the row is seafood. The asterisk sits to
   the RIGHT of the field — mirroring how labelled fields place the required
   marker right of the title (there are no per-cell titles here). The right-hand
   gutter is reserved on EVERY markable cell, required or not, so toggling the
   asterisk never reflows and the input edges stay aligned row to row. Same glyph
   + colour as the form's `.required` label marker.
   ──────────────────────────────────────────────────────────────────── */
.connect-origin-row [data-col="countries"],
.connect-seafood-field {
    position: relative;
    padding-right: 16px;
}

.connect-origin-row [data-col="countries"][data-required="true"]::after,
.connect-seafood-field[data-required="true"]::after {
    content: "";
    position: absolute;
    top: 8px;
    right: 2px;
    width: 14px;
    height: 14px;
    background-color: var(--infine-yellow-bright);
    -webkit-mask: url("/required-marker.svg") no-repeat center / contain;
    mask: url("/required-marker.svg") no-repeat center / contain;
    pointer-events: none;
}

/* Right-align the share value to the cell's right edge so it sits clear of the
   origin-country required asterisk in the gutter to its left — otherwise the
   asterisk reads as belonging to Share rather than Origin country. */
.connect-origin-row [data-col="share"] .mantine-Input-input {
    text-align: right;
}

/* Weight-list numeric columns (weight in grams, recycled %) right-align their
   values + titles so the digits line up under the right-aligned headers — same
   treatment as the ingredient Share column. */
.connect-composite-row [data-col="weight"] .mantine-Input-input,
.connect-composite-row [data-col="recycled"] .mantine-Input-input {
    text-align: right;
}

/* ─────────────────────────────────────────────────────────────────────
   "Unadded risk ingredient" cue beside the Add-details button.

   When the parsed ingredient list contains a risk commodity or likely
   seafood that has no :ingredient-details row yet, the footer shows a
   short named hint ("Cocoa needs details") prefixed with the same orange
   required-marker glyph the per-row markers use — so it reads as part of
   the form's "required" vocabulary (red stays reserved for errors).
   ──────────────────────────────────────────────────────────────────── */
.connect-origin-unadded {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--mantine-font-size-xs);
    /* Tight line box so the glyph and label centre cleanly on each other and on
       the adjacent button text (no stray leading throwing off the baseline). */
    line-height: 1;
    /* Same orange as the marker glyph (and the form's required asterisks) so the
       symbol and its label read as one unit. */
    color: var(--infine-yellow-bright);
}

.connect-origin-unadded::before {
    content: "";
    flex: none;
    width: 14px;
    height: 14px;
    background-color: var(--infine-yellow-bright);
    -webkit-mask: url("/required-marker.svg") no-repeat center / contain;
    mask: url("/required-marker.svg") no-repeat center / contain;
}
}
