@layer infine.components {
/* ─────────────────────────────────────────────────────────────────────
   View 1 — structured ingredient list (read-only display)

   The GS1 ingredient text, structured by Infine AI into a nested tree, shown
   as a run-on comma+parenthesis list. Each compound ingredient carries an
   identity hue via `.connect-ing-fam-N`, which binds `--fam` to the matching
   `--infine-fam-N` token; that one variable then drives the name colour, the
   soft fill and the parentheses. A compound's children inherit its `--fam`
   (they are tinted the parent's hue); top-level leaves have no family and stay
   neutral. Nothing else about a node renders here — origin, share,
   certifications and seafood belong to View 2, the per-ingredient details
   table, which both shows and edits them.

   NOTE: color-mix percentages are literal — a CSS variable in the percentage
   slot silently fails (learned in the mockup session); only the colour slot
   takes a var.
   ──────────────────────────────────────────────────────────────────── */

/* The structured list lives in an input-like box so it reads as an editable
   field with richer-than-plaintext content — not a run of static text. Border /
   background / hover match the Connect text inputs exactly (see
   connect-mantine-form.css): blue-light-fade resting border on the blue-bg-tint
   ground, blue-light on hover. */
.connect-ing-box {
    border: 1px solid var(--infine-blue-light-fade);
    border-radius: var(--mantine-radius-default, 4px);
    background: var(--infine-blue-bg-tint);
    padding: 8px 12px;
    cursor: text;
    transition: border-color 120ms ease;
}

.connect-ing-box:hover {
    border-color: var(--infine-blue-light);
}

.connect-ing-flow {
    font-size: var(--mantine-font-size-sm);
    line-height: 2.4;
    color: var(--infine-text);
}

/* Each family class binds --fam once; everything below reads it. */
.connect-ing-fam-1 { --fam: var(--infine-fam-1); }
.connect-ing-fam-2 { --fam: var(--infine-fam-2); }
.connect-ing-fam-3 { --fam: var(--infine-fam-3); }
.connect-ing-fam-4 { --fam: var(--infine-fam-4); }
.connect-ing-fam-5 { --fam: var(--infine-fam-5); }
.connect-ing-fam-6 { --fam: var(--infine-fam-6); }
.connect-ing-fam-7 { --fam: var(--infine-fam-7); }
.connect-ing-fam-8 { --fam: var(--infine-fam-8); }

/* One ingredient chip: a barely-rounded soft fill. Neutral (top-level leaf)
   by default — a whisper-faint tint of the text colour. */
.connect-ing-tok {
    border-radius: 4px;
    padding: 0.08em 0.34em;
    background: color-mix(in srgb, currentColor 4%, transparent);
    white-space: nowrap;
}

/* A chip that belongs to a family (compound, or a compound's child) gets a
   light tint of its hue instead of the neutral wash. Higher specificity than
   the base rule via :is(...).connect-ing-tok, so it wins. */
:is(.connect-ing-fam-1, .connect-ing-fam-2, .connect-ing-fam-3, .connect-ing-fam-4,
    .connect-ing-fam-5, .connect-ing-fam-6, .connect-ing-fam-7,
    .connect-ing-fam-8).connect-ing-tok {
    background: color-mix(in srgb, var(--fam) 12%, transparent);
}

/* Name takes the family hue when one is set (inherited from the chip/branch),
   else the flow's text colour. Compounds read a touch heavier than leaves. */
/* Family hue pulled toward ink for AA contrast on the tinted chips (raw --fam
   pink/sapphire sit near 2.6–3:1 on white, worse under the branch-hover tint).
   Neutral leaves are unaffected: currentColor is already the flow text colour,
   so the mix is a no-op for them. */
.connect-ing-name {
    color: color-mix(in srgb, var(--fam, currentColor) 55%, var(--infine-text));
    font-weight: 450;
}

.connect-ing-compound .connect-ing-name {
    font-weight: 600;
}

.connect-ing-paren {
    color: var(--fam);
    opacity: 0.65;
    font-size: 1.2em;
    font-weight: 700;
    margin: 0 0.06em;
}

.connect-ing-sep {
    color: var(--mantine-color-gray-5);
}

/* A branch wraps a compound + its parenthesised group so a group scope-hover
   can gather it — the VS-Code active-scope cue, but read-only here. */
.connect-ing-branch {
    border-radius: 6px;
    padding: 0.04em 0.1em;
    transition: background 120ms ease;
}

/* 8%, not the token's 12%: this layer stacks *under* every token tint in the
   group, and 12%+12% pushed the name text below AA contrast. */
.connect-ing-branch:hover {
    background: color-mix(in srgb, var(--fam) 8%, transparent);
}

/* Pointed at from its row in the per-ingredient details table below. */
.connect-ing-tok.connect-ing-linked {
    box-shadow: 0 0 0 2px var(--infine-blue-bright);
}

/* ─────────────────────────────────────────────────────────────────────
   View 1 — editing affordances

   Names are inline-contentEditable; each token carries a hover toolbar (nest /
   remove) and small muted `+` carets in the gaps insert siblings. The `+`
   affordances stay quiet so they never out-shout the ingredients — editing
   weight sits in the hover toolbar.
   ──────────────────────────────────────────────────────────────────── */

/* Tokens anchor the absolutely-positioned hover toolbar. */
.connect-ing-tok {
    position: relative;
}

.connect-ing-name {
    outline: none;
    cursor: text;
    /* Explicit dark caret: default `caret-color` follows `color`, which becomes the
       family hue on a tinted token. Dark reads on both the white focus chip and a
       plain token. */
    caret-color: var(--infine-text);
    /* Promote the editable host from bare `inline` to `inline-block`: Chromium can
       fail to paint the text caret in an inline contentEditable span (the caret
       has no layout box of its own). inline-block gives it one. min-width keeps a
       caret target when the name is momentarily empty (just-inserted). */
    display: inline-block;
    min-width: 0.5ch;
}

/* Actively-edited name: a strong blue ring marks edit mode. The fill is plain
   white (not a blue tint) so the dark text caret sits on maximum contrast — a
   thin caret on a tinted or light-blue ground was too easy to miss — and so a
   family-tinted token reverts to a neutral ground while being edited. */
.connect-ing-name:focus {
    color: var(--infine-text);
    background: var(--mantine-color-body);
    box-shadow: 0 0 0 2px var(--infine-blue-bright);
    border-radius: 4px;
    padding: 0.04em 0.32em;
    position: relative;
    z-index: 5;
}

/* A blank (just-inserted) name shows a muted placeholder from its data-ph. */
.connect-ing-name:empty::before {
    content: attr(data-ph);
    color: var(--mantine-color-gray-5);
    font-style: italic;
    font-weight: 400;
}

/* Hover toolbar, floating just above its token. */
.connect-ing-acts {
    position: absolute;
    bottom: calc(100% - 3px);
    left: 50%;
    transform: translateX(-50%) translateY(4px);
    display: flex;
    gap: 1px;
    padding: 2px;
    background: var(--mantine-color-body);
    border: 1px solid var(--mantine-color-default-border);
    border-radius: 7px;
    box-shadow: var(--mantine-shadow-md);
    opacity: 0;
    pointer-events: none;
    transition: opacity 120ms ease, transform 120ms ease;
    z-index: 6;
}

.connect-ing-tok:hover > .connect-ing-acts {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
    pointer-events: auto;
}

/* In edit mode (the token's name is focused) suppress the hover toolbar so the
   text edit has the user's full attention — nest/remove return the moment the
   name is unfocused. Higher specificity than the :hover rule above, so it wins
   even while the token is hovered. */
.connect-ing-tok:has(.connect-ing-name:focus) > .connect-ing-acts {
    opacity: 0;
    pointer-events: none;
}

.connect-ing-act {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    border: none;
    border-radius: 5px;
    background: none;
    color: var(--mantine-color-gray-6);
    cursor: pointer;
}

/* blue-light-fade, not the bg/bg-tint pales: those sit within a couple of
   levels of the toolbar's white body and read as no hover at all (red-bg gets
   away with it on the × only because red ink is perceptually loud). */
.connect-ing-act-nest:hover {
    background: var(--infine-blue-light-fade);
    color: var(--infine-blue);
}

/* The nest button's "(+)" — a plus inside parentheses, the view's own vocabulary
   for "add an ingredient inside this one's parens". Distinguished from the bare
   `+` carets exactly by the parens. */
.connect-ing-nest-glyph {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.03em;
    /* The flow's line-height (2.4) would give this span a 26px line box inside
       the 22px flex-centred button, shoving the glyph off-centre. */
    line-height: 1;
    /* Open Sans "(+)" ink sits ~1.4px below the button's centre at this size
       (baseline-probe + canvas actualBoundingBox measurement, not eyeballed —
       CSS-zoom eyeballing gave the opposite, wrong, answer). */
    transform: translateY(-1.5px);
}

.connect-ing-act-remove:hover {
    background: var(--infine-red-bg);
    color: var(--infine-red-bright);
}

/* Insert "+" affordances between ingredients. A constant soft blue at rest —
   present enough to read as an add-affordance, quiet enough not to out-shout the
   ingredients. `vertical-align: baseline` + a text-matched font-size sit the glyph
   on the text baseline (bare `middle` floated it off the row); `line-height: 1`
   hugs the glyph so its hover background isn't a pill taller than the row (the
   inline-block would otherwise inherit the flow's 2.4 line-height). */
.connect-ing-ins {
    display: inline-block;
    width: 0.7em;
    line-height: 1;
    text-align: center;
    color: color-mix(in srgb, var(--infine-blue-light) 65%, transparent);
    border-radius: 3px;
    cursor: pointer;
    user-select: none;
    vertical-align: baseline;
    transition: color 100ms ease, background 100ms ease;
}

.connect-ing-ins::before {
    content: "+";
    font-size: 0.9em;
    font-weight: 700;
}

/* Hover: white glyph on a half-strength navy wash. Translucent, not solid —
   a solid chip vanished on the tinted family-group backgrounds; a translucent
   layer darkens whatever it sits on, so the hover reads on white gaps and
   tinted groups alike. */
.connect-ing-ins:hover {
    color: var(--mantine-color-white);
    background: color-mix(in srgb, var(--infine-blue) 50%, transparent);
}

/* The end caret is the first-time "add" target — give it a little more body. */
.connect-ing-ins-end {
    width: auto;
    padding: 0 0.3em;
}

/* The `+` that adds a sub-ingredient inside a group — same quiet resting tone
   and soft hover as the sibling insert caret. */
.connect-ing-addchild {
    display: inline-block;
    margin-left: 0.1em;
    padding: 0 0.16em;
    line-height: 1;
    color: color-mix(in srgb, var(--infine-blue-light) 65%, transparent);
    font-size: 0.9em;
    font-weight: 700;
    border-radius: 3px;
    cursor: pointer;
    user-select: none;
    vertical-align: baseline;
    transition: color 100ms ease, background 100ms ease;
}

.connect-ing-addchild:hover {
    color: var(--mantine-color-white);
    background: color-mix(in srgb, var(--infine-blue) 50%, transparent);
}

/* Drag to move / re-nest. The grip drags its whole token (a compound's whole
   branch); valid destinations mark the insertion slot with a blue leading bar. */
.connect-ing-act-grip {
    cursor: grab;
}

.connect-ing-act-grip:hover {
    background: var(--infine-blue-light-fade);
    color: var(--infine-blue);
}

.connect-ing-act-grip:active {
    cursor: grabbing;
}

.connect-ing-dragging {
    opacity: 0.4;
}

/* The landing marker lives on the `+` affordance at the active slot — it sits
   exactly where the dropped node would be inserted, so it's the honest place
   to mark (not the neighbouring token). The glyph goes transparent (we're
   moving, not adding — a `+` would lie) and the chip fills solid blue, so the
   slot reads as a plain insertion bar. Transparent rather than hidden keeps
   the glyph sizing the chip — no layout shift when the marker appears. */
.connect-ing-ins.connect-ing-drop-slot,
.connect-ing-addchild.connect-ing-drop-slot {
    color: transparent;
    background: var(--infine-blue-bright);
}

/* Mid-drag, hover toolbars are noise over the drop targets — keep them down.
   Outranks the `.connect-ing-tok:hover` show rule by specificity. */
.connect-ing-flow-dragging .connect-ing-tok:hover > .connect-ing-acts {
    opacity: 0;
    pointer-events: none;
}

/* ─────────────────────────────────────────────────────────────────────
   View 2 — origin progress cue
   ──────────────────────────────────────────────────────────────────── */

/* Product-level origin progress cue under the details table. Same orange as
   the per-row required markers, prefixed with the required-marker glyph, so it
   reads as part of the form's "required" vocabulary (red stays for errors). */
.connect-ingredient-origin-progress {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-top: 8px;
    font-size: var(--mantine-font-size-xs);
    line-height: 1;
    color: var(--infine-yellow-bright);
}

.connect-ingredient-origin-progress::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;
}

