uiception
blockbrands-section-v3source: ok · hit
Brands Section v3
Logo grid: muted icons, hover reveals brand color, inset border, flickering grid, and label.
npx shadcn add "https://uiception.com/r/brands-section-v3.json"free
Where it goes
logo-cloud
Interaction
scroll-reveal
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Files14 · source cached 10/9/2026, 4:42:51 AM
registry/new-york/blocks/brands-section-v3/page.tsx
import BrandsSectionV3 from "./components/brands-section-v3"
export default function Page() {
return <BrandsSectionV3 />
}
registry/new-york/blocks/brands-section-v3/components/brands-section-v3.tsx
import BrandGrid from "./brand-grid"
import "../styles/brands-section-v3.css"
export default function BrandsSectionV3() {
return (
<section className="mx-auto max-w-4xl px-4 py-4 md:py-6 lg:py-8">
<BrandGrid />
</section>
)
}
registry/new-york/blocks/brands-section-v3/components/brand-grid.tsx
"use client"
import { brands } from "../lib/brands"
import { BrandCell } from "./brand-cell"
export default function BrandGrid() {
return (
<div
className="grid grid-cols-2 border border-border sm:grid-cols-3 lg:grid-cols-6"
role="list"
aria-label="Technology partner logos"
>
{brands.map((brand) => (
<BrandCell key={brand.name} brand={brand} />
))}
</div>
)
}
registry/new-york/blocks/brands-section-v3/components/brand-cell.tsx
"use client"
import { useEffect, useState, type CSSProperties } from "react"
import { FlickeringGrid } from "./flickering-grid"
import type { Brand } from "../lib/brands"
export function BrandCell({ brand }: { brand: Brand }) {
const Icon = brand.Icon
const DarkIcon = brand.darkIcon ?? brand.Icon
const [isHovered, setIsHovered] = useState(false)
const [coarsePointer, setCoarsePointer] = useState(false)
useEffect(() => {
const mq = window.matchMedia("(hover: none)")
const update = () => setCoarsePointer(mq.matches)
update()
mq.addEventListener("change", update)
return () => mq.removeEventListener("change", update)
}, [])
const gridActive = isHovered || coarsePointer
return (
<div
aria-label={brand.name}
className="bs3-cell group relative isolate z-0 -mt-px -ml-px flex cursor-default items-center justify-center overflow-hidden border border-border bg-background py-8 outline-none hover:z-10 focus-visible:z-10"
role="listitem"
style={{ "--bs3-brand-color": brand.color } as CSSProperties}
tabIndex={0}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
onFocus={() => setIsHovered(true)}
onBlur={() => setIsHovered(false)}
>
<div className="bs3-flicker-layer" aria-hidden>
<FlickeringGrid
active={gridActive}
className="h-full w-full mask-[radial-gradient(ellipse_75%_70%_at_50%_50%,white,transparent)]"
color={brand.color}
/>
</div>
<div className="relative z-10 flex w-full flex-col items-center">
<div className="bs3-logo-stack relative aspect-square w-10 max-w-full translate-y-4 transition-transform delay-130 duration-480 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:translate-y-0 group-hover:delay-0 group-focus-visible:translate-y-0 group-focus-visible:delay-0 md:w-12">
<span className="bs3-icon-default">
<Icon aria-hidden className="h-full w-full" />
</span>
<span className="bs3-icon-hover">
<Icon aria-hidden className="h-full w-full dark:hidden" />
<DarkIcon aria-hidden className="hidden h-full w-full dark:block" />
</span>
</div>
<span className="bs3-name-label mt-2 text-sm font-medium tracking-wide text-foreground">
{brand.name}
</span>
</div>
</div>
)
}
registry/new-york/blocks/brands-section-v3/components/flickering-grid.tsx
"use client"
import { cn } from "@/lib/utils"
import { useEffect, useRef } from "react"
export type FlickeringGridProps = {
className?: string
squareSize?: number
gridGap?: number
color?: string
maxOpacity?: number
flickerChance?: number
active?: boolean
flickerFrameSkip?: number
opacityLerp?: number
}
function parseRgb(hex: string): [number, number, number] {
const h = hex.replace("#", "")
const full =
h.length === 3
? h
.split("")
.map((c) => c + c)
.join("")
: h
if (full.length !== 6) {
return [96, 165, 250]
}
return [
Number.parseInt(full.slice(0, 2), 16),
Number.parseInt(full.slice(2, 4), 16),
Number.parseInt(full.slice(4, 6), 16),
]
}
type Dims = { w: number; h: number; cols: number; rows: number }
export function FlickeringGrid({
className,
squareSize = 4,
gridGap = 6,
color = "#60A5FA",
maxOpacity = 0.45,
flickerChance = 0.03,
active = true,
flickerFrameSkip = 10,
opacityLerp = 0.05,
}: FlickeringGridProps) {
const containerRef = useRef<HTMLDivElement>(null)
const canvasRef = useRef<HTMLCanvasElement>(null)
const currentRef = useRef<number[]>([])
const targetRef = useRef<number[]>([])
const dimsRef = useRef<Dims>({ w: 1, h: 1, cols: 1, rows: 1 })
const rafRef = useRef(0)
const frameRef = useRef(0)
useEffect(() => {
const container = containerRef.current
const canvas = canvasRef.current
if (!container || !canvas) {
return
}
const ctx = canvas.getContext("2d")
if (!ctx) {
return
}
const [r, g, b] = parseRgb(color)
const cell = squareSize + gridGap
const syncDimensions = () => {
const rect = container.getBoundingClientRect()
const w = Math.max(1, Math.floor(rect.width))
const h = Math.max(1, Math.floor(rect.height))
const dpr = window.devicePixelRatio || 1
canvas.width = w * dpr
canvas.height = h * dpr
canvas.style.width = `${w}px`
canvas.style.height = `${h}px`
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
const cols = Math.max(1, Math.ceil(w / cell))
const rows = Math.max(1, Math.ceil(h / cell))
const len = cols * rows
const prevC = currentRef.current
const prevT = targetRef.current
const nextC = new Array<number>(len)
const nextT = new Array<number>(len)
for (let i = 0; i < len; i++) {
const c = prevC[i] ?? Math.random() * maxOpacity * 0.4
const t = prevT[i] ?? c
nextC[i] = c
nextT[i] = t
}
currentRef.current = nextC
targetRef.current = nextT
dimsRef.current = { w, h, cols, rows }
}
const tick = () => {
if (!active) {
return
}
const { w, h, cols, rows } = dimsRef.current
const current = currentRef.current
const target = targetRef.current
frameRef.current += 1
if (frameRef.current % flickerFrameSkip === 0) {
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const idx = row * cols + col
if (idx >= target.length) {
continue
}
if (Math.random() < flickerChance) {
target[idx] = Math.random() * maxOpacity
}
}
}
}
ctx.clearRect(0, 0, w, h)
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const idx = row * cols + col
if (idx >= current.length) {
continue
}
const t = target[idx]
const c = current[idx]
current[idx] = c + (t - c) * opacityLerp
const o = current[idx]
const x = col * cell + gridGap / 2
const y = row * cell + gridGap / 2
ctx.fillStyle = `rgba(${r},${g},${b},${o})`
ctx.fillRect(x, y, squareSize, squareSize)
}
}
rafRef.current = requestAnimationFrame(tick)
}
const ro = new ResizeObserver(() => {
syncDimensions()
})
ro.observe(container)
syncDimensions()
if (active) {
rafRef.current = requestAnimationFrame(tick)
}
return () => {
ro.disconnect()
cancelAnimationFrame(rafRef.current)
}
}, [
active,
color,
squareSize,
gridGap,
maxOpacity,
flickerChance,
flickerFrameSkip,
opacityLerp,
])
return (
<div
ref={containerRef}
className={cn("relative h-full min-h-px w-full min-w-px", className)}
>
<canvas
ref={canvasRef}
className="pointer-events-none absolute inset-0 z-0 block h-full w-full"
aria-hidden
/>
</div>
)
}
registry/new-york/blocks/brands-section-v3/lib/brands.ts
import type { ComponentType, SVGProps } from "react"
import { Supabase } from "@/components/ui/svgs/supabase"
import { Convex } from "@/components/ui/svgs/convex"
import { Vercel } from "@/components/ui/svgs/vercel"
import { VercelDark } from "@/components/ui/svgs/vercelDark"
import { Neon } from "@/components/ui/svgs/neon"
import { Upstash } from "@/components/ui/svgs/upstash"
import { TursoLight } from "@/components/ui/svgs/tursoLight"
export type SvgComponent = ComponentType<SVGProps<SVGSVGElement>>
export type Brand = {
name: string
Icon: SvgComponent
darkIcon?: SvgComponent
color: string
}
export const brands: Brand[] = [
{ name: "Supabase", Icon: Supabase, color: "#3ECF8E" },
{ name: "Convex", Icon: Convex, color: "#F3B01C" },
{ name: "Vercel", Icon: Vercel, darkIcon: VercelDark, color: "#888888" },
{ name: "Neon", Icon: Neon, color: "#00E0D9" },
{ name: "Upstash", Icon: Upstash, color: "#00C98D" },
{ name: "Turso", Icon: TursoLight, color: "#3CD5A1" },
]
registry/new-york/blocks/brands-section-v3/styles/brands-section-v3.css
.bs3-cell {
--bs3-ease: cubic-bezier(0.22, 1, 0.36, 1);
}
.bs3-cell::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(to top, var(--background) 0%, transparent 84%);
pointer-events: none;
z-index: 1;
}
.bs3-cell::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
z-index: 4;
box-shadow: inset 0 0 0 1px transparent;
transition: box-shadow 300ms 150ms ease;
}
.bs3-cell:hover::after,
.bs3-cell:focus-visible::after {
box-shadow: inset 0 0 0 1px var(--bs3-brand-color);
transition: box-shadow 300ms 0ms ease;
}
.bs3-flicker-layer {
position: absolute;
inset: 0;
z-index: 2;
overflow: hidden;
pointer-events: none;
opacity: 0;
transition: opacity 300ms ease-out;
}
.bs3-cell:hover .bs3-flicker-layer,
.bs3-cell:focus-visible .bs3-flicker-layer {
opacity: 1;
}
.bs3-icon-default,
.bs3-icon-hover {
position: absolute;
inset: 0;
display: flex;
}
.bs3-icon-default {
opacity: 1;
filter: grayscale(1) opacity(0.35) blur(0);
transition:
opacity 520ms var(--bs3-ease) 120ms,
filter 520ms var(--bs3-ease) 120ms;
}
.dark .bs3-icon-default {
filter: grayscale(1) invert(1) opacity(0.22) blur(0);
}
.bs3-cell:hover .bs3-icon-default,
.bs3-cell:focus-visible .bs3-icon-default {
opacity: 0;
pointer-events: none;
filter: grayscale(1) opacity(0) blur(5px);
transition-delay: 0ms, 0ms;
}
.dark .bs3-cell:hover .bs3-icon-default,
.dark .bs3-cell:focus-visible .bs3-icon-default {
filter: grayscale(1) invert(1) opacity(0) blur(5px);
}
.bs3-icon-hover {
opacity: 0;
filter: blur(6px);
transition:
opacity 540ms var(--bs3-ease) 45ms,
filter 580ms var(--bs3-ease) 45ms;
}
.bs3-cell:hover .bs3-icon-hover,
.bs3-cell:focus-visible .bs3-icon-hover {
opacity: 1;
filter: blur(0);
transition-delay: 70ms, 70ms;
}
.bs3-name-label {
opacity: 0;
filter: blur(5px);
transform: translateY(0.375rem);
transition:
opacity 500ms var(--bs3-ease) 100ms,
filter 500ms var(--bs3-ease) 100ms,
transform 500ms var(--bs3-ease) 100ms;
}
.bs3-cell:hover .bs3-name-label,
.bs3-cell:focus-visible .bs3-name-label {
opacity: 1;
filter: none;
transform: translateY(0);
transition-delay: 95ms, 95ms, 95ms;
}
@media (prefers-reduced-motion: reduce) {
.bs3-logo-stack {
transition-duration: 0.01ms;
transition-delay: 0ms !important;
}
.bs3-icon-default,
.bs3-icon-hover {
transition-duration: 0.01ms !important;
transition-delay: 0ms !important;
}
.bs3-icon-default {
filter: grayscale(1) opacity(0.35) blur(0) !important;
}
.dark .bs3-icon-default {
filter: grayscale(1) invert(1) opacity(0.22) blur(0) !important;
}
.bs3-cell:hover .bs3-icon-default,
.bs3-cell:focus-visible .bs3-icon-default {
filter: grayscale(1) opacity(0) blur(0) !important;
}
.dark .bs3-cell:hover .bs3-icon-default,
.dark .bs3-cell:focus-visible .bs3-icon-default {
filter: grayscale(1) invert(1) opacity(0) blur(0) !important;
}
.bs3-icon-hover {
filter: blur(0) !important;
}
.bs3-cell:hover .bs3-icon-hover,
.bs3-cell:focus-visible .bs3-icon-hover {
filter: blur(0) !important;
}
.bs3-name-label {
transition-duration: 0.01ms !important;
transition-delay: 0ms !important;
filter: blur(0) !important;
transform: none !important;
}
.bs3-cell:hover .bs3-icon-default,
.bs3-cell:focus-visible .bs3-icon-default,
.bs3-cell:hover .bs3-icon-hover,
.bs3-cell:focus-visible .bs3-icon-hover,
.bs3-cell:hover .bs3-name-label,
.bs3-cell:focus-visible .bs3-name-label {
transition-delay: 0ms !important;
}
}
.bs3-cell:nth-child(2n) {
border-right: none;
}
.bs3-cell:nth-last-child(-n + 2) {
border-bottom: none;
}
@media (min-width: 640px) {
.bs3-cell:nth-child(2n) {
border-right: 1px solid var(--border);
}
.bs3-cell:nth-child(3n) {
border-right: none;
}
.bs3-cell:nth-last-child(-n + 2) {
border-bottom: 1px solid var(--border);
}
.bs3-cell:nth-last-child(-n + 3) {
border-bottom: none;
}
}
@media (min-width: 1024px) {
.bs3-cell {
border-right: 1px solid var(--border);
border-bottom: none;
}
.bs3-cell:nth-child(3n) {
border-right: 1px solid var(--border);
}
.bs3-cell:last-child {
border-right: none;
}
}
@media (hover: none) {
.bs3-cell::after {
box-shadow: inset 0 0 0 1px var(--bs3-brand-color);
transition: none;
}
.bs3-cell .bs3-flicker-layer {
opacity: 1;
transition: none;
}
.bs3-cell .bs3-logo-stack {
transform: translateY(0);
transition: none;
}
.bs3-cell .bs3-icon-default {
opacity: 0;
filter: grayscale(1) opacity(0);
transition: none;
}
.dark .bs3-cell .bs3-icon-default {
filter: grayscale(1) invert(1) opacity(0);
}
.bs3-cell .bs3-icon-hover {
opacity: 1;
filter: none;
transition: none;
}
.bs3-cell .bs3-name-label {
opacity: 1;
filter: none;
transform: none;
transition: none;
}
}
components/ui/svgs/supabase.tsx
import type { SVGProps } from "react";
const Supabase = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} viewBox="0 0 109 113" fill="none">
<path
d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0627L99.1935 40.0627C107.384 40.0627 111.952 49.5228 106.859 55.9374L63.7076 110.284Z"
fill="url(#paint0_linear)"
/>
<path
d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0627L99.1935 40.0627C107.384 40.0627 111.952 49.5228 106.859 55.9374L63.7076 110.284Z"
fill="url(#paint1_linear)"
fillOpacity="0.2"
/>
<path
d="M45.317 2.07103C48.1765 -1.53037 53.9745 0.442937 54.0434 5.041L54.4849 72.2922H9.83113C1.64038 72.2922 -2.92775 62.8321 2.1655 56.4175L45.317 2.07103Z"
fill="#3ECF8E"
/>
<defs>
<linearGradient
id="paint0_linear"
x1="53.9738"
y1="54.974"
x2="94.1635"
y2="71.8295"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#249361" />
<stop offset="1" stopColor="#3ECF8E" />
</linearGradient>
<linearGradient
id="paint1_linear"
x1="36.1558"
y1="30.578"
x2="54.4844"
y2="65.0806"
gradientUnits="userSpaceOnUse"
>
<stop />
<stop offset="1" stopOpacity="0" />
</linearGradient>
</defs>
</svg>
);
export { Supabase };
components/ui/svgs/convex.tsx
import type { SVGProps } from "react";
const Convex = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} viewBox="28 28 128 132" fill="none">
<path
fill="#F3B01C"
d="M108.092 130.021c18.166-2.018 35.293-11.698 44.723-27.854-4.466 39.961-48.162 65.218-83.83 49.711-3.286-1.425-6.115-3.796-8.056-6.844-8.016-12.586-10.65-28.601-6.865-43.135 10.817 18.668 32.81 30.111 54.028 28.122Z"
/>
<path
fill="#8D2676"
d="M53.401 90.174c-7.364 17.017-7.682 36.94 1.345 53.336-31.77-23.902-31.423-75.052-.388-98.715 2.87-2.187 6.282-3.485 9.86-3.683 14.713-.776 29.662 4.91 40.146 15.507-21.3.212-42.046 13.857-50.963 33.555Z"
/>
<path
fill="#EE342F"
d="M114.637 61.855C103.89 46.87 87.069 36.668 68.639 36.358c35.625-16.17 79.446 10.047 84.217 48.807.444 3.598-.139 7.267-1.734 10.512-6.656 13.518-18.998 24.002-33.42 27.882 10.567-19.599 9.263-43.544-3.065-61.704Z"
/>
</svg>
);
export { Convex };
components/ui/svgs/vercel.tsx
import type { SVGProps } from "react";
const Vercel = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} viewBox="0 0 256 222" preserveAspectRatio="xMidYMid">
<path d="m128 0 128 221.705H0z" />
</svg>
);
export { Vercel };
components/ui/svgs/vercelDark.tsx
import type { SVGProps } from "react";
const VercelDark = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} viewBox="0 0 256 222" preserveAspectRatio="xMidYMid">
<path fill="#fff" d="m128 0 128 221.705H0z" />
</svg>
);
export { VercelDark };
components/ui/svgs/neon.tsx
import type { SVGProps } from "react";
const Neon = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} viewBox="0 0 64 64" fill="none">
<title>Neon</title>
<path
d="M63 0.0177909V63.5526L38.4178 42.2501V63.5526H0V0L63 0.0177909ZM7.72251 55.8389H30.6953V25.3238L55.2779 47.0476V7.72922L7.72251 7.71559V55.8389Z"
fill="#37C38F"
/>
</svg>
);
export { Neon };
components/ui/svgs/upstash.tsx
import type { SVGProps } from "react";
const Upstash = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} viewBox="0 0 256 341" preserveAspectRatio="xMidYMid">
<path
fill="#00C98D"
d="M0 298.417c56.554 56.553 148.247 56.553 204.801 0 56.554-56.554 56.554-148.247 0-204.801l-25.6 25.6c42.415 42.416 42.415 111.185 0 153.6-42.416 42.416-111.185 42.416-153.601 0L0 298.416Z"
/>
<path
fill="#00C98D"
d="M51.2 247.216c28.277 28.277 74.123 28.277 102.4 0 28.277-28.276 28.277-74.123 0-102.4l-25.6 25.6c14.14 14.138 14.14 37.061 0 51.2-14.138 14.139-37.061 14.139-51.2 0l-25.6 25.6ZM256 42.415c-56.554-56.553-148.247-56.553-204.8 0-56.555 56.555-56.555 148.247 0 204.801l25.599-25.6c-42.415-42.415-42.415-111.185 0-153.6 42.416-42.416 111.185-42.416 153.6 0L256 42.416Z"
/>
<path
fill="#00C98D"
d="M204.8 93.616c-28.276-28.277-74.124-28.277-102.4 0-28.278 28.277-28.278 74.123 0 102.4l25.6-25.6c-14.14-14.138-14.14-37.061 0-51.2 14.138-14.139 37.06-14.139 51.2 0l25.6-25.6Z"
/>
<path
fill="#FFF"
fillOpacity=".4"
d="M256 42.415c-56.554-56.553-148.247-56.553-204.8 0-56.555 56.555-56.555 148.247 0 204.801l25.599-25.6c-42.415-42.415-42.415-111.185 0-153.6 42.416-42.416 111.185-42.416 153.6 0L256 42.416Z"
/>
<path
fill="#FFF"
fillOpacity=".4"
d="M204.8 93.616c-28.276-28.277-74.124-28.277-102.4 0-28.278 28.277-28.278 74.123 0 102.4l25.6-25.6c-14.14-14.138-14.14-37.061 0-51.2 14.138-14.139 37.06-14.139 51.2 0l25.6-25.6Z"
/>
</svg>
);
export { Upstash };
components/ui/svgs/tursoLight.tsx
import type { SVGProps } from "react";
const TursoLight = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} fill="none" viewBox="0 0 201 170">
<path
d="m200.035 48.61c-4.67-27.94-29.16-48.61-29.16-48.61v30.78l-14.54 3.75-9.11-10.97-4.81 9.46c-9.92-2.7-23.58-4.44-42.37-4.44-18.7901 0-32.4501 1.75-42.3701 4.44l-4.81-9.46-9.11 10.97-14.54-3.75v-30.78s-24.48997 20.67-29.1599684 48.61l32.1399684 11.12c1.05 19.43 9.79 71.88 12.29 76.64 2.66 5.07 16.78 19.56 27.83 25.13 0 0 4-4.23 6.44-7.96 3.1 3.65 19.11 16.45 21.3001 16.45 2.19 0 18.2-12.79 21.3-16.45 2.44 3.73 6.44 7.96 6.44 7.96 11.05-5.57 25.17-20.06 27.83-25.13 2.5-4.76 11.24-57.21 12.29-76.64l32.14-11.12zm-46.19 44.74-21.75 1.94 1.91 26.67s-13.23 10.95-33.96 10.95c-20.7301 0-33.9601-10.95-33.9601-10.95l1.91-26.67-21.75-1.94-3.72-30.04 36.05 12.48-2.8 37.39c6.7 1.7 13.75 3.39 24.2801 3.39 10.53 0 17.57-1.69 24.27-3.39l-2.8-37.39 36.05-12.48-3.72 30.04z"
fill="#183134"
/>
</svg>
);
export { TursoLight };