/**
 * Etch Form Builder — runtime styles (base layout + state).
 *
 * The form's BASE LOOK ships here, not in Etch block-CSS: Etch does NOT emit a
 * component's internal block-CSS onto a host page (proven 2026-06-27 — see
 * docs/SESSION-HANDOFF.md + docs/ETCH-BLOCK-CSS-EMISSION.md), so block-CSS rules
 * never reach the published form. runtime.css is enqueued on every page and DOES
 * emit, so the classes the generator attaches (.etchform*) are styled here.
 * Uses ACSS tokens with safe fallbacks so it blends with the site.
 *
 * Trade-off: these base rules are not canvas-editable in Etch. Per-project visual
 * tweaks belong in the site's own CSS/ACSS, which override these via specificity
 * or source order. (For canvas-editable form styling the form must be authored as
 * page-level blocks, not a component — see the handoff "Path B".)
 */

/* ---- Base layout ---- */
.etchform {
	max-width: 40rem;
	margin-inline: auto;
}

.etchform__form {
	display: flex;
	flex-direction: column;
	gap: var(--space-m, 1.25rem);
	padding: var(--space-l, 2rem);
	background: var(--base-ultra-light, #fff);
	border: 1px solid var(--border-color-light, #e4e4e7);
	border-radius: var(--radius, 14px);
	box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 8px 24px rgba(0, 0, 0, 0.04);
}

.etchform__head {
	display: flex;
	flex-direction: column;
	gap: 0.25rem;
	margin-block-end: 0.25rem;
}

.etchform__heading {
	margin: 0;
	line-height: 1.15;
}

.etchform__description {
	margin: 0;
	color: var(--text-dark-muted, #555);
}

/* Single-column base; the @container rule below upgrades to two columns when wide. */
.etchform__grid {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--space-m, 1.25rem);
}

.etchform__field {
	display: flex;
	flex-direction: column;
	gap: 0.4rem;
}

/* 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 `.etchform__field` above already 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, and `display: none` also removes the field from the `.etchform__grid` layout
   above (desired — no leftover gap). */
.etchform__field[hidden] {
	display: none;
}

.etchform__label {
	font-weight: 600;
	font-size: var(--text-s, 0.9rem);
	line-height: 1.2;
}

.etchform__control {
	width: 100%;
	font: inherit;
	font-size: var(--text-m, 1rem);
	line-height: 1.4;
	padding: 0.7em 0.9em;
	color: var(--base-ultra-dark, #18181b);
	background: var(--base-ultra-light, #fff);
	border: 1px solid var(--border-color-light, #cbced4);
	border-radius: var(--radius, 10px);
	transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.etchform__control--area {
	min-height: 8rem;
	resize: vertical;
}

/* Choice groups (radio / checkbox) — a reset fieldset holding stacked options. */
.etchform__group {
	display: flex;
	flex-direction: column;
	gap: 0.4rem;
	min-inline-size: 0;
	margin: 0;
	padding: 0;
	border: 0;
}

.etchform__option {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	font-weight: 400;
	font-size: var(--text-m, 1rem);
	line-height: 1.3;
}

.etchform__option input {
	flex: none;
	margin: 0;
}

/* Per-field help / description text, shown under the control. */
.etchform__help {
	margin: 0;
	color: var(--text-dark-muted, #555);
	font-size: var(--text-s, 0.9rem);
	line-height: 1.35;
}

.etchform__submit {
	align-self: flex-start;
	margin-block-start: 0.25rem;
}

/* Same specificity rationale as .etchform__field[hidden]/.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;
}

/* Captcha widget container, between the fields grid and the submit button. Sizing is the
   provider script's own widget (or empty, pre-render/no-sitekey); same spacing as .etchform__submit
   above, its neighbour in source order. */
.etchform__captcha {
	margin-block-start: 0.25rem;
}

/* Rich text (wysiwyg) fallback. The textarea also carries .etchform__control/--area (same
   data-ef-control special case every textarea gets), which already supplies border/padding —
   this rule exists for when the site setting is OFF and .etchform__rich is all a visitor sees:
   it must look like an intentional control on its own, not rely on that other class always
   being present. The TinyMCE-on path is styled by TinyMCE's own UI once the setting is enabled. */
.etchform__rich {
	border: 1px solid var(--border-color-light, #cbced4);
	border-radius: var(--radius, 10px);
}

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

/* File/image upload (Stage B). The wrapper (.etchform__file, attached from data-ef-file — see
   blueprint.js's CLASS_MAP) holds the visible <input type="file"> plus an invisible
   <input type="hidden"> that carries the upload ref once runtime.js's setupFileUpload() writes
   it (and is also where a failed-upload .etchform__error note lands, via
   `(input.parentNode || form).appendChild(...)` in showFieldErrors — input.parentNode is this
   wrapper). Neither input carries .etchform__control: that class is reserved for controls
   marked data-ef-control, which a file field's own attrs never include (it has no scalar text
   value that class's styling is meant for) — so the file input gets the same
   border/radius/focus-ring language directly, using the identical tokens .etchform__control and
   .etchform__rich already use, rather than inventing new ones. */
.etchform__file {
	display: flex;
	flex-direction: column;
	gap: 0.4rem;
}

.etchform__file input[type="file"] {
	width: 100%;
	font: inherit;
	font-size: var(--text-m, 1rem);
	color: var(--base-ultra-dark, #18181b);
	background: var(--base-ultra-light, #fff);
	border: 1px solid var(--border-color-light, #cbced4);
	border-radius: var(--radius, 10px);
	padding: 0.6em 0.9em;
	transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.etchform__file input[type="file"]:focus {
	outline: none;
	border-color: var(--primary, #2563eb);
	box-shadow: 0 0 0 3px color-mix( in srgb, var(--primary, #2563eb) 22%, transparent );
}

/* Gallery upload (Task 15). [data-ef-gallery] (buildGalleryInputGroup(), inside the same
   .etchform__file wrapper as the visible multi-file input) holds zero-to-N entries — 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, built by runtime.js's shared
   buildGalleryEntry() helper. Sized via CSS here, deliberately never via inline width/height on
   the <img> itself — see that function's own comment. */
/* [data-ef-gallery], not a class: buildGalleryInputGroup() gives this container only
   data-ef-gallery/data-field (no CLASS_MAP entry maps it to a class the way data-ef-file maps to
   .etchform__file) — matching the attribute selector already used for [data-ef-gallery-limit]
   below, and avoiding a CLASS_MAP change that would touch generated markup and need a rebuild. */
[data-ef-gallery] {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem;
}

[data-ef-gallery]:empty {
	display: none;
}

.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 {
	position: absolute !important;
	left: -9999px !important;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

/* Stage 5a multi-step forms. A step wraps its own fields one level inside .etchform__grid, so
   it needs the SAME grid treatment .etchform__grid itself gets (single column here, upgraded to
   12 columns in the @container block below) — otherwise its children sit inside a flex
   container with no grid ancestor to apply their span-N classes against, and silently stack
   full-width regardless of configured width (PR #85 review finding #0). */
.etchform__step {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--space-m, 1.25rem);
}

/* 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] {
	display: none;
}

.etchform__progress {
	display: flex;
	gap: var(--space-s, 0.75rem);
	margin-block-end: var(--space-m, 1rem);
}

.etchform__progress-item {
	flex: 1;
	padding-block-end: 0.4rem;
	border-block-end: 2px solid var(--border-color-light, #e4e4e7);
	color: var(--text-dark-muted, #555);
	font-size: var(--text-s, 0.9rem);
	text-align: center;
}

.etchform__progress-item--active {
	border-color: var(--primary, #2563eb);
	color: var(--base-ultra-dark, #18181b);
	font-weight: 600;
}

.etchform__progress-item--done {
	border-color: var(--success, #198754);
}

.etchform__stepnav {
	display: flex;
	justify-content: space-between;
	gap: var(--space-s, 0.75rem);
	margin-block-start: 0.25rem;
}

/* ---- 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;
}

/* Focus ring. Pseudo rules do not compile into an Etch component via the Public
   API, so the generated form's :focus state ships here with the plugin. */
.etchform__control:focus {
	outline: none;
	border-color: var(--primary, #2563eb);
	box-shadow: 0 0 0 3px color-mix( in srgb, var(--primary, #2563eb) 22%, transparent );
}

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

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

/* Responsive field layout — progressive enhancement.
   The generated form wraps its fields in .etchform__grid (single column by default,
   set in the base section above). Where container queries are supported, the grid
   becomes two columns once the form itself is wide enough, regardless of screen width —
   so it adapts in a narrow sidebar as well as a wide main column. Textareas span the
   full width. Old browsers keep the single column. */
@supports (container-type: inline-size) {
	.etchform__form {
		container-type: inline-size;
	}

	@container (min-width: 28rem) {
		/* Selector is more specific than the base .etchform__grid above, so the multi-column
		   upgrade wins regardless of source order. The 28rem breakpoint is measured against
		   the form CONTENT box (.etchform max-width 40rem minus .etchform__form padding, both
		   set in the base section above); keep it well under that. A 12-column track lets each
		   field claim a per-field span (default full). grid-auto-flow:dense backfills the gap a
		   full-width field would otherwise leave when it sits mid-list. */
		.etchform__form .etchform__grid,
		.etchform__form .etchform__step {
			grid-template-columns: repeat(12, minmax(0, 1fr));
			grid-auto-flow: dense;
		}

		/* A step is itself an item of the OUTER .etchform__grid — without an explicit span it
		   would auto-place into a single 1/12 track (a thin sliver) instead of spanning the full
		   width it needs in order to host its own nested 12-column grid above. */
		.etchform__step { grid-column: 1 / -1; }

		/* A step's own children are not always fields: ConfigMigrator's reconciliation also
		   allows a heading/divider/text-block or a nested section directly inside a step (the
		   canvas UI doesn't expose adding one there yet, but a REST-authored config can). Only
		   .etchform__field gets an explicit span below — every other direct child needs the same
		   full-width span .etchform__field itself gets, or it falls back to the grid's default
		   single-track auto-placement and renders squeezed into a sliver. */
		.etchform__step > :not(.etchform__field) { grid-column: 1 / -1; }

		/* Default = full width; the per-field span modifiers (attached by the generator from the
		   field's width, 1–12) override via equal specificity + later source order. */
		.etchform__field { grid-column: span 12; }
		.etchform__field--span-1 { grid-column: span 1; }
		.etchform__field--span-2 { grid-column: span 2; }
		.etchform__field--span-3 { grid-column: span 3; }
		.etchform__field--span-4 { grid-column: span 4; }
		.etchform__field--span-5 { grid-column: span 5; }
		.etchform__field--span-6 { grid-column: span 6; }
		.etchform__field--span-7 { grid-column: span 7; }
		.etchform__field--span-8 { grid-column: span 8; }
		.etchform__field--span-9 { grid-column: span 9; }
		.etchform__field--span-10 { grid-column: span 10; }
		.etchform__field--span-11 { grid-column: span 11; }
		.etchform__field--span-12 { grid-column: span 12; }

		/* A textarea always claims the full row (its :has() specificity beats the span classes). */
		.etchform__field:has(textarea) {
			grid-column: 1 / -1;
		}
	}
}

/* :has()-driven validation feedback, no JavaScript required. :user-invalid only flags a
   field after the user has interacted with it (unlike :invalid), so untouched required
   fields don't look wrong before the user has tried to submit. The `:not(:focus)` guard
   yields to the :focus border so an actively-edited field isn't flagged red mid-typing. */
.etchform__control:user-invalid:not(:focus) {
	border-color: var(--danger, #dc3545);
}

.etchform__field:has(.etchform__control:user-invalid:not(:focus)) .etchform__label {
	color: var(--danger, #dc3545);
}

/* 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` (not flex-direction: row-reverse — see below) 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 keeping native radiogroup arrow-key navigation
   pointed the right way: row-reverse only flips PAINT order, so ArrowLeft on a reversed row still
   moves to the PREVIOUS DOM sibling (a HIGHER value, since DOM is descending) — visually moving
   the selection right/up, backwards from every sighted keyboard user's expectation (caught live,
   2026-09-15). `direction: rtl` instead flips both paint AND the browser's own arrow-key mapping
   together, so ArrowLeft continues to decrease the rating. `> 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. */
.etchform__rating {
	direction: rtl;
	flex-direction: row;
	justify-content: flex-end;
	gap: 0.15em;
}

.etchform__rating > legend {
	direction: ltr;
}

.etchform__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. */
.etchform__rating [data-ef-rating-unit] > label[data-ef-option] {
	position: absolute;
	inset-block: 0;
	inset-inline: 0;
	cursor: pointer;
}
.etchform__rating [data-ef-rating-unit] > label[data-ef-half-value] {
	inset-inline-end: 50%;
}
.etchform__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. */
.etchform__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. */
.etchform__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. */
.etchform__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. */
.etchform__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. */
.etchform__rating:not(:hover) [data-ef-rating-unit]:has(input:checked) [data-ef-star-full],
.etchform__rating:not(:hover) [data-ef-rating-unit]:has(input:checked) ~ [data-ef-rating-unit] [data-ef-star-full] {
	color: var(--primary, #2563eb);
}
.etchform__rating [data-ef-rating-unit]:hover [data-ef-star-full],
.etchform__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. */
.etchform__rating:not(:hover) [data-ef-rating-unit]:has(input:checked) [data-ef-star-full] path,
.etchform__rating:not(:hover) [data-ef-rating-unit]:has(input:checked) ~ [data-ef-rating-unit] [data-ef-star-full] path,
.etchform__rating [data-ef-rating-unit]:hover [data-ef-star-full] path,
.etchform__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. */
.etchform__rating:not(:hover) [data-ef-rating-unit]:has(input[data-ef-half]:checked) [data-ef-star-full],
.etchform__rating [data-ef-rating-unit]:has(label[data-ef-half-value]:hover) [data-ef-star-full] {
	display: none;
}
.etchform__rating:not(:hover) [data-ef-rating-unit]:has(input[data-ef-half]:checked) [data-ef-star-half],
.etchform__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 (see .etchform__file
   input[type="file"]:focus etc. above) — :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. */
.etchform__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) {
	.etchform__rating [data-ef-rating-unit] svg {
		transition: none;
	}
}
