Rooster van logotegels met een dragende lijn: twee kolommen (vier vanaf 48rem), elke tegel omlijnd, om en om een gedempt vlak, plusjes op de kruispunten; boven en onder een lijn over de volle vensterbreedte. Statisch.
primitives/proof/logo-lattice.tsx
/**
* LogoLattice — een rooster van logotegels met een dragende lijn: twee
* kolommen (vier vanaf 48rem), elke tegel omlijnd, om en om een gedempt
* vlak, en op de kruispunten kleine plusjes. Boven en onder loopt een lijn
* over de volle breedte van het venster, buiten het rooster om.
*
* Geoogst uit 21st.dev Logo Cloud 2 (efferd): acht vaste CDN-logo's van
* techmerken → `logos` van het project (src+alt of een node), lucide
* PlusIcon → inline plus, de per-tegel Tailwind-klassen (border-r/-b,
* md:bg-secondary, dark:brightness-0 dark:invert) → één patroon uit
* nth-child en een `invert`-prop, w-screen-lijnen → 100vw met translate.
*/
import type * as React from "react";
export interface LatticeLogo {
/** Beeld-URL óf een eigen node (SVG). */
src?: string;
node?: React.ReactNode;
alt: string;
}
export interface LogoLatticeProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
logos: readonly LatticeLogo[];
/** Beelden omkeren naar de tekstkleur (voor donkere logo's op een donker vlak). */
invert?: boolean;
/** De lijnen boven en onder over de volle vensterbreedte. Default aan. */
fullBleedLines?: boolean;
}
const Plus = ({ hoek }: { hoek: "rb" | "lb" }) => (
<svg className={`fp-lattice__plus fp-lattice__plus--${hoek}`} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1} strokeLinecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
);
export function LogoLattice({ logos, invert = false, fullBleedLines = true, className, ...rest }: LogoLatticeProps) {
return (
<div {...rest} className={["fp-lattice", invert ? "fp-lattice--invert" : "", fullBleedLines ? "fp-lattice--lijnen" : "", className].filter(Boolean).join(" ")}>
{logos.map((logo, i) => (
<div key={`${logo.alt}-${i}`} className="fp-lattice__tegel">
{logo.node ?? (
// eslint-disable-next-line @next/next/no-img-element -- logo's van het project
<img src={logo.src} alt={logo.alt} className="fp-lattice__beeld" loading="lazy" />
)}
{/* Plusjes op de kruispunten: rechtsonder op de eerste tegel van elke rij, plus linksonder in het midden. */}
{i % 2 === 0 && i < logos.length - 2 && <Plus hoek="rb" />}
{i % 4 === 2 && <Plus hoek="lb" />}
</div>
))}
</div>
);
}
primitives/proof/proof.css
/**
* Bewijs II — importeren in de app-root (registry-site: app/globals.css).
* Token-contract 2.
*/
/* ── logo-lattice ───────────────────────────────────────────────────────── */
.fp-lattice { position: relative; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); width: 100%; border-left: var(--border-width) solid var(--border); border-right: var(--border-width) solid var(--border); color: var(--foreground); }
@media (min-width: 48rem) { .fp-lattice { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
/* De lijnen boven en onder, over de volle vensterbreedte, buiten het rooster. */
.fp-lattice--lijnen::before, .fp-lattice--lijnen::after { content: ""; position: absolute; left: 50%; width: 100vw; height: var(--border-width); background: var(--border); transform: translateX(-50%); pointer-events: none; }
.fp-lattice--lijnen::before { top: calc(-1 * var(--border-width)); }
.fp-lattice--lijnen::after { bottom: calc(-1 * var(--border-width)); }
.fp-lattice__tegel { position: relative; display: flex; align-items: center; justify-content: center; padding: var(--space-xl) var(--space-md); background: var(--background); border-bottom: var(--border-width) solid var(--border); }
@media (min-width: 48rem) { .fp-lattice__tegel { padding: var(--space-xl); } }
/* Twee kolommen: elke oneven tegel krijgt een rechterlijn; de laatste rij geen onderlijn. */
.fp-lattice__tegel:nth-child(odd) { border-right: var(--border-width) solid var(--border); }
.fp-lattice__tegel:nth-last-child(-n + 2) { border-bottom: 0; }
/* Dambord van gedempte vlakken: smal 1,4,5,8; breed 1,3,6,8 (om en om per rij). */
.fp-lattice__tegel:nth-child(4n + 1), .fp-lattice__tegel:nth-child(4n) { background: var(--muted); }
@media (min-width: 48rem) {
.fp-lattice__tegel:nth-child(odd) { border-right: 0; }
.fp-lattice__tegel:not(:nth-child(4n)) { border-right: var(--border-width) solid var(--border); }
.fp-lattice__tegel:nth-last-child(-n + 4) { border-bottom: 0; }
.fp-lattice__tegel:nth-child(4n + 1), .fp-lattice__tegel:nth-child(4n) { background: var(--background); }
.fp-lattice__tegel:nth-child(8n + 1), .fp-lattice__tegel:nth-child(8n + 3), .fp-lattice__tegel:nth-child(8n + 6), .fp-lattice__tegel:nth-child(8n) { background: var(--muted); }
}
.fp-lattice__beeld { height: 1rem; width: auto; pointer-events: none; user-select: none; }
@media (min-width: 48rem) { .fp-lattice__beeld { height: 1.25rem; } }
.fp-lattice--invert .fp-lattice__beeld { filter: brightness(0) invert(1); }
.fp-lattice__tegel > svg:not(.fp-lattice__plus) { height: 1.25rem; width: auto; }
.fp-lattice__plus { position: absolute; z-index: 1; width: 1.5rem; height: 1.5rem; }
.fp-lattice__plus--rb { right: -0.75rem; bottom: -0.75rem; }
.fp-lattice__plus--lb { left: -0.75rem; bottom: -0.75rem; display: none; }
@media (min-width: 48rem) { .fp-lattice__plus--lb { display: block; } }