The 1 file this component needs and nothing else, each fenced with the path it belongs at, followed by what the library's own verification covered and what it did not. No instructions attached — for placing it yourself, or handing it to something that has its own.
Read it first
Install kit: "Image bento features"
Library page: https://astro.baysixmedia.com/components/image-bento-features/
Files: 1 required, 1 supporting
Write each block below to the path named in its header, relative to the project root. A block marked (required) is one the component does not work without; (supporting) is documentation. Nothing here needs downloading except a block that says so in as many words.
===== FILE (required): src/components/ImageBentoFeatures.astro =====
---
/**
* Image bento features
*
* An eyebrow pill and a centred display heading over a five-tile bento: a
* wide navy text tile, a wide orange text tile, two square photo tiles and a
* tall photo tile with its caption set over a dark fade. The arrangement is
* the measured one — a 2×2 of wide/square tiles on the left, the tall tile
* spanning both rows on the right — and it restacks to one column on phones.
*
* Static: no script. Tiles are content, not controls, so nothing in the
* section is focusable unless the caller puts a link in it.
*
* Theming reads `--lx-*` custom properties with fallbacks, so a page can
* re-theme every logistics section from one ancestor and this one still
* works on its own.
*/
/**
* A photo slot. With `src` it is an `<img>`; without, a labelled striped
* placeholder that says what the photo should show and what size to get.
*/
export interface BentoImage {
src?: string;
/** Alt text for the photo. "" (the default) marks it decorative. */
alt?: string;
/** What the photo should show: the brief on the placeholder. */
hint?: string;
}
export interface BentoTile {
/**
* `navy` and `accent` are text tiles on a solid fill. `image` is a photo
* tile; give it a title and it gets the dark fade and a caption, leave the
* title out and it is the picture alone.
*/
tone: "navy" | "accent" | "image";
title?: string;
text?: string;
/** The photo for an `image` tile. Ignored on text tiles. */
image?: BentoImage;
/**
* The footer row on a navy tile: a stack of faces, a small accent figure
* and a label, behind a hairline. Only drawn on `navy` tiles.
*/
stat?: { value: string; label: string; avatars?: BentoImage[] };
/**
* The faint globe on a navy tile, or the map-and-pin pattern on an accent
* tile. On by default; purely decorative either way.
*/
pattern?: boolean;
}
interface Props {
/** Small uppercase label in the pill. `""` hides the pill. */
eyebrow?: string;
heading?: string;
/** The tiles take the next level down. */
headingLevel?: "h2" | "h3";
/**
* Laid out in reading order for a phone: the first five take the measured
* desktop positions (wide navy, square, wide accent, square, tall).
* A sixth onward flows into extra rows below.
*/
tiles?: BentoTile[];
/** `""` when the page already loads Bebas Neue and Poppins. */
fontStylesheet?: string;
id?: string;
}
/* No built-in tiles, figures or photo briefs: every tile comes from the caller. */
const {
eyebrow = "Why choose us",
heading = "",
headingLevel = "h2",
tiles = [] as BentoTile[],
fontStylesheet = "https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Poppins:wght@400;500;600&display=swap",
id,
} = Astro.props;
const Heading = headingLevel;
const TileHeading = headingLevel === "h2" ? "h3" : "h4";
const uid = id ?? `ibf-${Math.random().toString(36).slice(2, 9)}`;
/*
* What to shoot for each slot, from the measured desktop boxes: the wide
* slots are 527x260, the squares 260x260 (cropped to 335x287 on phones), the
* tall one 393x529. Sizes are roughly 2-3x the largest rendered box.
*/
const specFor = (index: number) =>
index === 4
? "3:4 · 1200 × 1600 px"
: index === 0 || index === 2
? "2:1 · 1600 × 800 px"
: "~1:1 · 1000 × 1000 px";
---
{tiles.length > 0 && fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
{tiles.length > 0 && (
<section class="ibf" id={id} aria-labelledby={heading ? `${uid}-heading` : undefined}>
<div class="ibf-inner">
{(eyebrow || heading) && (
<div class="ibf-head">
{eyebrow && <p class="ibf-eyebrow">{eyebrow}</p>}
{heading && <Heading class="ibf-heading" id={`${uid}-heading`}>{heading}</Heading>}
</div>
)}
<ul class="ibf-grid" role="list">
{tiles.map((tile, index) => {
const isImage = tile.tone === "image";
const hasCopy = Boolean(tile.title || tile.text);
const pattern = tile.pattern !== false;
return (
<li
class:list={[
"ibf-tile",
`ibf-tile--${tile.tone}`,
isImage && hasCopy && "ibf-tile--captioned",
]}
>
{isImage && tile.image?.src && (
<img class="ibf-photo" src={tile.image.src} alt={tile.image.alt ?? ""} loading="lazy" decoding="async" />
)}
{isImage && !tile.image?.src && (
<div
class:list={["ibf-ph", hasCopy && "ibf-ph--dark"]}
role="img"
aria-label={`Image placeholder: ${tile.image?.hint ?? "photo"}`}
>
<span class="ibf-ph-text">
<span class="ibf-ph-label">Image</span>
{tile.image?.hint && <span class="ibf-ph-hint">{tile.image.hint}</span>}
<span class="ibf-ph-spec">{specFor(index)}</span>
</span>
</div>
)}
{isImage && hasCopy && <span class="ibf-fade" aria-hidden="true" />}
{/* Faint globe, measured at 7% opacity behind the navy tile's copy. */}
{tile.tone === "navy" && pattern && (
<svg class="ibf-globe" viewBox="0 0 110 118" aria-hidden="true" focusable="false">
<g fill="none" stroke="currentColor" stroke-width="2">
<circle cx="55" cy="52" r="48" />
<ellipse cx="55" cy="52" rx="22" ry="48" />
<path d="M7 52h96M14 28h82M14 76h82" />
<path d="M40 104l-8 12 22-8" />
</g>
</svg>
)}
{/* Street-map lines and a location pin, full bleed behind the copy. */}
{tile.tone === "accent" && pattern && (
<svg class="ibf-map" viewBox="0 0 440 260" preserveAspectRatio="xMidYMid slice" aria-hidden="true" focusable="false">
<g fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M-10 40 L120 90 L180 -10 M60 270 L120 90 L300 150 L440 60 M300 150 L260 270 M200 120 L230 -10 M-10 180 L160 200 L200 270 M160 200 L220 130 M300 150 L450 210 M340 -10 L380 110 L450 130" />
<path d="M0 120 L90 140 M380 110 L330 270 M250 40 L380 20" />
</g>
<g fill="none" stroke="currentColor" stroke-width="2.5">
<path d="M370 150 C340 118 322 96 322 74 a48 48 0 0 1 96 0 C418 96 400 118 370 150 Z" />
<path d="M348 66 l22 -12 22 12 v24 l-22 12 -22 -12 Z M348 66 l22 12 22 -12 M370 78 v24" />
</g>
</svg>
)}
{hasCopy && (
<div class="ibf-copy">
{tile.title && <TileHeading class="ibf-title">{tile.title}</TileHeading>}
{tile.text && <p class="ibf-text">{tile.text}</p>}
</div>
)}
{tile.tone === "navy" && tile.stat && (
<div class="ibf-stat">
{tile.stat.avatars && tile.stat.avatars.length > 0 && (
<span class="ibf-faces">
{tile.stat.avatars.map((face) =>
face.src ? (
<img class="ibf-face" src={face.src} alt={face.alt ?? ""} width="40" height="40" loading="lazy" decoding="async" />
) : (
<span
class="ibf-face ibf-face--ph"
role="img"
aria-label={`Image placeholder: ${face.hint ?? "portrait"} (1:1, 160 × 160 px)`}
/>
),
)}
</span>
)}
<span class="ibf-stat-copy">
<span class="ibf-stat-value">{tile.stat.value}</span>
<span class="ibf-stat-label">{tile.stat.label}</span>
</span>
</div>
)}
</li>
);
})}
</ul>
</div>
</section>
)}
<style>
.ibf {
container-type: inline-size;
box-sizing: border-box;
background: #ffffff;
color: var(--lx-navy, #0a2540);
font-family: var(--lx-body, Poppins, ui-sans-serif, system-ui, sans-serif);
}
.ibf *,
.ibf *::before,
.ibf *::after {
box-sizing: border-box;
}
/* Phone first: 60px block, 20px inline, 40px between head and grid. */
.ibf-inner {
max-width: calc(1200px + 60px);
margin-inline: auto;
padding: 60px 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 40px;
}
.ibf-head {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
text-align: center;
}
.ibf-eyebrow {
margin: 0;
padding: 8px 16px;
border-radius: 100px;
/* Inset, so the measured 30px pill height is not grown by a border. */
box-shadow: inset 0 0 0 1px var(--lx-hairline, rgba(10, 37, 64, 0.14));
font-size: 14px;
font-weight: 500;
line-height: 14px;
text-transform: uppercase;
color: var(--lx-navy, #0a2540);
}
.ibf-heading {
margin: 0;
max-width: 26rem;
font-family: var(--lx-display, "Bebas Neue", Impact, "Arial Narrow", sans-serif);
font-size: 30px;
font-weight: 400;
line-height: 1;
letter-spacing: -0.02em;
color: var(--lx-navy, #0a2540);
text-wrap: balance;
}
/* ---- the grid -------------------------------------------------------- */
.ibf-grid {
width: 100%;
margin: 0;
padding: 0;
list-style: none;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 10px;
}
.ibf-tile {
position: relative;
isolation: isolate;
overflow: hidden;
border-radius: 10px;
display: flex;
flex-direction: column;
justify-content: flex-end;
min-width: 0;
color: #ffffff;
}
.ibf-tile--navy {
background: var(--lx-navy, #0a2540);
justify-content: space-between;
gap: 40px;
padding: 20px;
}
.ibf-tile--accent {
background: var(--lx-accent, #ff5f00);
padding: 100px 20px 20px;
}
/* A bare photo tile takes its shape from the measured phone crop, 335×287. */
.ibf-tile--image {
aspect-ratio: 335 / 287;
background: var(--lx-surface, #f6f6f6);
}
.ibf-tile--captioned {
aspect-ratio: auto;
padding: 200px 20px 20px;
background: #111111;
}
.ibf-photo {
position: absolute;
inset: 0;
z-index: -2;
width: 100%;
height: 100%;
object-fit: cover;
}
/* ---- photo placeholder ----------------------------------------------- */
.ibf-ph {
position: absolute;
inset: 0;
z-index: -2;
display: grid;
place-items: center;
padding: 16px;
text-align: center;
border-radius: inherit;
--ibf-ph-ink: rgba(10, 37, 64, 0.62);
background: repeating-linear-gradient(135deg, #e6e9ee 0 12px, #edf0f3 12px 24px);
color: var(--ibf-ph-ink);
border: 1px dashed color-mix(in srgb, var(--ibf-ph-ink) 35%, transparent);
}
/* Under a caption and its fade: dark stripes, and the brief sits at the
top so the caption at the bottom never covers it. */
.ibf-ph--dark {
--ibf-ph-ink: rgba(255, 255, 255, 0.72);
place-items: start center;
padding-top: 28px;
background:
repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.06) 0 12px, rgba(255, 255, 255, 0.1) 12px 24px),
#1b2530;
}
.ibf-ph-text {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
max-width: 28ch;
font-family: var(--lx-body, Poppins, ui-sans-serif, system-ui, sans-serif);
}
.ibf-ph-label {
font-size: 11px;
font-weight: 500;
line-height: 1.2;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.ibf-ph-hint {
font-size: 13px;
font-weight: 500;
line-height: 1.4;
}
.ibf-ph-spec {
font-size: 12px;
font-weight: 400;
line-height: 1.3;
}
/* A face is too small to read a brief: stripes only; the hint rides in
the accessible label. */
.ibf-face--ph {
display: block;
background: repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.14) 0 5px, rgba(255, 255, 255, 0.24) 5px 10px), var(--lx-navy, #0a2540);
border: 1px dashed rgba(255, 255, 255, 0.35);
}
.ibf-fade {
position: absolute;
inset: 0;
z-index: -1;
background: linear-gradient(rgba(17, 17, 17, 0) 51%, rgb(17, 17, 17) 100%);
}
.ibf-globe {
position: absolute;
z-index: -1;
right: 20px;
bottom: 16px;
width: 72px;
height: auto;
color: #ffffff;
opacity: 0.07;
}
.ibf-map {
position: absolute;
inset: 0;
z-index: -1;
width: 100%;
height: 100%;
color: #ffffff;
opacity: 0.22;
}
.ibf-copy {
display: flex;
flex-direction: column;
gap: 12px;
}
.ibf-title {
margin: 0;
font-family: var(--lx-display, "Bebas Neue", Impact, "Arial Narrow", sans-serif);
font-size: 20px;
font-weight: 400;
line-height: 1;
letter-spacing: -0.02em;
color: #ffffff;
}
.ibf-text {
margin: 0;
max-width: 21rem;
font-size: 16px;
line-height: 25.6px;
color: rgba(255, 255, 255, 0.8);
}
/* ---- navy tile footer ------------------------------------------------ */
.ibf-stat {
display: flex;
align-items: center;
gap: 10px;
padding-top: 20px;
border-top: 1px solid rgba(255, 255, 255, 0.14);
}
.ibf-faces {
display: flex;
flex: none;
--ibf-face: 32px;
}
/* Each face tucks a quarter of itself under the one before, as measured. */
.ibf-face {
width: var(--ibf-face);
height: var(--ibf-face);
border-radius: 50%;
object-fit: cover;
box-shadow: 0 0 0 2px var(--lx-navy, #0a2540);
}
.ibf-face + .ibf-face {
margin-left: calc(var(--ibf-face) * -0.25);
}
.ibf-stat-copy {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.ibf-stat-value {
font-size: 14px;
font-weight: 500;
line-height: 14px;
text-transform: uppercase;
color: var(--lx-accent, #ff5f00);
}
.ibf-stat-label {
font-size: 16px;
line-height: 25.6px;
color: #ffffff;
}
/* ---- tablet: 2-up, wide tiles span both columns ---------------------- */
@container (min-width: 795px) {
.ibf-inner {
padding: 90px 30px;
gap: 56px;
}
.ibf-heading {
font-size: 40px;
}
.ibf-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-auto-rows: minmax(260px, auto);
}
.ibf-tile--navy {
padding: 30px 30px 20px;
}
.ibf-tile--accent {
padding: 134px 30px 30px;
}
.ibf-tile--image {
aspect-ratio: auto;
}
.ibf-tile--captioned {
padding: 180px 30px 40px;
}
.ibf-title {
font-size: 32px;
}
.ibf-faces {
--ibf-face: 40px;
}
.ibf-globe {
right: 30px;
bottom: auto;
top: 103px;
width: 110px;
}
/* DOM order is the phone order: navy, photo, accent, photo, tall. */
.ibf-tile:nth-child(1) { grid-column: 1 / -1; grid-row: 1; }
.ibf-tile:nth-child(2) { grid-column: 1; grid-row: 2; }
.ibf-tile:nth-child(4) { grid-column: 2; grid-row: 2; }
.ibf-tile:nth-child(3) { grid-column: 1 / -1; grid-row: 3; }
.ibf-tile:nth-child(5) { grid-column: 1 / -1; grid-row: 4; min-height: 420px; }
}
/* ---- desktop: the measured bento ------------------------------------- */
@container (min-width: 1180px) {
.ibf-inner {
padding: 120px 30px;
gap: 70px;
}
.ibf-heading {
font-size: 48px;
}
/*
* 260 · 257 · 260 · 393 with 10px gaps is exactly 1200. The wide tiles
* (527) cross the middle track from opposite sides, which is what makes
* the left block read as a staggered 2×2 rather than a plain grid.
*/
.ibf-grid {
grid-template-columns: 260fr 257fr 260fr 393fr;
grid-template-rows: 260px 259px;
grid-auto-rows: 260px;
}
/* A captioned photo in a 260px slot only has room for the copy; the
measured 134px top padding belongs to the tall slot. */
.ibf-tile--captioned {
padding: 30px;
}
.ibf-tile--captioned:nth-child(5) {
padding: 134px 30px 40px;
}
.ibf-tile:nth-child(1) { grid-column: 1 / 3; grid-row: 1; }
.ibf-tile:nth-child(2) { grid-column: 3; grid-row: 1; }
.ibf-tile:nth-child(3) { grid-column: 2 / 4; grid-row: 2; }
.ibf-tile:nth-child(4) { grid-column: 1; grid-row: 2; }
.ibf-tile:nth-child(5) { grid-column: 4; grid-row: 1 / 3; min-height: 0; }
.ibf-tile:nth-child(n + 6) { grid-column: auto; grid-row: auto; }
}
@media (forced-colors: active) {
.ibf-tile {
border: 1px solid CanvasText;
}
}
</style>
===== END src/components/ImageBentoFeatures.astro =====
===== FILE (supporting): src/components/ImageBentoFeatures.md =====
# Image bento features
An eyebrow pill and a centred display heading over a five-tile bento: a wide
navy text tile with a stat footer, a wide orange text tile, two square photo
tiles and a tall photo tile with its caption over a dark fade.
Catalog page: `/components/image-bento-features`
## Purpose
The "why choose us" band on a service homepage: three or four short selling
points, broken up with photos so it doesn't read as a list. The layout is
fixed at five slots on desktop; it is a composition, not a masonry grid.
## Files required
**`ImageBentoFeatures.astro`**: the whole component.
- No imports, no packages, no script.
- Component-scoped `<style>`. Colours and faces come from `--lx-*` custom
properties with fallbacks, so it works alone and re-themes from one ancestor.
- Loads Bebas Neue and Poppins from Google Fonts through `fontStylesheet`
(pass `""` if the page already loads them).
`ImageBentoFeatures.md`: this file.
No image files. Every photo slot defaults to a labelled placeholder (see
**Images**), and the globe watermark and the orange tile's map-and-pin
pattern are inline SVG.
## Basic usage
```astro
---
import ImageBentoFeatures from "../components/ImageBentoFeatures.astro";
---
<ImageBentoFeatures
heading="Why growers ship with us"
tiles={[
{ tone: "navy", title: "On-time pickup", text: "…", stat: { value: "98%", label: "Pickups on schedule" } },
{ tone: "image", image: { src: "/img/dock.jpg" } },
{ tone: "accent", title: "Cold chain", text: "…" },
{ tone: "image", image: { src: "/img/yard.jpg" } },
{ tone: "image", image: { src: "/img/night.jpg" }, title: "Overnight lanes", text: "…" },
]}
/>
```
## Props
| Prop | Type | Default | What it does |
| :--- | :--- | :--- | :--- |
| `eyebrow` | `string?` | `"Why choose us"` | The pill label. `""` hides it. |
| `heading` | `string?` | `""` | Centred display heading. Omitted when empty. |
| `headingLevel` | `"h2" \| "h3"?` | `"h2"` | Tile titles take the next level down. |
| `tiles` | `BentoTile[]?` | `[]` | See below. No tiles → the component renders nothing. |
| `fontStylesheet` | `string?` | Google Fonts URL | `""` turns the `<link>` off. |
| `id` | `string?` | none | Root id. Also seeds the heading id. |
```ts
interface BentoImage {
src?: string; // unset = placeholder
alt?: string; // "" (default) = decorative
hint?: string; // what the photo should show, printed on the placeholder
}
interface BentoTile {
tone: "navy" | "accent" | "image";
title?: string;
text?: string;
image?: BentoImage; // image tiles only
stat?: { value: string; label: string; avatars?: BentoImage[] }; // navy only
pattern?: boolean; // globe on navy, map on accent; default true
}
```
### Slot order
Tiles are written in **phone reading order**. The first five take the
measured desktop positions:
| # | Desktop position | Intended tone |
| :--- | :--- | :--- |
| 1 | top-left, wide (527) | navy |
| 2 | top-middle, square (260) | image |
| 3 | bottom-middle, wide (527) | accent |
| 4 | bottom-left, square (260) | image |
| 5 | right column, both rows (393 × 529) | captioned image |
Any tone works in any slot; those are what the design was drawn with. A
sixth tile and on are auto-placed in extra 260px rows.
An `image` tile with a `title` or `text` gets the dark fade (transparent at
51% to `#111` at the bottom) and its caption. Without either it is the
picture alone.
## Images
Every photo slot is `{ src?, alt?, hint? }`. Without `src` it renders a
striped placeholder in the same box, labelled `IMAGE` with the hint and the
size to shoot for, and `role="img"` with `aria-label="Image placeholder: …"`.
Light stripes on bare tiles; dark stripes under a caption, with the brief
at the top so the caption never covers it.
The component has no default tiles, so no default hints either: a slot
prints the `hint` you give it, or just `IMAGE` and the size. The hints below
are the ones the catalog demo passes.
| Slot | Demo hint | Ratio · size | Measured box |
| :--- | :--- | :--- | :--- |
| 2, square | Forklift in a bright, empty warehouse aisle, racking and ceiling lights receding | ~1:1 · 1000 × 1000 px | 260 × 260 (335 × 287 phone) |
| 4, square | Container ship being loaded at a port, cranes against a blue sky, wet quay | ~1:1 · 1000 × 1000 px | 260 × 259 (335 × 287 phone) |
| 5, tall | Semi truck head-on at night, headlights on, motion-blurred tunnel or highway lights | 3:4 · 1200 × 1600 px | 393 × 529 |
| 1 or 3, wide (if you put a photo there) | none | 2:1 · 1600 × 800 px | 527 × 260 |
| Faces in the stat row (×3) | Head-and-shoulders portrait, driver or dispatcher, plain background | 1:1 · 160 × 160 px | 40 × 40 (32 phone) |
Faces are too small to print a brief: they show stripes only, and the hint
and size ride in the accessible label.
## Behaviour
None: a static section. Nothing moves, nothing is focusable unless you put a
link in a tile's copy yourself.
## Responsive
Container queries against the section, not the viewport.
| Section width | Layout |
| :--- | :--- |
| ≥ 1180px | The measured bento: tracks `260fr 257fr 260fr 393fr`, rows 260/259, 10px gaps, 120px block padding, 48px heading, 32px tile titles. |
| 795–1179px | Two columns: wide tiles span both, the two squares sit side by side, the tall tile becomes a 420px-min band. 32px titles. |
| < 795px | One column in DOM order, 60/20 padding, 30px heading, 20px titles, photo tiles at 335:287. |
## Accessibility
- Section labelled by its heading; tiles are a `<ul>`, titles are real
headings one level under the section heading.
- Photos default to `alt=""` because they illustrate copy that already says
it. Pass `alt` when a photo carries its own information.
- Placeholders are `role="img"` with an "Image placeholder: …" label, so the
demo state is announced honestly rather than silently empty.
- Watermark and map pattern are `aria-hidden` inline SVG.
- Body text on navy is white at 80%, and on orange it's the same. **Orange
`#ff5f00` with white 16px text is about 3:1**, which passes for large text
only. That matches the reference; if it matters for your site, darken
`--lx-accent` on this section.
- Forced colours: tiles get a real border.
## Reduced motion
Nothing to reduce: the component has no animation or transition.
## Without JavaScript
Identical. It ships no script.
## Approximations
- **The two "mystery" nodes in the dump**: the 0.07-opacity element is a
globe-shaped watermark behind the navy tile's copy (redrawn as an original
line globe). The `translate(-20px)` is **not a hover arrow**: it is the
middle avatar in the face stack, pulled left so the faces overlap. There is
no hover state on any tile in the reference, so none was added.
- Face stack: rendered as 40px (32px on phone) circles overlapping by a
quarter with a 2px navy ring. The dump's absolute-position arithmetic
doesn't resolve to an exact overlap; the screenshot shows about 10px.
- The hairline above the stat footer is visible in the screenshot but not in
the computed styles (Framer draws it separately); 1px white at 14% is a
visual match, not a measured value.
- The orange tile's background art was a line map with a pin; redrawn from
scratch at 22% white.
- No photos ship: every image slot is a placeholder until you pass `src`.
- Tablet has no capture; the 2-column arrangement is interpolated.
- Phone reorders the second row (accent before the port photo) exactly as
measured. Here that's done by DOM order rather than CSS `order`, so reading
order and visual order always agree.
===== END src/components/ImageBentoFeatures.md =====
--- VERIFIED IN THE COMPONENT LIBRARY ---
Enrolled for verification but never run.
No verification run has been recorded.
No checks have passed yet.
No usable source fingerprint, so these results cannot be tied to the files below.
--- NOT PROVEN ---
- ClientRouter: not applicable — this component ships no script.
--- WHAT THIS DOES NOT CERTIFY ---
Verification was performed on the unmodified component, in an isolated fixture, in the
library. It says nothing about this project's CSS, routes, layout or data, and nothing
about the component once you adapt it. A verified component is a good starting point,
not a guarantee about the installation you are about to build.
--- TEST IN THIS PROJECT AFTER INSTALLING OR CUSTOMISING ---
- Horizontal overflow at 1440, 1280, 1024, 834, 390 and 320px.
- Keyboard operation end to end, including visible focus.
- The no-JavaScript render.
- prefers-reduced-motion: reduce.
- Two instances on one page, if this project renders more than one.
- If this project uses <ClientRouter />: teardown and reinitialisation across a real navigation.