/* ─────────────────────────────────────────────────────────────────────
   Source-row aside — the color-coding note
   ──────────────────────────────────────────────────────────────────── */

/* Right-aligned on the flex source row; inherits the row's muted gray.
   The attribution beside it is a single flex child (`.connect-source-attribution`),
   so squeezing this aside in makes that text wrap at word boundaries —
   it can no longer break mid-phrase ("Pasted / by") the way separately-flexed phrase spans did. */
.connect-source-aside {
    flex: 0 1 auto;
    margin-left: auto;
    padding-left: 12px;
    text-align: right;
    /* Must read over the muted attribution beside it: this is guidance the
       user needs (don't read the hues as analysis), not provenance metadata. */
    color: var(--mantine-color-gray-7);
    font-weight: 600;
}

/* ─────────────────────────────────────────────────────────────────────
   Language strip — which language a hand-written list is in

   Rendered inside the box, under the flow and inside the same border, so
   the question reads as part of the list being written and cannot be
   scrolled past on the way to the details table. It hosts the
   `:ingredient-language` field's own select: the strip's `data-state` is
   that field's, while the block around it carries the list's — an emptied
   list is itself required-empty while the person starts it over, and that
   must not paint an answered language orange. The block's own state rules
   carve `.connect-ing-strip *` out (connect-field-block.css); the wash
   tokens are rebound here to the strip's state.
   ──────────────────────────────────────────────────────────────────── */
