/**
 * Etch Form Builder — runtime styles: the BEHAVIOUR layer.
 *
 * Since 0.41.5 (#734 phase 2) the form's base LOOK is Etch block CSS: the bridge provisions one class rule per
 * generated class into Etch (builder-ui/src/styles/defaults.js, editable in Etch's CSS panel). This file keeps only
 * what the form needs to WORK, whatever a site does to those rules: the [hidden] overrides, the required markers,
 * the honeypot, visually hidden text, the rating stars, the step focus ring, and every element runtime.js injects
 * (messages, errors, gallery entries: not Etch blocks, so no Etch rule can ever reach them). It is enqueued on every
 * page, in the Etch canvas, the block editor and the wp-admin preview.
 *
 * Forms built before 0.41.5 (no data-ef-styles on the root) get their look from defaults-legacy.css until their next
 * Insert/Refresh. Uses ACSS tokens with safe fallbacks so it blends with the site.
 */

/* Stage 4 conditional logic: the runtime (rules.js + runtime.js's setupVisibility) toggles the
   NATIVE `hidden` attribute on a conditioned field's wrapper rather than a custom class, to avoid
   any specificity fight with 3rd-party/theme CSS. But the `.etchform__field` block-CSS default sets
   its own `display: flex` (specificity 0,1,0), which otherwise beats the UA stylesheet's `[hidden]
   { display: none }` rule — so without this rule a hidden field's wrapper would stay visible
   despite the `hidden` attribute. This selector (0,2,0) wins over the base rule regardless of
   source order (block CSS is emitted after this file), and `display: none` also removes the field
   from the `.etchform__grid` layout (desired — no leftover gap). */
.etchform__field[hidden],
[data-ef-field][hidden] {
	display: none;
}

/* Required marker (#565, WCAG 3.3.2 / 1.4.1). Keyed on data-ef-* attributes only: Etch drops a class
   that has no rule from a component (#479), and a marker must not depend on one.

   Base — the generator's [data-ef-required] on the field wrapper. It carries the requirement where
   nothing native does: a repeater (buildRepeaterField never read `required`), a checkbox GROUP (its "at
   least one" is enforced server-side, no input is `required`), colour and range. The asterisk is EXPOSED
   (no empty alt text): without it a screen-reader user would get no cue at all, and the legend ("Fields
   marked with * are required") explains it. Plain selectors, no :has(), on purpose: a selector list is
   dropped as a whole when one member is invalid, so a browser without :has() (Firefox < 121, Safari <
   15.4) still marks every top-level field of a form generated with the attribute (a repeater SUB-field's
   label has no attribute of its own and is marked by group A only). Colour: the text-contrast token,
   plain --danger is 4.0:1 on white. It comes BEFORE group A and is weaker than every group A selector
   that can match the same label, so group A overrides it (a tie is settled by this order). */
