/**
 * The feed's front-end styles.
 *
 * Site-wide rather than per-placement, and that is a decision rather than laziness. The reason is
 * `CONVENTIONS.md` §4's **first** bullet: a plugin-rendered feed can appear anywhere — a block, a
 * shortcode in a widget, `[bildflode]` inside an Etch template — and Etch does **not** emit a
 * component's block-CSS onto a host page, so a stylesheet that only loaded with the block would leave
 * some placement unstyled. §4's second bullet is not the reason and cannot be: the Etch-**native** path
 * deliberately ships no CSS at all — "that is the point. The site's own ACSS styling is the design" —
 * so a conditional enqueue could not have reached it either way. This header gave that path as the
 * reason until 2026-09-19.
 *
 * Every ACSS token carries a fallback, because the plugin must look right on a site without ACSS —
 * and the fallback is what such a site gets, so it is chosen rather than left as a placeholder.
 *
 * No !important, deliberately: Etch's own defaults are 0,0,0 specificity, and a site must always be
 * able to override this file. This header used to add that ACSS 4.x lives inside `@layer`, so honest
 * specificity wins — it does not. The generated `automatic.css` on the measurement site has **0**
 * layers (`grep -c '@layer'` → 0, measured 2026-09-19), so source order *does* decide a conflict here,
 * which is what makes the order of the rules below load-bearing rather than neutralised — and why a
 * computed-style read, not this file, is the instrument for a cascade question (`CONVENTIONS.md` §5).
 */