.connect-ing-strip {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 10px;
    /* Bleeds to the box's edges: the box pads its flow 8px 12px. */
    margin: 8px -12px -8px;
    padding: 8px 12px;
    border-top: 1px solid var(--infine-blue-light-fade);
    border-radius: 0 0 var(--mantine-radius-default, 4px) var(--mantine-radius-default, 4px);
    background: var(--infine-blue-bg);
    cursor: default;
    /* The select sits on white inside the tinted strip, and the wash follows
       the language field rather than the list. */
    --field-bg: var(--mantine-color-body);
    --field-overlay: transparent;
}

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

.connect-ing-strip[data-state="required-empty"] .mantine-Input-input {
    border-bottom-color: var(--infine-yellow-bright);
}

/* Revealed the way a dependent field is, on the render it first applies. */
.connect-ing-strip--reveal {
    animation: connect-field-reveal 320ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

.connect-ing-strip__label {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: var(--mantine-font-size-xs);
    font-weight: 600;
    line-height: 1.4;
    color: var(--infine-blue);
    white-space: nowrap;
}

.connect-ing-strip__select {
    flex: none;
    width: 220px;
}

.connect-ing-strip__hint {
    flex: 1 1 200px;
    font-size: var(--mantine-font-size-xs);
    line-height: 1.45;
    color: var(--mantine-color-gray-7);
}
}
