The 5 files 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: "Team register"
Library page: https://astro.baysixmedia.com/components/team-register/
Files: 5 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/TeamRegister.astro =====
---
/**
* TeamRegister (candidate, Step 40)
*
* A team as a ruled register, not a headshot grid. The section's lead
* (eyebrow, heading, intro, one action) holds its own column beside the
* register on wide sections and sits above it on narrow ones. Each person is
* one ruled row: a small 4:5 plate, then the name set in display type with
* the person's credentials as a distinct line under it, then their role and
* focus, then their own contact actions. People are grouped under the
* business's own `group` labels, each group under a heavy rule, in the order
* the groups first appear.
*
* The plate is the same object with or without a photograph. A supplied
* portrait is printed into the plate (tinted by default: greyscale multiplied
* onto the group's tone, so mixed photographs read as one set); without one,
* the plate carries a monogram built from the supplied name on the group's
* tone. A set with no photographs at all is a set of toned plates, by design.
*
* Three scales, chosen from the number of people:
* few (2–3) large plates and names: principals, not a sparse list;
* mid (4–6) the drawn-for case;
* many (7–12) compact rows that still align as a register.
*
* Truth rules. Every name, role, credential, focus line, group, portrait,
* profile link, phone and email renders only when supplied, exactly as
* supplied, in the order supplied. No default people, credentials, badges,
* ratings or bios. Credentials are shown as stated (joined with commas), never
* expanded or verified. No people, no section. Links go through safeHref; tel:
* is built from the digits of the supplied number; mailto: only from a
* well-formed address. A person with only a name and role is a complete row.
*
* Responsive (container queries on the section's own width): the lead sits
* above the register below 1120px (heading and intro split from 700px), and
* holds a sticky 4-of-12 column beside it from 1120px. Rows follow the
* register's own width: below 640px the plate sits beside one text column
* (name, credentials, role, focus, actions); from 640px the row becomes
* aligned columns — plate, name and credentials, role and focus, actions (the
* few scale keeps role under the name so the names can be set large).
*
* Reads, never declares: --tr-bg, --tr-text, --tr-text-muted, --tr-brand,
* --tr-brand-text, --tr-border, --tr-family, --tr-display, --tr-grid-max,
* --tr-grid-gutter, --tr-heading-size. No script, no motion beyond link colour.
*/
export interface TeamRegisterPerson {
/** The person's name as they use it professionally. */
name: string;
/** Their role or title, as supplied. */
role?: string;
/** Credentials as stated ("MD", "FACP", "DRE #01234567"); shown joined with commas. */
credentials?: readonly string[];
/** Specialty, market or focus, as stated. */
focus?: string;
/** The business's own grouping ("Physicians", "Care team"). */
group?: string;
/** A real photograph of this person. Omitted: a monogram plate. */
portrait?: { src: string; alt?: string } | null;
/** Their own profile page. */
href?: string;
email?: string;
phone?: string;
}
export interface TeamRegisterProps {
heading: string;
headingLevel?: "h2" | "h3";
eyebrow?: string;
intro?: string;
people?: readonly TeamRegisterPerson[];
/** One action in the lead, e.g. booking or the full team page. */
cta?: { label: string; href: string } | null;
/** "tinted" prints every portrait in greyscale onto its group's tone; "natural" keeps the photographs' colour. */
portraitTone?: "tinted" | "natural";
/** Words on the per-person actions and the screen-reader label for credentials. */
labels?: { credentials?: string; profile?: string; email?: string; call?: string };
fontStylesheet?: string;
class?: string;
id?: string;
}
interface Props extends TeamRegisterProps {}
const {
heading,
headingLevel = "h2",
eyebrow = "",
intro = "",
people = [],
cta = null,
portraitTone = "tinted",
labels = {},
fontStylesheet = "https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400..700&family=Instrument+Serif:ital@0;1&display=swap",
class: className,
id,
} = Astro.props;
const SAFE = /^(?:https?:|mailto:|tel:|[#/.?])/i;
const safeHref = (href: string | undefined) => {
const h = (href ?? "").trim();
return h && SAFE.test(h) ? h : "";
};
const text = (v: unknown) => (typeof v === "string" ? v.replace(/\s+/g, " ").trim() : "");
const telHref = (n: string) => {
const digits = n.replace(/[^\d+]/g, "").replace(/(?!^)\+/g, "");
return digits.replace(/\D/g, "").length >= 3 ? `tel:${digits}` : "";
};
const emailOk = (e: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e);
const L = {
credentials: text(labels?.credentials) || "Credentials",
profile: text(labels?.profile) || "Profile",
email: text(labels?.email) || "Email",
call: text(labels?.call) || "Call",
};
/* Two initials from the supplied name: honorifics and anything after a comma or in brackets are skipped. */
const HONORIFIC = /^(?:dr|mr|mrs|ms|mx|prof|rev|sr|sra|fr)\.?$/i;
function monogram(name: string): string {
const core = name.split(",")[0].replace(/\([^)]*\)/g, " ");
const words = core.split(/\s+/).filter((w) => w && !HONORIFIC.test(w) && /\p{L}/u.test(w));
const first = (w: string | undefined) => (w ? (Array.from(w).find((ch) => /\p{L}/u.test(ch)) ?? "") : "");
if (words.length === 0) return first(name).toLocaleUpperCase();
if (words.length === 1) return first(words[0]).toLocaleUpperCase();
return (first(words[0]) + first(words[words.length - 1])).toLocaleUpperCase();
}
type Act = { key: "profile" | "phone" | "email"; label: string; href: string };
type Person = {
name: string;
initials: string;
role: string;
focus: string;
credentials: string[];
portrait: { src: string; alt: string } | null;
actions: Act[];
};
function clean(p: TeamRegisterPerson | null | undefined): (Person & { group: string }) | null {
if (!p || typeof p !== "object") return null;
const name = text(p.name);
if (!name) return null;
const credentials = (Array.isArray(p.credentials) ? p.credentials : []).map(text).filter(Boolean);
const src = safeHref(p.portrait?.src) || (text(p.portrait?.src).startsWith("data:image/") ? text(p.portrait?.src) : "");
const actions: Act[] = [];
const profile = safeHref(p.href);
if (profile) actions.push({ key: "profile", label: L.profile, href: profile });
const phone = text(p.phone);
const tel = phone ? telHref(phone) : "";
if (tel) actions.push({ key: "phone", label: phone, href: tel });
const email = text(p.email);
if (email && emailOk(email)) actions.push({ key: "email", label: L.email, href: `mailto:${email}` });
return {
name,
initials: monogram(name),
role: text(p.role),
focus: text(p.focus),
credentials,
portrait: src ? { src, alt: text(p.portrait?.alt) } : null,
actions,
group: text(p.group),
};
}
const list = (Array.isArray(people) ? people : []).map(clean).filter((c): c is Person & { group: string } => c !== null);
/* Groups in the order they first appear; people without a group gather in an unlabelled group at the end. */
const labelled = list.some((p) => p.group);
const order: string[] = [];
for (const p of list) if (p.group && !order.includes(p.group)) order.push(p.group);
const groups = labelled
? [
...order.map((label, i) => ({ label, tone: i % 4, people: list.filter((p) => p.group === label) })),
{ label: "", tone: order.length % 4, people: list.filter((p) => !p.group) },
].filter((g) => g.people.length > 0)
: [{ label: "", tone: 0, people: list }];
const total = list.length;
const scale = total <= 3 ? "few" : total <= 6 ? "mid" : "many";
const quiet = list.every((p) => p.actions.length === 0);
const leadAction = cta && text(cta.label) && safeHref(cta.href) ? { label: text(cta.label), href: safeHref(cta.href) } : null;
const title = text(heading);
const Heading = headingLevel;
const Sub = headingLevel === "h2" ? "h3" : "h4";
const Name = labelled ? (headingLevel === "h2" ? "h4" : "h5") : Sub;
---
{total > 0 && (
<>
{fontStylesheet && <link rel="stylesheet" href={fontStylesheet} />}
<section class:list={["tr", `tr--${scale}`, `tr--${portraitTone === "natural" ? "natural" : "tinted"}`, { "tr--quiet": quiet }, className]} id={id}>
<div class="tr-inner">
{(eyebrow || title || intro || leadAction) && (
<div class="tr-lead">
{(eyebrow || title) && (
<div class="tr-lead-head">
{eyebrow && <p class="tr-eyebrow">{eyebrow}</p>}
{title && <Heading class="tr-title">{title}</Heading>}
</div>
)}
{(intro || leadAction) && (
<div class="tr-lead-body">
{intro && <p class="tr-intro">{intro}</p>}
{leadAction && <a class="tr-cta" href={leadAction.href}>{leadAction.label}</a>}
</div>
)}
</div>
)}
<div class="tr-register">
{groups.map((g) => (
<div class:list={["tr-group", { "tr-group--labelled": g.label }]}>
{g.label && <Sub class="tr-group-label">{g.label}</Sub>}
<ul class="tr-list" role="list">
{g.people.map((p) => (
<li class="tr-person">
<div class="tr-plate" data-tone={g.tone}>
{p.portrait ? (
<img src={p.portrait.src} alt={p.portrait.alt} width="400" height="500" loading="lazy" decoding="async" />
) : (
<span class="tr-mono" aria-hidden="true">{p.initials}</span>
)}
</div>
<div class="tr-who">
<Name class="tr-name">{p.name}</Name>
{p.credentials.length > 0 && (
<p class="tr-cred">
<span class="tr-sr">{L.credentials}: </span>
{p.credentials.map((c, i) => <><span class="tr-cred-item">{c}{i < p.credentials.length - 1 ? "," : ""}</span>{i < p.credentials.length - 1 ? " " : ""}</>)}
</p>
)}
</div>
{(p.role || p.focus) && (
<div class="tr-work">
{p.role && <p class="tr-role">{p.role}</p>}
{p.focus && <p class="tr-focus">{p.focus}</p>}
</div>
)}
{p.actions.length > 0 && (
<div class="tr-acts">
{p.actions.map((a) => (
<a class:list={["tr-act", `tr-act--${a.key}`]} href={a.href}>
<span>{a.label}</span><span class="tr-sr">, {a.key === "phone" ? `${L.call} ${p.name}` : p.name}</span>
</a>
))}
</div>
)}
</li>
))}
</ul>
</div>
))}
</div>
</div>
</section>
</>
)}
<style>
.tr {
--tr-surface: var(--tr-bg, #f2f4f3);
--tr-ink: var(--tr-text, #15212a);
--tr-soft: var(--tr-text-muted, #54606a);
--tr-accent: var(--tr-brand, #1d5667);
--tr-accent-ink: var(--tr-brand-text, #ffffff);
--tr-line: var(--tr-border, #cbd3d4);
--tr-font: var(--tr-family, "Instrument Sans", ui-sans-serif, system-ui, sans-serif);
--tr-font-display: var(--tr-display, "Instrument Serif", ui-serif, Georgia, serif);
container: tr / inline-size;
background: var(--tr-surface);
color: var(--tr-ink);
font-family: var(--tr-font);
}
.tr *,
.tr *::before,
.tr *::after {
box-sizing: border-box;
}
.tr-sr {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}
.tr-inner {
display: grid;
gap: 44px;
max-width: var(--tr-grid-max, 1232px);
margin: 0 auto;
padding: 64px 20px 72px;
}
/* ---- lead ---- */
.tr-lead {
display: grid;
gap: 20px;
min-width: 0;
}
.tr-lead-head,
.tr-lead-body {
min-width: 0;
}
.tr-eyebrow {
margin: 0 0 16px;
color: var(--tr-accent);
font: 600 15px/1.4 var(--tr-font);
overflow-wrap: anywhere;
}
.tr-title {
max-width: 14ch;
margin: 0;
font-family: var(--tr-font-display);
font-size: var(--tr-heading-size, clamp(40px, 4.8vw, 64px));
font-weight: 400;
line-height: 1;
letter-spacing: -0.012em;
text-wrap: balance;
overflow-wrap: break-word;
}
.tr-intro {
max-width: 40ch;
margin: 0;
color: var(--tr-soft);
font: 400 17px/1.6 var(--tr-font);
overflow-wrap: break-word;
}
.tr-cta {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 48px;
max-width: 100%;
padding: 12px 22px;
border-radius: 3px;
background: var(--tr-accent);
color: var(--tr-accent-ink);
font: 600 15px/1.25 var(--tr-font);
text-align: center;
text-decoration: none;
overflow-wrap: anywhere;
transition: background-color 0.2s ease;
}
.tr-intro + .tr-cta {
margin-top: 26px;
}
.tr-cta:hover {
background: color-mix(in srgb, var(--tr-accent) 80%, var(--tr-ink));
}
/* ---- register ---- */
.tr-register {
container: trr / inline-size;
display: grid;
gap: 52px;
min-width: 0;
}
.tr-group {
min-width: 0;
border-top: 2px solid var(--tr-ink);
}
.tr-group-label {
margin: 0;
padding: 16px 0 4px;
font-family: var(--tr-font-display);
font-size: 26px;
font-style: italic;
font-weight: 400;
line-height: 1.1;
letter-spacing: 0;
overflow-wrap: break-word;
}
.tr-list {
--tr-plate: 72px;
--tr-name: 27px;
--tr-pad: 20px;
min-width: 0;
margin: 0;
padding: 0;
list-style: none;
}
.tr--few .tr-list {
--tr-plate: 96px;
--tr-name: 33px;
--tr-pad: 24px;
}
.tr--many .tr-list {
--tr-plate: 56px;
--tr-name: 23px;
--tr-pad: 16px;
}
/* One row: plate beside a single text column (narrow register). */
.tr-person {
display: grid;
grid-template-columns: var(--tr-plate) minmax(0, 1fr);
grid-template-areas:
"plate who"
"plate work"
"plate act";
grid-template-rows: auto auto 1fr;
column-gap: 18px;
min-width: 0;
padding: var(--tr-pad) 0;
border-bottom: 1px solid var(--tr-line);
}
.tr-plate {
--tr-tone: color-mix(in srgb, var(--tr-accent) 16%, var(--tr-surface));
--tr-tone-ink: color-mix(in srgb, var(--tr-accent) 72%, var(--tr-ink));
container: trp / inline-size;
grid-area: plate;
position: relative;
align-self: start;
width: var(--tr-plate);
aspect-ratio: 4 / 5;
overflow: hidden;
border-radius: 2px;
background: var(--tr-tone);
}
.tr-plate[data-tone="1"] {
--tr-tone: color-mix(in srgb, var(--tr-accent) 32%, var(--tr-surface));
--tr-tone-ink: color-mix(in srgb, var(--tr-accent) 55%, var(--tr-ink));
}
.tr-plate[data-tone="2"] {
--tr-tone: color-mix(in srgb, var(--tr-ink) 11%, var(--tr-surface));
--tr-tone-ink: color-mix(in srgb, var(--tr-ink) 78%, var(--tr-surface));
}
.tr-plate[data-tone="3"] {
--tr-tone: color-mix(in srgb, var(--tr-accent) 8%, color-mix(in srgb, var(--tr-ink) 5%, var(--tr-surface)));
--tr-tone-ink: var(--tr-accent);
}
.tr-plate img {
position: absolute;
inset: 0;
display: block;
width: 100%;
height: 100%;
object-fit: cover;
object-position: 50% 30%;
}
/* Tinted: every photograph printed in greyscale onto its group's tone, so a mixed set reads as one. */
.tr--tinted .tr-plate img {
filter: grayscale(1) contrast(1.06) brightness(1.03);
mix-blend-mode: multiply;
}
.tr-mono {
position: absolute;
left: 10cqi;
bottom: 7cqi;
color: var(--tr-tone-ink);
font-family: var(--tr-font-display);
font-size: 50cqi;
font-weight: 400;
line-height: 0.8;
letter-spacing: -0.02em;
white-space: nowrap;
}
.tr-who,
.tr-work,
.tr-acts {
min-width: 0;
}
.tr-who {
grid-area: who;
}
.tr-name {
margin: 0;
font-family: var(--tr-font-display);
font-size: var(--tr-name);
font-weight: 400;
line-height: 1.04;
letter-spacing: -0.006em;
text-wrap: balance;
overflow-wrap: break-word;
}
/* Credentials: their own line, never mistaken for the role — small, set in the accent, tabular figures. */
.tr-cred {
margin: 8px 0 0;
color: var(--tr-accent);
font: 600 13.5px/1.5 var(--tr-font);
font-variant-numeric: tabular-nums lining-nums;
letter-spacing: 0.015em;
overflow-wrap: anywhere;
}
.tr-work {
grid-area: work;
margin-top: 12px;
}
.tr-role {
margin: 0;
font: 500 16px/1.45 var(--tr-font);
overflow-wrap: break-word;
}
.tr-focus {
margin: 2px 0 0;
color: var(--tr-soft);
font: 400 15px/1.5 var(--tr-font);
overflow-wrap: break-word;
}
.tr-acts {
grid-area: act;
display: flex;
flex-wrap: wrap;
align-content: start;
column-gap: 20px;
margin-top: 6px;
}
.tr-act {
display: inline-flex;
align-items: center;
min-height: 44px;
max-width: 100%;
color: var(--tr-ink);
font: 600 15px/1.3 var(--tr-font);
font-variant-numeric: tabular-nums;
text-decoration: underline;
text-decoration-thickness: 1px;
text-decoration-color: var(--tr-line);
text-underline-offset: 5px;
overflow-wrap: anywhere;
transition:
color 0.2s ease,
text-decoration-color 0.2s ease;
}
.tr-act:hover {
color: var(--tr-accent);
text-decoration-color: currentColor;
}
.tr-act:focus-visible,
.tr-cta:focus-visible {
outline: 3px solid var(--tr-accent);
outline-offset: 3px;
border-radius: 2px;
}
/* ---- the register's own width: aligned columns ---- */
@container trr (min-width: 640px) {
.tr-list {
--tr-plate: 100px;
--tr-name: 31px;
--tr-pad: 24px;
}
.tr--few .tr-list {
--tr-plate: clamp(132px, 22cqi, 184px);
--tr-name: clamp(40px, 6.6cqi, 60px);
--tr-pad: 32px;
}
.tr--many .tr-list {
--tr-plate: 58px;
--tr-name: 25px;
--tr-pad: 14px;
}
.tr-group-label {
padding: 18px 0 6px;
font-size: 28px;
}
/* mid / many: plate | name and credentials | role and focus | actions — one line of columns down the register. */
.tr-person {
grid-template-columns: var(--tr-plate) minmax(0, 11fr) minmax(0, 9fr) minmax(0, 6fr);
grid-template-areas: "plate who work act";
grid-template-rows: auto;
column-gap: 28px;
}
.tr--quiet .tr-person {
grid-template-columns: var(--tr-plate) minmax(0, 1fr) minmax(0, 1fr);
grid-template-areas: "plate who work";
}
.tr-work {
margin-top: 0;
padding-top: 6px;
}
/* The actions stack; their first line sits level with the name. */
.tr-acts {
flex-direction: column;
align-items: flex-start;
margin-top: -8px;
}
/* few: principals — the name large, role and focus under it, actions in their own column. */
.tr--few .tr-person {
grid-template-columns: var(--tr-plate) minmax(0, 1fr) minmax(0, 13rem);
grid-template-areas:
"plate who act"
"plate work act";
grid-template-rows: auto 1fr;
column-gap: 36px;
}
.tr--few.tr--quiet .tr-person {
grid-template-columns: var(--tr-plate) minmax(0, 1fr);
grid-template-areas:
"plate who"
"plate work";
}
/* The role and focus settle to the foot of the plate, level with its monogram or the photograph's edge. */
.tr--few .tr-work {
align-self: end;
margin-top: 18px;
padding-top: 0;
}
.tr--few .tr-role {
font-size: 18px;
}
.tr--few .tr-cred {
margin-top: 12px;
font-size: 14.5px;
}
.tr--few .tr-acts {
margin-top: -2px;
}
}
/* ---- section width ---- */
@container tr (min-width: 600px) {
.tr-inner {
gap: 56px;
padding: 88px 32px 96px;
}
}
/* Tablet: heading and intro split across the top of the register. */
@container tr (min-width: 700px) {
.tr-lead {
grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
column-gap: 48px;
align-items: end;
}
.tr-lead-body:only-child {
grid-column: 1 / -1;
}
}
@container tr (min-width: 960px) {
.tr-inner {
padding: 112px var(--tr-grid-gutter, 48px) 120px;
}
}
/* Wide: the lead holds its own column beside the register and stays in view while the register scrolls. */
@container tr (min-width: 1120px) {
.tr-inner {
grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
column-gap: 72px;
align-items: start;
}
.tr-lead {
position: sticky;
top: 32px;
grid-template-columns: minmax(0, 1fr);
gap: 24px;
}
.tr-title {
max-width: 11ch;
}
.tr-intro {
max-width: 34ch;
}
.tr-register {
gap: 64px;
}
/* No lead: the register takes the full width. */
.tr-register:only-child {
grid-column: 1 / -1;
}
}
@media (prefers-reduced-motion: reduce) {
.tr-cta,
.tr-act {
transition: none;
}
}
@media (forced-colors: active) {
.tr-group,
.tr-person {
border-color: CanvasText;
}
.tr-plate {
border: 1px solid CanvasText;
}
.tr-mono {
color: CanvasText;
}
.tr--tinted .tr-plate img {
mix-blend-mode: normal;
}
.tr-cta {
border: 2px solid ButtonText;
}
.tr-act:focus-visible,
.tr-cta:focus-visible {
outline-color: Highlight;
}
}
</style>
===== END src/components/TeamRegister.astro =====
===== FILE (supporting): src/components/TeamRegister.md =====
# Team register
A team as a ruled register, not a headshot grid: a lead column (heading, intro, one action) beside the people, grouped under the business's own labels. Each person is one aligned row — a small 4:5 plate (the portrait printed onto the group's tone, or a monogram from the name), the name in a display serif with the credentials as their own accent line, the role and focus, and the person's own profile, call and email links. Everything renders only when supplied.
## Integration
- A section heading (h2 by default); group labels are one level down and names one level below those (names are one level down when nothing is grouped).
- No people, no section. Designed for 2–12 people (ideal 4–6); the scale follows the count: 2–3 set large, 4–6 standard, 7–12 compact.
- People are grouped by their `group`, in the order groups first appear; people without a group follow in an unlabelled group. With no groups at all it is one list.
- Every name, role, credential, focus line, portrait and link renders only when supplied. A missing portrait becomes a monogram plate built from the name on the group's tone.
- Profile links go through safeHref; tel: is built from the digits of the supplied phone; mailto: only from a well-formed email.
- Container queries on the section's width: lead above the register below 1120px (heading and intro split from 700px), sticky lead column beside it from 1120px; rows become aligned columns from 640px of the register's own width.
- Reads --tr-bg, --tr-text, --tr-text-muted, --tr-brand, --tr-brand-text, --tr-border, --tr-family, --tr-display, --tr-grid-max, --tr-grid-gutter and --tr-heading-size.
- Loads Instrument Sans and Instrument Serif from Google Fonts by default. Pass fontStylesheet="" on a site that loads its own fonts.
## Provenance
Staged as candidate `team-register` (Step 28) and admitted through the candidate pipeline. Source type: original. References (direction only): https://superpower.com/, https://www.akercompanies.com/about, https://www.aspelinreitan.no/kontakt, https://www.clouarchitects.com/team/, https://www.basha-franklin.com/team. Quality history: quality/candidates/records/team-register.json.
===== END src/components/TeamRegister.md =====
===== FILE (required): public/team-register/portrait-1.webp =====
[binary asset — download https://astro.baysixmedia.com/team-register/portrait-1.webp and save it to public/team-register/portrait-1.webp]
===== FILE (required): public/team-register/portrait-2.webp =====
[binary asset — download https://astro.baysixmedia.com/team-register/portrait-2.webp and save it to public/team-register/portrait-2.webp]
===== FILE (required): public/team-register/portrait-3.webp =====
[binary asset — download https://astro.baysixmedia.com/team-register/portrait-3.webp and save it to public/team-register/portrait-3.webp]
===== FILE (required): public/team-register/portrait-4.webp =====
[binary asset — download https://astro.baysixmedia.com/team-register/portrait-4.webp and save it to public/team-register/portrait-4.webp]
--- 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.