.jbbf-feed {
	/*
	 * The feed's own knobs. The settings slice sets these values through classes; a site can set
	 * them from its own stylesheet, which an inline style would have made impossible.
	 */
	--jbbf-gap: var(--space-s, 0.75rem);
	--jbbf-radius: var(--radius-m, 0.5rem);
	--jbbf-border: var(--border, #d4d4d8);
	--jbbf-columns: 3;
	/* A translucent scrim: ACSS v4 removed the --*-trans-* tokens, so color-mix is the way. */
	--jbbf-scrim: color-mix(in oklab, var(--base-ultra-dark, #18181b) 55%, transparent);
	--jbbf-focus: var(--focus-color, currentColor);
	--jbbf-hover-scale: 1.02;

	container-type: inline-size;
}

/* The bounded column classes. Bounded on purpose: a class per arbitrary number is the same escaping
   problem with more rules. */
.jbbf-feed--cols-1 { --jbbf-columns: 1; }
.jbbf-feed--cols-2 { --jbbf-columns: 2; }
.jbbf-feed--cols-3 { --jbbf-columns: 3; }
.jbbf-feed--cols-4 { --jbbf-columns: 4; }
.jbbf-feed--cols-5 { --jbbf-columns: 5; }
.jbbf-feed--cols-6 { --jbbf-columns: 6; }

/*
 * The Stil steps. Each class sets ONE knob from the ACSS scale, and no class is the theme. They sit after
 * the base `.jbbf-feed` block and are the same specificity, so SOURCE ORDER is what makes them win.
 * `--jbbf-gap: 0px`, not `0`: the gap is divided and multiplied inside `calc()` below, and a unitless `0`
 * is a number there, which is not a length.
 */
.jbbf-feed--gap-none { --jbbf-gap: 0px; }
.jbbf-feed--gap-xs { --jbbf-gap: var(--space-xs, 0.5rem); }
.jbbf-feed--gap-m { --jbbf-gap: var(--space-m, 1.5rem); }
.jbbf-feed--gap-l { --jbbf-gap: var(--space-l, 2rem); }
.jbbf-feed--radius-none { --jbbf-radius: var(--radius-none, 0); }
.jbbf-feed--radius-l { --jbbf-radius: calc(var(--radius-m, 0.5rem) * 2); }
/* A circular card clips a corner badge (FR-28); the badge is inset under this class, see
   `.jbbf-feed--radius-round .jbbf-feed__badge` below. */
.jbbf-feed--radius-round { --jbbf-radius: var(--radius-circle, 50vw); }
.jbbf-feed--hover-none { --jbbf-hover-scale: 1; }

/*
 * Masonry: a multi-column block, not a grid.
 *
 * `column-count` packs the items into balanced columns and `break-inside: avoid` stops one being split
 * across a column boundary. Both are measured (design §2 fact 5, seven items of varying heights).
 *
 * The packing is COLUMN-MAJOR: items 1–2 fill column 1, 3–5 column 2, and so on. DOM order, visual
 * reading order and tab order are the same order — also measured — but "newest first" reads down a
 * column at a time rather than across a row. That is the price of masonry, and it is stated here
 * because it is visible to a visitor rather than discoverable by a test.
 */
.jbbf-feed--masonry .jbbf-feed__grid {
	display: block;
	column-count: var(--jbbf-columns);
	column-gap: var(--jbbf-gap);
}

.jbbf-feed--masonry .jbbf-feed__item {
	break-inside: avoid;
	margin-block-end: var(--jbbf-gap);
}

/*
 * There is deliberately NO `aspect-ratio` declaration for this arm — not `auto`, and not `1 / 1`.
 *
 * The variant differs from the grid only in that no square is forced on the image, and the way to say
 * that is to declare nothing here. This arm used to read `aspect-ratio: auto`, which looks like the way
 * to say it and is the opposite: **an author declaration of `auto` REPLACES the ratio the browser
 * derives from the `width`/`height` attributes** (computed `auto 1024 / 1024`, which the UA supplies
 * for any image carrying them), leaving a bare `auto` that promises "use the intrinsic ratio" while the
 * element has none to give.
 *
 * **Measured 2026-09-28 on `/jbbf-task4-verification/`**: WordPress core's auto-sizes feature prepends
 * `auto, ` to `sizes` on every non-LCP image (`loading="lazy"`), which makes the browser apply size
 * containment to it, and core's companion stylesheet
 * (`#wp-img-auto-sizes-contain-inline-css`) sets `contain-intrinsic-size: 3000px 1500px`. With size
 * containment there IS no intrinsic ratio, so the bare `auto` had nothing to fall back on and every
 * masonry image rendered at `contain-intrinsic-height` — **1500px at any column width**, for a 1:1 and
 * a 0.583-ratio image alike. The grid arm was immune because a specified `1 / 1` outranks a
 * non-existent intrinsic ratio, and the first image of each feed was immune because core exempts the
 * LCP candidate from auto sizes. Dropping this declaration restores the attribute-derived ratio under
 * containment: measured `[121, 363, 211, 622, 242, 363, 484, 151]` where it had been eight 1500s.
 *
 * The reason this cannot be fixed by "put the square back and undo it here" is the sentence above: ANY
 * author declaration on this arm clobbers the attribute ratio. So the square is declared positively for
 * the layouts that want it, next to the base image rule below. Design §2 fact 6 — "an image holds its
 * natural ratio once `aspect-ratio` is released" — was measured with a data-URI SVG in a fixture and
 * does not hold for a real `sizes="auto"` image on a page; that fixture reproduces neither the
 * containment nor the core stylesheet. It is refuted rather than amended.
 *
 * The container arm below still collapses the columns to one on a narrow feed, which is a different
 * property and unaffected by any of this.
 */

/*
 * The carousel: a scroll-snap track.
 *
 * The SCROLLING styles and the FOCUSABLE element are the same element, and that is the whole point of
 * this rule being on `.jbbf-feed__track` rather than on a wrapper around the list. Measured: with a
 * focusable wrapper around a scrolling `<ul>`, ArrowRight moved the track by 0 — arrow keys scroll the
 * nearest scrollable ancestor of the focused element and never a descendant (design §2 fact 7).
 *
 * `grid-auto-columns` is derived from `--jbbf-columns`, so one setting means one thing across all three
 * layouts: `columns: 1` is the one-slide-with-a-peek carousel, and a site that raises it gets a
 * multi-item track for free. The alternative was a peek width of its own, which would have been a
 * second, undocumented knob.
 *
 * **Every track here must be an AUTO track, and `grid-template-columns: none` is what makes that true.**
 * The `@supports` block below gives *every* grid `repeat(var(--jbbf-columns), minmax(0, 1fr))`, and in an
 * **overflowing** `grid-auto-flow: column` grid those explicit tracks lose to the auto ones that follow:
 * the auto tracks claim their full percentage first, leaving the `minmax(0, …)` tracks a free space of
 * zero. **Measured 2026-09-28**, six items in a 600px feed: at `columns: 1` the used template was
 * `0px 600px 600px 600px 600px 600px`, so the newest item rendered at no width at all, and at
 * `columns: 3` the first THREE collapsed to `0px` (`0px 0px 0px 192px 192px 192px`). With this line the
 * used template is `600px` × 6, and `192px` × 6 at `columns: 3` — the formula above exactly, which is
 * the tie that makes "one setting, one meaning" a measurement rather than a hope. Two class selectors
 * beat the one-class rule below, so this is decided by SPECIFICITY; the container arm further down
 * overrides only the WIDTH, which is a different property and the right one for it to touch.
 */
.jbbf-feed__track {
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	scroll-padding-inline-start: var(--jbbf-gap);
}

.jbbf-feed--carousel .jbbf-feed__grid {
	grid-auto-flow: column;
	grid-template-columns: none;
	grid-auto-columns: calc(
		(100% - (var(--jbbf-columns) - 1) * var(--jbbf-gap)) / var(--jbbf-columns)
	);
}

.jbbf-feed--carousel .jbbf-feed__item {
	scroll-snap-align: start;
}

.jbbf-feed__controls {
	display: flex;
	gap: var(--jbbf-gap);
	justify-content: flex-end;
	margin-block-start: calc(var(--jbbf-gap) / 2);
}

/*
 * The `hidden` attribute the renderer writes needs this rule to mean anything, and its absence is not a
 * cosmetic defect: an AUTHOR `display` beats the UA stylesheet's `[hidden] { display: none }`.
 * **Measured 2026-09-28**: a `[hidden]` element carrying the rule above computed to `display: flex` and
 * rendered 30px tall, buttons and all. Without this, the controls are visible before the script runs, on
 * a track that does not overflow, and with no JavaScript at all — which is the two dead buttons design
 * decision 5 exists to avoid, and the opposite of what `controls()`, `initCarousel()` and the markup
 * contract each say in their own words. Nothing in either test lane can see it: jsdom performs no
 * layout and the assertions are on the attribute and the property, never on the computed display.
 *
 * Decided by SPECIFICITY — two class selectors against one — rather than by `!important`, which this
 * file's header rules out. That is the same mechanism the scoped square (`--grid`/`--carousel` against
 * the bare class) uses, and unlike the container arms below it is settled by weight and not by position.
 * (This cited `.jbbf-feed--masonry .jbbf-feed__image` as its precedent until that rule was deleted on
 * 2026-09-28; the deletion is the fix the masonry arm's own comment describes.)
 */
.jbbf-feed__controls[hidden] {
	display: none;
}

.jbbf-feed__control {
	padding: 0.4em 0.8em;
	border: 1px solid var(--jbbf-border);
	border-radius: var(--jbbf-radius);
	background-color: transparent;
	color: var(--text-color, #3f3f46);
	cursor: pointer;
}

.jbbf-feed__control:focus-visible {
	outline: var(--focus-width, 2px) solid var(--jbbf-focus);
	outline-offset: var(--focus-offset, 2px);
}

/*
 * One column outside the guard, so a browser without container queries still gets a readable feed
 * rather than six columns in a sidebar (CONVENTIONS §6).
 */
.jbbf-feed__grid {
	display: grid;
	gap: var(--jbbf-gap);
	grid-template-columns: 1fr;
	margin: 0;
	padding: 0;
	list-style: none;
}

@supports (container-type: inline-size) {
	.jbbf-feed__grid {
		grid-template-columns: repeat(var(--jbbf-columns), minmax(0, 1fr));
	}

	/*
	 * The container query WINS below 30rem; the setting decides above it. The two rules have equal
	 * specificity and this block is declared after the `--jbbf-columns` rule above, so SOURCE ORDER is
	 * what makes that true — swapping the two would invert the behaviour below 30rem rather than tidy
	 * it. Stated because the comment here used to read "the container query is the default and the
	 * setting overrides it — not the other way round", which is the reverse of what these two rules do
	 * and reads as a licence to swap them.
	 */
	@container (max-width: 30rem) {
		.jbbf-feed__grid {
			grid-template-columns: 1fr;
		}
	}

	/*
	 * The masonry arm. A TIE at equal specificity against `.jbbf-feed--masonry .jbbf-feed__grid`
	 * further up — NOT against the grid arm immediately above, which sets a different property and
	 * does not compete with this one. So SOURCE ORDER is what decides it, and this must stay after
	 * that masonry rule — it already does: that rule sits outside this block, above it.
	 * `CONVENTIONS.md` §6's paragraph on the grid describes the same mechanism, and its measurement
	 * is the reason.
	 */
	@container (max-width: 30rem) {
		.jbbf-feed--masonry .jbbf-feed__grid {
			column-count: 1;
		}
	}

	/*
	 * The carousel's arm: below 30rem one slide fills the track. It is a TIE with
	 * `.jbbf-feed--carousel .jbbf-feed__grid` — the rule OUTSIDE this block, above — because the two set
	 * the same property (`grid-auto-columns`) at the same specificity (`0,2,0`), so SOURCE ORDER decides
	 * it and this must stay after that rule. It does NOT compete with the masonry arm immediately above
	 * it: that sets `column-count` on a different element set, so no property is contested
	 * (CONVENTIONS §6).
	 */
	@container (max-width: 30rem) {
		.jbbf-feed--carousel .jbbf-feed__grid {
			grid-auto-columns: 100%;
		}
	}
}

.jbbf-feed__item {
	margin: 0;
}

/*
 * A host theme's rich-text styles must not restyle the feed's list (#101).
 *
 * A feed placed inside a rich-text container (`.wysiwyg` on the measurement site) meets that theme's
 * `ul`/`li` rules, which select by TYPE and so match the feed's list without any class of the feed's.
 * Measured on etch2, 2026-10-01 (probe: `docs/FEED-HOST-THEME-PROBE.md`) against the site's
 * `.wysiwyg ul { padding-inline-start: 1.6em }`, `.wysiwyg ul li { list-style-type: disc }` and
 * `.wysiwyg li + li { margin-block-start: 0.4em }` (specificity `0,1,1`, `0,1,2`, `0,1,2`): the list was
 * indented, every item showed a bullet, and every item after the first carried a top margin. Those are
 * `em` values, so they scale with the font size of the list and the item (inherited from the container, not
 * the root): 25.6px and 6.4px at 16px, 28.8px and 7.2px at 18px, which is why two measurement runs of the
 * same page at different font sizes disagree by that factor. In the grid and the carousel the
 * margin showed as a visible offset (the first tile sat higher than the others). In masonry the COMPUTED
 * margin is the same, but a multi-column layout truncates a margin at a column break, so the offset is
 * not uniform there (the first three items measured level).
 *
 * The resets are repeated here at a specificity above a one-class-and-two-types theme rule: `margin`,
 * `padding` and `list-style` on the list (`0,2,1`), `list-style` and `margin-block-start` on the items
 * (`0,3,0`). The `margin` and `padding` shorthands also cover sides that were not measured clobbered.
 * It is a SEPARATE block because raising the existing selectors would change what they win against:
 * the list at `0,2,1` would outrank the carousel's `grid-template-columns: none` (`0,2,0`), and the items
 * at `0,3,0` would outrank `.jbbf-feed--masonry .jbbf-feed__item { margin-block-end }` (`0,2,0`) — which
 * is why the item rule sets `margin-block-start` only.
 *
 * Where a theme still wins: for the list, `.container .wysiwyg ul` (`0,2,1`) TIES `ul.jbbf-feed__grid`, so
 * source order decides and that is the theme stylesheet's position, not this file's; anything higher wins
 * outright. For the items, a rule at exactly `0,3,0` (e.g. `.a .b .jbbf-feed__item`) TIES and source order
 * decides; `0,3,1` or more (e.g. `.a .b .c li`) wins outright. These rules also outrank a site's own
 * override of the same properties on the grid or items at one or two classes (`0,1,0`, `0,2,0`); override
 * them with higher specificity, not `!important` (header). The Stil step should set the feed's gap and
 * spacing through the custom properties above, not by overriding these.
 */
.jbbf-feed ul.jbbf-feed__grid {
	margin: 0;
	padding: 0;
	list-style: none;
}

.jbbf-feed .jbbf-feed__grid > .jbbf-feed__item {
	list-style: none;
	margin-block-start: 0;
}

.jbbf-feed__media {
	position: relative;
	display: block;
	overflow: hidden;
	border-radius: var(--jbbf-radius);
	background-color: var(--bg-light, #f4f4f5);
}

.jbbf-feed__image {
	display: block;
	width: 100%;
	height: auto;
	object-fit: cover;
}

/*
 * The forced square: a square keeps a card stable before the image arrives — the PRD's "no unexpected
 * cumulative layout shift" — and `object-fit: cover` keeps the crop honest while it does.
 *
 * Declared POSITIVELY, for the two layouts that want a square, and scoped to the card's own image.
 * Both halves are load-bearing and both were measured on 2026-09-28 (see the masonry arm above for the
 * full mechanism):
 *
 *  - POSITIVE, because the alternative shape — square on the base rule, released for masonry — cannot
 *    work. Releasing it means declaring `aspect-ratio: auto` on the masonry image, and that declaration
 *    replaces the `auto <w> / <h>` the browser derives from the image's `width`/`height` attributes,
 *    which is exactly the ratio masonry exists to show. There is no declaration that means "leave the
 *    ratio alone", so the arm that must not force a square must not declare anything.
 *  - `> .jbbf-feed__media`, because the lightbox's image is a `cloneNode(true)` of a card image and the
 *    dialog is rendered INSIDE the feed section, so a descendant selector would square the lightbox
 *    image too. Its parent is `.jbbf-lightbox__link`, so one child combinator excludes it — and the
 *    lightbox arm near the foot of this file then needs no `aspect-ratio` of its own either.
 *
 * `grid` is `FeedRenderer::DEFAULT_LAYOUT` and `LAYOUTS` admits only these three, so every feed carries
 * exactly one of these classes: an unscoped image is a masonry image, and there is no fourth case.
 */
.jbbf-feed--grid .jbbf-feed__media > .jbbf-feed__image,
.jbbf-feed--carousel .jbbf-feed__media > .jbbf-feed__image {
	aspect-ratio: 1 / 1;
}

.jbbf-feed__media:focus-visible {
	outline: var(--focus-width, 2px) solid var(--jbbf-focus);
	outline-offset: var(--focus-offset, 2px);
}

/* The label FR-28 requires: a word, not an icon, so a screen reader announces what the item is. */
.jbbf-feed__badge {
	position: absolute;
	inset-block-start: 0;
	inset-inline-start: 0;
	padding: 0.15em 0.5em;
	border-end-end-radius: var(--jbbf-radius);
	background-color: var(--jbbf-scrim);
	color: var(--text-light, #fafafa);
	font-size: var(--text-xs, 0.8rem);
	line-height: 1.6;
}

/*
 * A fully rounded card is a circle, and a badge at its corner lies outside it: measured on a 194 px card,
 * three of the badge's four corners were clipped and no badge showed. A corner at inset i lies inside the
 * inscribed circle from i = (1 - 1/sqrt(2)) / 2, about 14.6 % of the card, so the badge sits at 15 % on
 * both axes (a percentage, so it follows the card). Two classes, so it beats the rule above whatever the
 * source order. Measured on etch2 (task 5c): every corner inside on a 194 px card, the nearest by about
 * 1 px (half a percent of the card, by construction), and on square cards from 100 px ("Carousel") and
 * 90 px ("Video") up. The badge's own size does not scale, so below that its far corner is clipped.
 */
.jbbf-feed--radius-round .jbbf-feed__badge {
	inset-block-start: 15%;
	inset-inline-start: 15%;
}

.jbbf-feed__caption {
	margin-block-start: calc(var(--jbbf-gap) / 2);
	color: var(--text-color, #3f3f46);
	font-size: var(--text-s, 0.9rem);
}

.jbbf-feed__empty {
	margin: 0;
	padding: var(--space-m, 1rem);
	border: 1px solid var(--jbbf-border);
	border-radius: var(--jbbf-radius);
	color: var(--text-color, #3f3f46);
}

/*
 * Capped under the round step alone: its radius is half the viewport, which would make a message box a
 * pill. Not a `min()` over the knob on every feed: that changed a theme whose own radius is above 1rem,
 * and `min( 0, 1rem )` is invalid when a theme's `--radius-none` is the unitless `0`.
 */
.jbbf-feed--radius-round .jbbf-feed__empty {
	border-radius: 1rem;
}

/* The hover is wrapped rather than the transition alone: a scale that never runs is still a scale a
   reduced-motion reader asked not to have. */
@media (prefers-reduced-motion: no-preference) {
	/* Two classes, not one: a host theme's `.wysiwyg a { transition: opacity … }` is (0,1,1) and beat the
	   old single-class rule (#78). Honest specificity, no `!important`, so a site can still override it. */
	.jbbf-feed .jbbf-feed__media {
		transition: transform var(--transition-duration, 200ms) var(--ease-smooth, ease);
	}

	.jbbf-feed .jbbf-feed__media:hover {
		transform: scale(var(--jbbf-hover-scale));
	}
}

/* The lightbox. A closed `<dialog>` is `display: none`, so nothing here loads until it opens. */
.jbbf-lightbox {
	/* Not the feed's step. The dialog is rendered INSIDE the feed, so it would inherit `round`'s half-viewport
	   radius, and an open modal dialog scrolls (`overflow: auto`): a pill would clip a portrait image's corners.
	   Its buttons read this too, so they stay the theme's radius with the dialog. */
	--jbbf-radius: var(--radius-m, 0.5rem);
	padding: var(--space-s, 0.75rem);
	border: 0;
	border-radius: var(--jbbf-radius);
	background-color: var(--bg-light, #f4f4f5);
	max-inline-size: min(90vw, 60rem);
}

.jbbf-lightbox::backdrop {
	background-color: var(--jbbf-scrim);
}

/*
 * The image keeps its natural proportions here, which is the opposite of the card's cover crop: a
 * lightbox exists to show the whole picture, and `object-fit: cover` would defeat it.
 *
 * No `aspect-ratio` declaration, for the reason the masonry arm above gives in full: the image the
 * script clones here carries the card's `sizes="auto, …"`, so it is size-contained too, and a bare
 * `auto` would resolve its height to `contain-intrinsic-height` rather than to its own proportions.
 * Nothing has to be undone either — the square is scoped to `.jbbf-feed__media > .jbbf-feed__image`,
 * and this image's parent is `.jbbf-lightbox__link`.
 */
.jbbf-lightbox .jbbf-feed__image {
	display: block;
	inline-size: auto;
	max-inline-size: 100%;
	max-block-size: 80vh;
	object-fit: contain;
}

.jbbf-lightbox__close,
.jbbf-lightbox__prev,
.jbbf-lightbox__next {
	padding: 0.4em 0.8em;
	border: 1px solid var(--jbbf-border);
	border-radius: var(--jbbf-radius);
	background-color: transparent;
	color: var(--text-color, #3f3f46);
	cursor: pointer;
}

.jbbf-lightbox__close:focus-visible,
.jbbf-lightbox__prev:focus-visible,
.jbbf-lightbox__next:focus-visible {
	outline: var(--focus-width, 2px) solid var(--jbbf-focus);
	outline-offset: var(--focus-offset, 2px);
}