[data-ef-field][data-ef-required] > [data-ef-label]::after,
[data-ef-field][data-ef-required] > [data-ef-group] > [data-ef-legend]::after {
	content: " *";
	color: var(--danger-dark, #842029);
	font-weight: 700;
}

/* Group A — a :required control backs the requirement: text-like inputs, selects, radio and rating
   groups, consent, file, a gallery (its `required` flips off once an image is in, so
   data-ef-gallery-required is the stable signal) and, via the label that precedes its control, a repeater
   sub-field. This also covers a form generated before data-ef-required existed. The asterisk is generated
   content kept OUT of the accessibility tree (the native `required` already announces it); the first
   declaration is the fallback for a browser without alt text for generated content. A repeater is
   excluded: its sub-fields' controls would otherwise mark it. Rich text is excluded too (rule below). */
[data-ef-field]:not([data-ef-repeater]):not(:has([data-ef-rich])):has(:required) > [data-ef-label]::after,
[data-ef-field]:not([data-ef-repeater]):has(> [data-ef-group] :required) > [data-ef-group] > [data-ef-legend]::after,
[data-ef-label]:has(+ :required:not([data-ef-rich]))::after,
[data-ef-label]:has(+ [data-ef-file] :required)::after,
[data-ef-field]:has([data-ef-gallery-required]) > [data-ef-label]::after {
	content: " *";
	content: " *" / "";
	color: var(--danger-dark, #842029);
	font-weight: 700;
}

/* A required rich-text field (TinyMCE) is marked with the asterisk EXPOSED: the editor hides the native
   `required` textarea and its iframe carries no aria-required, so nothing else tells a screen-reader user.
   Keyed on the field wrapper, not on the label's next sibling: wp.editor.initialize() puts the editor's
   own wrapper (.wp-editor-wrap) around the textarea. Group A excludes [data-ef-rich], so the two never
   compete. Without TinyMCE (the plain textarea fallback) a reader hears the asterisk and "required" both,
   which is harmless. */
[data-ef-field]:has([data-ef-rich]:required) > [data-ef-label]::after {
	content: " *";
	color: var(--danger-dark, #842029);
	font-weight: 700;
}

/* One line per form that says what the marker means. Inserted by runtime.js (setupRequiredLegend) when a
   marker is actually rendered, in the site's language; the canvas preview writes the same element. */
.etchform__required-legend,
[data-ef-required-legend] {
	margin: 0;
	color: var(--text-dark-muted, #555);
	font-size: var(--text-s, 0.9rem);
	line-height: 1.35;
}

/* Same specificity rationale as .etchform__field[hidden] above and .etchform__step[hidden] below: the
   generator (CLASS_MAP) also gives the submit button theme utility classes (`btn`, `btn--primary`)
   so it matches the site's own button styling — but on a theme whose `.btn` sets its own
   `display` (e.g. Automatic.css's `display: flex`), that beats the UA stylesheet's bare
   `[hidden] { display: none }` rule, so runtime.js's `submitBtn.hidden = !isLast` (stepped forms)
   had no visible effect: the button stayed clickable on non-final steps. */
.etchform__submit[hidden] {
	display: none;
}

/* Gallery upload (Task 15). runtime.js's buildGalleryEntry() fills the gallery box (styled by the
   .etchform__file block-CSS default) with one .etchform__gallery-item per uploaded/prefilled image, each a
   thumbnail + remove button paired 1:1 with its own (not directly styled) hidden data-ef-multi ref. Sized via
   CSS here, deliberately never via inline width/height on the <img> itself — see that function's own comment. */
.etchform__gallery-item {
	position: relative;
	width: 5rem;
	height: 5rem;
}

.etchform__gallery-thumb {
	width: 100%;
	height: 100%;
	object-fit: cover;
	border-radius: var(--radius, 10px);
	border: 1px solid var(--border-color-light, #cbced4);
}

.etchform__gallery-remove {
	position: absolute;
	top: -0.4rem;
	right: -0.4rem;
	width: 1.4rem;
	height: 1.4rem;
	line-height: 1;
	border-radius: 50%;
	border: 1px solid var(--border-color-light, #cbced4);
	background: var(--base-ultra-light, #fff);
	color: var(--base-ultra-dark, #18181b);
	cursor: pointer;
	font-size: 0.7rem;
}

.etchform__gallery-remove:focus {
	outline: none;
	border-color: var(--primary, #2563eb);
	box-shadow: 0 0 0 3px color-mix( in srgb, var(--primary, #2563eb) 22%, transparent );
}

[data-ef-gallery-limit] {
	font-size: var(--text-s, 0.875rem);
	color: var(--base-dark, #52525b);
}

.etchform__hp,
[data-ef-hp] {
	position: absolute !important;
	left: -9999px !important;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

/* The marker twins ([data-ef-field][hidden] above, [data-ef-step][hidden] here): defaults-legacy.css gives a
   pre-0.41.0 wrapper that lost its class a display through its marker (#479), so the override must reach it too.
   Same specificity rationale as .etchform__field[hidden] above: a bare UA `[hidden]` rule would
   already be beaten by the flex `display` this class sets, so it needs its own override. */
.etchform__step[hidden],
[data-ef-step][hidden] {
	display: none;
}

/* #711: runtime.js focuses the step after Next/Previous (tabindex -1, so never a Tab stop). The
   plugin draws the ring itself, for keyboard focus only, so a theme reset cannot make the
   keyboard position invisible and a mouse click on the step's padding shows nothing. Scoped to
   tabindex="-1", the one runtime.js sets: the builder canvas also imports this file, and its step
   (tabindex="0") draws its own selection outline. */
.etchform__step[tabindex="-1"]:focus {
	outline: none;
}

.etchform__step[tabindex="-1"]:focus-visible {
	outline: 2px solid var(--primary, #2563eb);
	outline-offset: 4px;
}

/* ---- Runtime-injected state ---- */
.etchform__message {
	padding: var(--space-s, 0.75rem) var(--space-m, 1rem);
	border-radius: var(--radius, 8px);
	margin-block-end: var(--space-m, 1rem);
	font-size: var(--text-s, 0.95rem);
	line-height: 1.4;
}

.etchform__message--success {
	color: var(--success-dark, #0f5132);
	/* ACSS 4 removed the pre-built *-trans-10 transparency tokens; its own replacement idiom
	   is color-mix() against the real color, same color space (in srgb) already used for the
	   focus-ring color-mix() calls elsewhere in this file. */
	background: color-mix(in srgb, var(--success, #198754) 10%, transparent);
	border: 1px solid var(--success, #198754);
}

.etchform__message--error {
	color: var(--danger-dark, #842029);
	background: color-mix(in srgb, var(--danger, #dc3545) 10%, transparent);
	border: 1px solid var(--danger, #dc3545);
}

.etchform__error {
	display: block;
	margin-block-start: 0.3rem;
	color: var(--danger, #dc3545);
	font-size: var(--text-xs, 0.85rem);
	line-height: 1.3;
}

.etchform__control.is-error,
.is-error {
	border-color: var(--danger, #dc3545) !important;
}

[type="submit"].is-busy {
	opacity: 0.65;
	cursor: progress;
}

/* Rating field — graphical stars (ratingDisplay: 'stars'). Pure CSS click/hover cumulative fill,
   no JavaScript: blueprint.js's buildRatingGroup renders each star UNIT (data-ef-rating-unit) in
   DESCENDING value order. `direction: rtl` is what lets `:has(input:checked) ~ [data-ef-rating-unit]`
   fill every star up to and including the selected/hovered one with zero JavaScript, while the
   stars read ascending left to right on any page direction. `> legend` and each unit reset back to
   `ltr` so the field's own text (and each star's internal half/full split) doesn't mirror.
   Arrow keys are NOT left to the browser: its native radio arrow keys follow the focused input's
   own direction (the unit's `ltr`), so ArrowRight moved to the next radio in DOM order — a LOWER
   value, and through half steps out of order (#558). runtime.js's setupRating maps the keys to
   the numeric order shown here instead, so this rule must keep the order ascending left to right. */
fieldset:is(.etchform__rating, [data-ef-rating]) {
	/* The flex layout and the fieldset reset are repeated here (they also come from
	   .etchform__group): Etch can drop a class that has no style rule of its own from the
	   component (#479), and this block must not depend on a second class surviving.
	   `fieldset` (0,1,1) makes it beat .etchform__group (0,1,0), whose column layout the same
	   fieldset also carries, by specificity: since #734 phase 2 that rule is Etch block CSS,
	   emitted AFTER this file, so the order between the two no longer decides
	   (styles/cascade.test.js). */
	display: flex;
	min-inline-size: 0;
	margin: 0;
	padding: 0;
	border: 0;
	direction: rtl;
	flex-direction: row;
	justify-content: flex-end;
	gap: 0.15em;
}

/* `direction: ltr` alone only mirrors the legend's text: a <legend> is placed by its FIELDSET's
   direction, so under rtl it sat at the right edge (#431). Full width puts it back at the start. */
:is(.etchform__rating, [data-ef-rating]) > legend {
	direction: ltr;
	inline-size: 100%;
}

:is(.etchform__rating, [data-ef-rating]) [data-ef-rating-unit] {
	direction: ltr;
	position: relative;
	display: inline-block;
	width: 1.5em;
	height: 1.5em;
	cursor: pointer;
}

/* A unit's option label(s): a whole-star unit has exactly one, filling the whole unit. A
   half-stepped unit has two — the half-value one claims the left 50%, and whichever OTHER
   data-ef-option label follows it (always the full-value one — see buildSplitCell) claims the
   right 50%. Both stay clickable/focusable at their real 50%-width size; the shared icon pair
   sits visually on top (see the pointer-events:none svg rule below), so a click on either half
   reaches the input beneath it, not the icon. */
:is(.etchform__rating, [data-ef-rating]) [data-ef-rating-unit] > label[data-ef-option] {
	position: absolute;
	inset-block: 0;
	inset-inline: 0;
	cursor: pointer;
}
:is(.etchform__rating, [data-ef-rating]) [data-ef-rating-unit] > label[data-ef-half-value] {
	inset-inline-end: 50%;
}
:is(.etchform__rating, [data-ef-rating]) [data-ef-rating-unit] > label[data-ef-half-value] ~ label[data-ef-option] {
	inset-inline-start: 50%;
}

/* The native radios stay in the DOM, focusable and in the accessibility tree — just visually
   replaced by the shared icon(s) on top. Shares the same visually-hidden clip as
   [data-ef-visually-hidden] below (each option's own text label) rather than
   display:none/visibility:hidden, which would drop it from some assistive-tech interaction
   models instead of merely hiding it visually. */
:is(.etchform__rating, [data-ef-rating]) [data-ef-rating-unit] input,
[data-ef-visually-hidden] {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/* The icon pair sits on top of the (invisible) option label(s), spanning the WHOLE unit —
   pointer-events:none so a click/hover always reaches whichever label is underneath instead of
   the decorative icon. */
:is(.etchform__rating, [data-ef-rating]) [data-ef-rating-unit] svg {
	position: absolute;
	inset: 0;
	width: 1.5em;
	height: 1.5em;
	pointer-events: none;
	/* --border-color-light (#d1d5db, used elsewhere in this file for 1px borders) measures only
	   1.47:1 against a white background — nowhere near the 3:1 WCAG 1.4.11 (Non-text Contrast)
	   floor for an active, operable UI component's resting state. An icon's fill IS its entire
	   visible boundary (unlike a bordered control, which still reads as present via its box even
	   at low border contrast), so an empty star at that contrast is close to invisible — a real
	   discoverability defect, not just a technicality. --text-dark-muted (7.46:1) is the fix.
	   Confirmed via webblabb-a11y-gate, 2026 — see CHANGELOG for this feature's entry. */
	color: var(--text-dark-muted, #555);
	transition: color 0.1s ease;
}

/* Resting (empty) full star is an OUTLINE, not a solid fill — a SHAPE distinction on top of the
   color one above. A color-only distinction (muted vs --primary) is not safe against an
   arbitrary theme: live-verifying this release on a real Automatic.css site whose --primary is
   customized to a dark, low-saturation brand color, it resolved to near-black — visually
   indistinguishable from --text-dark-muted (also near-black), so 0 stars and 5 stars looked
   identical even though the underlying radio value changed correctly. Overriding the inline
   fill="currentColor" on the path (not the svg) keeps this scoped to the full-star icon only —
   the half-star icon's own two-path fill-based rendering (see its own comment below) is
   untouched. */
:is(.etchform__rating, [data-ef-rating]) [data-ef-rating-unit] [data-ef-star-full] path {
	fill: none;
	stroke: currentColor;
	stroke-width: 1.5;
	stroke-linejoin: round;
}

/* A half-stepped unit's half icon is hidden by default — blueprint.js emits it alongside the full
   icon on every such unit (see its own buildSplitCell comment) precisely so this rule, not the
   markup, decides which one shows. A whole-star unit never has a half icon at all. */
:is(.etchform__rating, [data-ef-rating]) [data-ef-star-half] {
	display: none;
}

/* Fill color: every unit up to and including the checked one — and, while hovering ANYWHERE in a
   unit (either half-zone counts, via plain :hover bubbling from either descendant label), up to
   and including whichever unit the pointer is over — taking over COMPLETELY rather than combining
   with the checked state (the :not(:hover) guard on the fieldset below), so hovering a LOWER
   value than the current selection previews only that lower fill, not both ranges filled at once. */
:is(.etchform__rating, [data-ef-rating]):not(:hover) [data-ef-rating-unit]:has(input:checked) [data-ef-star-full],
:is(.etchform__rating, [data-ef-rating]):not(:hover) [data-ef-rating-unit]:has(input:checked) ~ [data-ef-rating-unit] [data-ef-star-full] {
	color: var(--primary, #2563eb);
}
:is(.etchform__rating, [data-ef-rating]) [data-ef-rating-unit]:hover [data-ef-star-full],
:is(.etchform__rating, [data-ef-rating]) [data-ef-rating-unit]:hover ~ [data-ef-rating-unit] [data-ef-star-full] {
	color: var(--primary, #2563eb);
}

/* Solid fill on every passed star — the other half of the outline/solid shape distinction above.
   Without this, a passed star would just be an outline in --primary instead of --text-dark-muted,
   which is still a color-only difference and reintroduces the exact collision this fix exists
   to prevent. */
:is(.etchform__rating, [data-ef-rating]):not(:hover) [data-ef-rating-unit]:has(input:checked) [data-ef-star-full] path,
:is(.etchform__rating, [data-ef-rating]):not(:hover) [data-ef-rating-unit]:has(input:checked) ~ [data-ef-rating-unit] [data-ef-star-full] path,
:is(.etchform__rating, [data-ef-rating]) [data-ef-rating-unit]:hover [data-ef-star-full] path,
:is(.etchform__rating, [data-ef-rating]) [data-ef-rating-unit]:hover ~ [data-ef-rating-unit] [data-ef-star-full] path {
	fill: currentColor;
}

/* The boundary swap: ONLY a unit whose OWN half-zone input (data-ef-half) is the checked/hovered
   one switches ITS OWN icon from full to half — deliberately no `~` here, so this can never
   cascade onto a sibling unit. Every "passed" unit (matched by the `~` rules above) keeps showing
   its full icon regardless of its own shape, which is what makes a whole selection like "3" render
   as 3 clean full stars instead of an alternating half/full pattern (the review finding this whole
   feature's markup restructure exists to fix). Hover is scoped to `:has(label[data-ef-half-value]
   :hover)`, not the bare `:hover` the fill-color rule above uses, because hovering the FULL zone
   of the same split unit must NOT trigger the half shape — only its half zone specifically. */
:is(.etchform__rating, [data-ef-rating]):not(:hover) [data-ef-rating-unit]:has(input[data-ef-half]:checked) [data-ef-star-full],
:is(.etchform__rating, [data-ef-rating]) [data-ef-rating-unit]:has(label[data-ef-half-value]:hover) [data-ef-star-full] {
	display: none;
}
:is(.etchform__rating, [data-ef-rating]):not(:hover) [data-ef-rating-unit]:has(input[data-ef-half]:checked) [data-ef-star-half],
:is(.etchform__rating, [data-ef-rating]) [data-ef-rating-unit]:has(label[data-ef-half-value]:hover) [data-ef-star-half] {
	display: inline;
	color: var(--primary, #2563eb);
}

/* Same focus-ring color/treatment as every other :focus state in this file (the .etchform__control
   and .etchform__file block-CSS defaults) — :has() carries it from the visually-hidden input onto
   its OWN option label (the specific half/full zone that has focus, not the whole unit), since
   the input itself has no visible box to put an outline on. */
:is(.etchform__rating, [data-ef-rating]) [data-ef-option]:has(input:focus-visible) {
	outline: 2px solid var(--primary, #2563eb);
	outline-offset: 2px;
	border-radius: 4px;
}

@media (prefers-reduced-motion: reduce) {
	:is(.etchform__rating, [data-ef-rating]) [data-ef-rating-unit] svg {
		transition: none;
	}
}
