Skip to content
← Templates

Folio 01.

A complete canvas for your work. Made from the collection.

Get the template

LIVE PREVIEW / FICTIONAL DESIGNER, PROJECTS, PRICES & TESTIMONIALS

am.

Available for select projects

Thoughtful design. Unexpected details.

I’m Alex, an independent designer and developer turning ambitious ideas into digital experiences people love.

Selected work

A few things I’ve made.

How I work

A considered path from first sketch to launch.

01 / A clear point of view

Know what makes you, you.

A considered identity gives every detail a place to belong.

02 / Made with intention

The little things add up.

From the first hello to the final hover, every moment feels considered.

03 / Ready to go further

Beautiful, then usable.

A smooth handoff keeps the good work moving into the real world.

04 / Built around real people

A thoughtful way to move forward.

Make a useful promise clear, then give people a confident next step.

Ways to work together

A good fit for your next chapter.

Billing period

Design partner

For a considered visual direction.

$1,200/ month

Monthly design retainer

  • Brand & interface design
  • One request at a time
  • Direct collaboration
Let’s talk

Design + development

Popular

From the first idea to a live site.

$2,400/ month

Monthly design & development retainer

  • Everything in design
  • React & Next.js development
  • Launch support
Let’s talk

Kind words

Good work. Better company.

The rare kind of collaborator who makes the idea better, then makes it happen.

Jamie Chen

Founder, Forma · Example testimonial

1 / 3

Something in mind?

Let’s make it happen.

hello@example.com
Alex MorganMade with care. Built with Gear5.

Your turn.

Take the whole template.

npx shadcn@latest add https://gear5-ui.vercel.app/r/portfolio-template.json

Installs the page component, five blocks, and safe-link helper. Requires React and Tailwind CSS 4. Replace all example content before publishing; the service links open your configured email address.

import { PortfolioTemplate } from "@/components/gear5/portfolio-template";
export default function Portfolio() {
  return <PortfolioTemplate name="Your name" initials="yn."
    email="you@example.com" accent="#d9fc87"
    title={"Thoughtful design.\nUnexpected details."}
    description="Describe the work you do and the people you help."
    projects={[{
      id: "studio", title: "Studio", category: "Brand & web design",
      description: "The story behind the project.", href: "/work/studio",
      symbol: "s.", color: "#d5e6d3",
    }]}
    testimonials={[]}
    services={[]}
  />;
}

Template source → components/gear5/portfolio-template.tsx
"use client";

import { useId, type CSSProperties } from "react";
import { OrbitHero } from "@/components/gear5/orbit-hero";
import { ProjectGallery, type GalleryProject } from "@/components/gear5/project-gallery";
import { SpotlightBento, type BentoFeature } from "@/components/gear5/spotlight-bento";
import { PricingSwitch, type PricingPlan } from "@/components/gear5/pricing-switch";
import {
  TestimonialDeck,
  deckTestimonials,
  type DeckTestimonial,
} from "@/components/gear5/testimonial-deck";
import { sanitizeHref } from "@/lib/gear5/sanitize";

export interface PortfolioTemplateProps {
  name?: string;
  initials?: string;
  accent?: string;
  email?: string;
  title?: string;
  description?: string;
  availability?: string;
  projects?: GalleryProject[];
  process?: BentoFeature[];
  testimonials?: DeckTestimonial[];
  projectLinkLabel?: string;
  services?: PricingPlan[];
  labels?: {
    work?: string;
    process?: string;
    services?: string;
    contact?: string;
    contactEyebrow?: string;
    contactTitle?: string;
    footer?: string;
  };
}
const servicesDefault: PricingPlan[] = [
  {
    id: "design",
    name: "Design partner",
    description: "For a considered visual direction.",
    monthlyPrice: "$1,200",
    yearlyPrice: "$1,000",
    monthlyNote: "Monthly design retainer",
    yearlyNote: "$12,000 billed yearly",
    features: [
      "Brand & interface design",
      "One request at a time",
      "Direct collaboration",
    ],
    href: "#contact",
  },
  {
    id: "full",
    name: "Design + development",
    description: "From the first idea to a live site.",
    monthlyPrice: "$2,400",
    yearlyPrice: "$2,000",
    monthlyNote: "Monthly design & development retainer",
    yearlyNote: "$24,000 billed yearly",
    features: [
      "Everything in design",
      "React & Next.js development",
      "Launch support",
    ],
    href: "#contact",
    featured: true,
  },
];

const projectsDefault: GalleryProject[] = [
  {
    id: "forma",
    title: "Forma",
    category: "Identity",
    description:
      "A new visual language for an architecture studio, made to grow.",
    artworkAlt: "Abstract pale sage identity artwork marked f.",
    artwork: (
      <div className="flex aspect-[5/4] items-center justify-center bg-[#d5e6d3] font-serif text-8xl text-[#283329]">
        f.
      </div>
    ),
    href: "#contact",
    linkLabel: "Discuss a similar project",
  },
  {
    id: "mono",
    title: "Mono",
    category: "Product",
    description: "Turning a busy creative workflow into a calmer workspace.",
    artworkAlt: "Abstract lavender product artwork marked m.",
    artwork: (
      <div className="flex aspect-[5/4] items-center justify-center bg-[#c9c4e4] font-mono text-7xl text-[#302b48]">
        m↗
      </div>
    ),
    href: "#contact",
    linkLabel: "Discuss a similar project",
  },
  {
    id: "terrain",
    title: "Terrain",
    category: "Digital",
    description: "An independent publication for the places in between.",
    artworkAlt: "Warm sand-colored publication artwork marked t.",
    artwork: (
      <div className="flex aspect-[5/4] items-center justify-center bg-[#ead3b9] font-serif text-7xl italic text-[#49362b]">
        t*
      </div>
    ),
    href: "#contact",
    linkLabel: "Discuss a similar project",
  },
];

/** Complete sample portfolio with an editable gallery and process grid. Replace sample content before publishing. */
export function PortfolioTemplate({
  name = "Alex Morgan",
  initials = "am.",
  accent = "#d9fc87",
  email = "hello@example.com",
  title = "Thoughtful design.\nUnexpected details.",
  description = "I’m Alex, an independent designer and developer turning ambitious ideas into digital experiences people love.",
  availability = "Available for select projects",
  projects = projectsDefault,
  process: processFeatures,
  testimonials = deckTestimonials,
  projectLinkLabel = "Discuss a similar project",
  services = servicesDefault,
  labels = {},
}: PortfolioTemplateProps) {
  const id = useId().replace(/:/g, "");
  const work = `${id}-work`,
    processId = `${id}-process`,
    pricing = `${id}-services`,
    contact = `${id}-contact`;
  const emailHref = sanitizeHref(`mailto:${email}`);
  return (
    <div
      style={{ "--g5-accent": accent } as CSSProperties}
      className="bg-[#0b0c0e] p-4 text-[#f4f4ef] sm:p-8"
    >
      <div className="mx-auto max-w-6xl">
        <header className="mb-6 flex flex-wrap items-center justify-between gap-4 px-3 py-3">
          <a
            href={`#${id}-home`}
            aria-label={name}
            className="font-serif text-3xl font-bold tracking-[-.06em]"
          >
            {initials}
          </a>
          <nav
            aria-label={name}
            className="flex flex-wrap gap-5 text-xs text-[#c2c4bd]"
          >
            <a href={`#${work}`}>{labels.work ?? "Work"}</a>
            <a href={`#${processId}`}>{labels.process ?? "Process"}</a>
            <a href={`#${pricing}`}>{labels.services ?? "Services"}</a>
            <a href={`#${contact}`} className="text-[var(--g5-accent)]">
              {labels.contact ?? "Let’s talk"} ↗
            </a>
          </nav>
        </header>
        <div id={`${id}-home`}>
          <OrbitHero
            headingLevel="h1"
            eyebrow={availability}
            title={title}
            description={description}
            mark={initials}
            accent={accent}
            action={{ label: labels.work ?? "Selected work", href: `#${work}` }}
            secondaryAction={{
              label: labels.contact ?? "Let’s talk",
              href: `#${contact}`,
            }}
          />
        </div>
        <div id={work} className="mt-6 scroll-mt-24">
          <ProjectGallery
            accent={accent}
            title="A few things I’ve made."
            eyebrow={labels.work ?? "Selected work"}
            projects={projects.map((project) => ({
              ...project,
              linkLabel: project.linkLabel ?? projectLinkLabel,
              href: project.href === "#contact" ? emailHref : project.href,
            }))}
          />
        </div>
        <div id={processId} className="mt-6 scroll-mt-24">
          <SpotlightBento
            accent={accent}
            eyebrow={labels.process ?? "How I work"}
            title="A considered path from first sketch to launch."
            features={processFeatures}
          />
        </div>
        <div id={pricing} className="mt-6 scroll-mt-24">
          <PricingSwitch
            title="A good fit for your next chapter."
            eyebrow={labels.services ?? "Ways to work together"}
            accent={accent}
            plans={services.map((plan) => ({
              ...plan,
              href: plan.href === "#contact" ? emailHref : plan.href,
            }))}
            actionLabel={labels.contact ?? "Let’s talk"}
          />
        </div>
        <div className="mt-6">
          <TestimonialDeck testimonials={testimonials} accent={accent} />
        </div>
        <section
          id={contact}
          className="mt-6 scroll-mt-24 rounded-3xl bg-[var(--g5-accent)] p-8 text-[#111214] sm:p-12"
        >
          <p className="font-mono text-xs uppercase">
            {labels.contactEyebrow ?? "Something in mind?"}
          </p>
          <h2 className="mt-4 text-4xl font-medium tracking-[-.06em] sm:text-6xl">
            {labels.contactTitle ?? "Let’s make it happen."}
          </h2>
          <a
            href={emailHref}
            className="mt-8 inline-flex min-h-11 items-center gap-8 rounded-full border border-black/30 px-6 py-3 text-sm font-semibold"
          >
            {email}
            <span aria-hidden="true">↗</span>
          </a>
        </section>
        <footer className="flex flex-wrap justify-between gap-3 px-3 pt-8 text-xs text-[#b4b6af]">
          <span>{name}</span>
          <span>{labels.footer ?? "Made with care. Built with Gear5."}</span>
        </footer>
      </div>
    </div>
  );
}

Orbit Hero → components/gear5/orbit-hero.tsx
"use client";

import { useRef, type CSSProperties } from "react";
import { sanitizeHref } from "@/lib/gear5/sanitize";

export interface OrbitHeroProps {
  eyebrow?: string;
  title?: string;
  description?: string;
  action?: { label: string; href: string };
  secondaryAction?: { label: string; href: string };
  accent?: string;
  headingLevel?: "h1" | "h2";
  artworkLabels?: [string, string, string];
  mark?: string;
  className?: string;
}

/** Pointer-responsive artwork is decorative; content and links work without motion. */
export function OrbitHero({
  eyebrow = "Independent designer & developer",
  title = "Ideas into\nsomething real.",
  description = "Thoughtful digital experiences. A little unexpected. Always made for people.",
  action = { label: "Explore my work", href: "#work" },
  secondaryAction = { label: "Let’s talk", href: "#contact" },
  accent = "#d9fc87",
  headingLevel: Heading = "h2",
  artworkLabels = ["Design", "Build", "Make it move"],
  mark = "G5",
  className = "",
}: OrbitHeroProps) {
  const art = useRef<HTMLDivElement>(null);
  return (
    <section
      style={{ "--g5-accent": accent } as CSSProperties}
      className={`overflow-hidden rounded-3xl bg-[#111214] p-6 text-[#f4f4ef] sm:p-10 ${className}`}
    >
      <div className="grid items-center gap-8 lg:grid-cols-[1.1fr_1fr]">
        <div>
          <p className="mb-6 flex items-center gap-2 font-mono text-[11px] tracking-[.12em] text-[#c2c4bd] uppercase">
            <span
              aria-hidden="true"
              className="size-1.5 rounded-full bg-[var(--g5-accent)]"
            />
            {eyebrow}
          </p>
          <Heading className="whitespace-pre-line text-[clamp(2.6rem,6.5vw,5.7rem)] leading-[.99] font-medium tracking-[-.075em]">
            {title}
          </Heading>
          <p className="mt-6 max-w-md text-sm leading-7 text-[#b4b6af] sm:text-base">
            {description}
          </p>
          <div className="mt-8 flex flex-wrap gap-3">
            <a
              href={sanitizeHref(action.href)}
              className="inline-flex min-h-11 items-center gap-5 rounded-full bg-[var(--g5-accent)] px-6 py-3 text-sm font-semibold text-[#111214] transition-opacity hover:opacity-85 motion-reduce:transition-none"
            >
              {action.label}
              <span aria-hidden="true">↗</span>
            </a>
            <a
              href={sanitizeHref(secondaryAction.href)}
              className="inline-flex min-h-11 items-center rounded-full border border-white/20 px-5 py-3 text-sm transition-colors hover:bg-white/10 motion-reduce:transition-none"
            >
              {secondaryAction.label}
            </a>
          </div>
        </div>
        <div
          ref={art}
          aria-hidden="true"
          className="relative mx-auto flex aspect-square w-full max-w-[460px] items-center justify-center [perspective:900px]"
          onPointerMove={(event) => {
            if (event.pointerType === "touch" || !art.current) return;
            const box = event.currentTarget.getBoundingClientRect();
            art.current.style.setProperty(
              "--g5-x",
              `${((event.clientY - box.top) / box.height - 0.5) * -14}deg`,
            );
            art.current.style.setProperty(
              "--g5-y",
              `${((event.clientX - box.left) / box.width - 0.5) * 14}deg`,
            );
          }}
          onPointerLeave={() => {
            art.current?.style.setProperty("--g5-x", "0deg");
            art.current?.style.setProperty("--g5-y", "0deg");
          }}
        >
          <div className="absolute inset-6 rounded-full border border-dashed border-white/15" />
          <div className="absolute inset-16 rounded-full border border-white/10" />
          <div className="relative flex size-[65%] items-center justify-center rounded-[2.5rem] border border-white/15 bg-[#1e2023] shadow-[0_30px_80px_#0008] transition-transform duration-500 motion-safe:[transform:rotateX(var(--g5-x,0deg))_rotateY(var(--g5-y,0deg))_rotate(-12deg)] motion-reduce:transition-none">
            <span className="font-mono text-[clamp(4rem,10vw,8rem)] font-bold tracking-[-.12em] text-[var(--g5-accent)]">
              {mark}
            </span>
            <span className="absolute inset-5 rounded-[1.7rem] border border-white/10" />
          </div>
          <span className="absolute start-0 top-[17%] rounded-xl border border-white/20 bg-[#282b2e] px-5 py-3 font-mono text-xs shadow-xl motion-safe:-rotate-12">
            {artworkLabels[0]}
            <span className="ms-4 text-[var(--g5-accent)]">✳</span>
          </span>
          <span className="absolute end-0 top-[40%] rounded-xl bg-[var(--g5-accent)] px-5 py-3 font-mono text-xs text-[#111214] shadow-xl motion-safe:rotate-6">
            {artworkLabels[1]}
            <span className="ms-4">↗</span>
          </span>
          <span className="absolute bottom-[12%] start-[12%] rounded-xl border border-white/20 bg-[#282b2e] px-5 py-3 font-mono text-xs shadow-xl motion-safe:rotate-6">
            {artworkLabels[2]}
            <span className="ms-4 text-[var(--g5-accent)]">⌘</span>
          </span>
        </div>
      </div>
    </section>
  );
}

sanitizeHref → lib/gear5/sanitize.ts
/**
 * Security primitive: neutralise the one class of URL that turns "render a
 * link" into "run arbitrary script" — `javascript:` and other executable
 * schemes reaching an `href`/`src` from data the app did not author itself
 * (a CMS field, a query param, a user's own profile link).
 */

const SAFE_SCHEMES = new Set(["http:", "https:", "mailto:", "tel:", "sms:"]);

/**
 * Returns `url` unchanged if its scheme is safe to navigate to, or `"#"`
 * otherwise. Relative URLs (no scheme) are always safe and pass through.
 */
export function sanitizeHref(url: string): string {
  const trimmed = url.trim();
  if (!trimmed) return "#";

  // Browsers discard tabs and newlines when parsing a URL scheme.
  const schemeProbe = trimmed.replace(/[\t\r\n]/g, "");
  if (!/^[a-z][a-z0-9+.-]*:/i.test(schemeProbe)) return trimmed;

  try {
    const scheme = new URL(trimmed, "https://example.com").protocol;
    return SAFE_SCHEMES.has(scheme) ? trimmed : "#";
  } catch {
    return "#";
  }
}

Project Gallery → components/gear5/project-gallery.tsx
"use client";

import { useId, useState, type CSSProperties, type ReactNode } from "react";
import { sanitizeHref } from "@/lib/gear5/sanitize";

export interface GalleryProject {
  id: string;
  title: string;
  category: string;
  description: string;
  /** Add a locally hosted image, optimized with your framework, or a small SVG preview. */
  artwork?: ReactNode;
  artworkAlt?: string;
  href?: string;
  linkLabel?: string;
}

const exampleProjects: GalleryProject[] = [
  {
    id: "forma",
    title: "Forma",
    category: "Identity",
    description: "A quiet visual language for architecture with big ideas.",
    artworkAlt: "Abstract sage-green identity card showing a serif f.",
    artwork: (
      <div className="flex aspect-[5/4] items-center justify-center overflow-hidden bg-[#c8d5c1] text-[#273028]">
        <span className="font-serif text-[clamp(5rem,18vw,10rem)] tracking-[-.12em]">
          f.
        </span>
      </div>
    ),
    linkLabel: "Read the Forma case study",
  },
  {
    id: "mono",
    title: "Mono",
    category: "Product",
    description: "A calmer way to keep a small team moving forward.",
    artworkAlt: "Lavender product tile marked m with an upward arrow.",
    artwork: (
      <div className="flex aspect-[5/4] items-center justify-center overflow-hidden bg-[#d2ccef] text-[#302b48]">
        <span className="font-mono text-[clamp(4rem,15vw,8rem)] tracking-[-.16em]">
          m↗
        </span>
      </div>
    ),
    linkLabel: "Read the Mono case study",
  },
  {
    id: "terrain",
    title: "Terrain",
    category: "Digital",
    description: "A digital home that makes room for the natural world.",
    artworkAlt: "Warm sand-colored creative title card marked t.",
    artwork: (
      <div className="flex aspect-[5/4] items-center justify-center overflow-hidden bg-[#e8cdb5] text-[#49362b]">
        <span className="font-serif text-[clamp(4rem,15vw,8rem)] italic">
          t*
        </span>
      </div>
    ),
    linkLabel: "Read the Terrain case study",
  },
];

export interface ProjectGalleryProps {
  projects?: GalleryProject[];
  title?: string;
  eyebrow?: string;
  allLabel?: string;
  emptyLabel?: string;
  accent?: string;
  className?: string;
}

/** A filtered, image-forward portfolio grid with a keyboard-friendly detail preview. */
export function ProjectGallery({
  projects = exampleProjects,
  title = "Selected work, up close.",
  eyebrow = "A few things I’ve made",
  allLabel = "Everything",
  emptyLabel = "Add projects to see them here.",
  accent = "#d9fc87",
  className = "",
}: ProjectGalleryProps) {
  const id = useId();
  const [category, setCategory] = useState(allLabel);
  const [selectedId, setSelectedId] = useState(projects[0]?.id ?? "");
  const categories = [
    allLabel,
    ...new Set(projects.map((project) => project.category)),
  ];
  const visible =
    category === allLabel
      ? projects
      : projects.filter((project) => project.category === category);
  const selected =
    visible.find((project) => project.id === selectedId) ?? visible[0];

  function moveSelection(index: number) {
    const project = visible[index];
    if (project) setSelectedId(project.id);
  }

  return (
    <section
      style={{ "--g5-accent": accent } as CSSProperties}
      aria-labelledby={`${id}-title`}
      className={`rounded-3xl bg-[#111214] p-6 text-[#f4f4ef] sm:p-10 ${className}`}
    >
      <div className="flex flex-wrap items-end justify-between gap-5">
        <div>
          <p className="font-mono text-[11px] tracking-[.12em] text-[#b4b6af] uppercase">
            {eyebrow}
          </p>
          <h2
            id={`${id}-title`}
            className="mt-3 text-3xl font-medium tracking-[-.05em] sm:text-4xl"
          >
            {title}
          </h2>
        </div>
        {categories.length > 2 && (
          <div aria-label="Filter projects" className="flex flex-wrap gap-2">
            {categories.map((option) => (
              <button
                key={option}
                type="button"
                aria-pressed={category === option}
                onClick={() => setCategory(option)}
                className={`min-h-10 rounded-full border px-4 py-2 text-xs transition-colors motion-reduce:transition-none ${
                  category === option
                    ? "border-transparent bg-[var(--g5-accent)] font-medium text-[#111214]"
                    : "border-white/15 text-[#c2c4bd] hover:bg-white/5"
                }`}
              >
                {option === allLabel ? allLabel : option}
              </button>
            ))}
          </div>
        )}
      </div>

      {visible.length === 0 ? (
        <p className="mt-8 rounded-2xl border border-white/10 px-5 py-12 text-center text-sm text-[#b4b6af]">
          {emptyLabel}
        </p>
      ) : (
        <>
          <div
            role="group"
            aria-label="Projects"
            className="mt-8 grid gap-3 sm:grid-cols-2 lg:grid-cols-3"
          >
            {visible.map((project, index) => (
              <button
                key={project.id}
                id={`${id}-project-${project.id}`}
                type="button"
                aria-pressed={selected?.id === project.id}
                aria-controls={`${id}-detail`}
                onClick={() => moveSelection(index)}
                onKeyDown={(event) => {
                  let next: number;
                  if (event.key === "Home") next = 0;
                  else if (event.key === "End") next = visible.length - 1;
                  else if (
                    event.key === "ArrowRight" ||
                    event.key === "ArrowDown"
                  )
                    next = (index + 1) % visible.length;
                  else if (event.key === "ArrowLeft" || event.key === "ArrowUp")
                    next = (index - 1 + visible.length) % visible.length;
                  else return;
                  event.preventDefault();
                  moveSelection(next);
                  document
                    .getElementById(`${id}-project-${visible[next].id}`)
                    ?.focus();
                }}
                className={`group min-w-0 overflow-hidden rounded-2xl border text-start transition-[border-color,opacity] motion-reduce:transition-none ${
                  selected?.id === project.id
                    ? "border-[var(--g5-accent)]"
                    : "border-white/10 hover:border-white/35"
                }`}
              >
                <div
                  aria-hidden={!project.artworkAlt}
                  role={project.artworkAlt ? "img" : undefined}
                  aria-label={project.artworkAlt}
                  className="overflow-hidden [&>div]:transition-transform [&>div]:duration-500 motion-safe:group-hover:[&>div]:scale-[1.03] motion-reduce:[&>div]:transition-none"
                >
                  {project.artwork ?? (
                    <div className="flex aspect-[5/4] items-center justify-center bg-[#26292b] font-serif text-7xl text-white/70">
                      {project.title.slice(0, 1)}
                    </div>
                  )}
                </div>
                <span className="flex items-center justify-between gap-3 px-4 py-4">
                  <span className="min-w-0">
                    <span className="block truncate text-sm font-medium">
                      {project.title}
                    </span>
                    <span className="mt-1 block font-mono text-[10px] text-[#b4b6af]">
                      {project.category}
                    </span>
                  </span>
                  <span
                    aria-hidden="true"
                    className="text-sm text-[var(--g5-accent)]"
                  >
                    {selected?.id === project.id ? "↗" : "+"}
                  </span>
                </span>
              </button>
            ))}
          </div>

          {selected && (
            <article
              id={`${id}-detail`}
              aria-live="polite"
              aria-atomic="true"
              className="mt-4 grid overflow-hidden rounded-2xl border border-white/10 bg-[#181a1c] sm:grid-cols-[.8fr_1fr]"
            >
              <div
                aria-hidden={!selected.artworkAlt}
                role={selected.artworkAlt ? "img" : undefined}
                aria-label={selected.artworkAlt}
                className="min-h-48 overflow-hidden sm:min-h-64"
              >
                {selected.artwork ?? (
                  <div className="flex h-full min-h-48 items-center justify-center bg-[#26292b] font-serif text-8xl text-white/70 sm:min-h-64">
                    {selected.title.slice(0, 1)}
                  </div>
                )}
              </div>
              <div className="flex flex-col justify-center p-6 sm:p-8">
                <p className="font-mono text-[10px] text-[var(--g5-accent)] uppercase">
                  {selected.category}
                </p>
                <h3 className="mt-3 text-2xl font-medium tracking-[-.04em]">
                  {selected.title}
                </h3>
                <p className="mt-3 max-w-md text-sm leading-7 text-[#b4b6af]">
                  {selected.description}
                </p>
                {selected.href && (
                  <a
                    href={sanitizeHref(selected.href)}
                    className="mt-6 inline-flex min-h-11 w-fit items-center gap-4 rounded-full border border-white/20 px-5 py-3 text-sm transition-colors hover:bg-white/5 motion-reduce:transition-none"
                  >
                    {selected.linkLabel ?? "View project"}
                    <span
                      aria-hidden="true"
                      className="text-[var(--g5-accent)]"
                    >
                      ↗
                    </span>
                  </a>
                )}
              </div>
            </article>
          )}
        </>
      )}
    </section>
  );
}

Spotlight Bento → components/gear5/spotlight-bento.tsx
"use client";

import {
  useEffect,
  useId,
  useRef,
  type CSSProperties,
  type ReactNode,
  type PointerEvent,
} from "react";
import { sanitizeHref } from "@/lib/gear5/sanitize";

export interface BentoFeature {
  id: string;
  eyebrow: string;
  title: string;
  description: string;
  size?: "standard" | "wide" | "tall";
  visual?: ReactNode;
  href?: string;
  linkLabel?: string;
}

const defaults: BentoFeature[] = [
  {
    id: "direction",
    eyebrow: "01 / A clear point of view",
    title: "Know what makes you, you.",
    description: "A considered identity gives every detail a place to belong.",
    size: "wide",
    visual: (
      <div
        aria-hidden="true"
        className="flex items-center gap-3 overflow-hidden"
      >
        <span className="font-serif text-6xl tracking-[-.08em]">Aa</span>
        <span className="h-px flex-1 bg-current/30" />
        <span className="font-mono text-xs">FIND YOUR FORM</span>
      </div>
    ),
  },
  {
    id: "details",
    eyebrow: "02 / Made with intention",
    title: "The little things add up.",
    description:
      "From the first hello to the final hover, every moment feels considered.",
    size: "tall",
    visual: (
      <div aria-hidden="true" className="flex gap-2">
        {["#d9fc87", "#d2ccef", "#e8cdb5"].map((color, index) => (
          <span
            key={color}
            style={{
              backgroundColor: color,
              transform: `translateY(${index * -5}px)`,
            }}
            className="size-10 rounded-xl"
          />
        ))}
      </div>
    ),
  },
  {
    id: "delivery",
    eyebrow: "03 / Ready to go further",
    title: "Beautiful, then usable.",
    description:
      "A smooth handoff keeps the good work moving into the real world.",
    visual: (
      <div
        aria-hidden="true"
        className="flex items-center gap-2 font-mono text-sm"
      >
        <span className="rounded-full border border-current/20 px-3 py-2">
          Brief
        </span>
        <span className="text-[var(--g5-accent)]">→</span>
        <span className="rounded-full border border-current/20 px-3 py-2">
          Launch
        </span>
      </div>
    ),
  },
  {
    id: "people",
    eyebrow: "04 / Built around real people",
    title: "A thoughtful way to move forward.",
    description:
      "Make a useful promise clear, then give people a confident next step.",
    visual: (
      <div
        aria-hidden="true"
        className="flex items-center gap-3 font-mono text-xs text-white/60"
      >
        <span className="size-8 rounded-full border border-white/20 bg-[var(--g5-accent)]/20" />
        MADE FOR PEOPLE
      </div>
    ),
  },
];

export interface SpotlightBentoProps {
  features?: BentoFeature[];
  title?: string;
  eyebrow?: string;
  accent?: string;
  className?: string;
}

/** Unequal content tiles with a restrained mouse spotlight and static touch fallback. */
export function SpotlightBento({
  features = defaults,
  title = "Good ideas, with room to breathe.",
  eyebrow = "The details make it",
  accent = "#d9fc87",
  className = "",
}: SpotlightBentoProps) {
  const id = useId();
  const reducedMotion = useRef(false);
  useEffect(() => {
    const preference = window.matchMedia?.(
      "(prefers-reduced-motion: reduce)",
    );
    if (!preference) return;

    const update = () => {
      reducedMotion.current = preference.matches;
    };
    update();
    preference.addEventListener?.("change", update);
    return () => preference.removeEventListener?.("change", update);
  }, []);
  return (
    <section
      style={{ "--g5-accent": accent } as CSSProperties}
      aria-labelledby={`${id}-title`}
      className={`rounded-3xl bg-[#111214] p-6 text-[#f4f4ef] sm:p-10 ${className}`}
    >
      <p className="font-mono text-[11px] tracking-[.12em] text-[#b4b6af] uppercase">
        {eyebrow}
      </p>
      <h2
        id={`${id}-title`}
        className="mt-3 max-w-2xl text-3xl font-medium tracking-[-.05em] sm:text-4xl"
      >
        {title}
      </h2>
      {features.length === 0 ? (
        <p className="mt-6 text-sm text-[#b4b6af]">
          Add a feature to start your grid.
        </p>
      ) : (
        <div className="mt-8 grid auto-rows-[minmax(12rem,auto)] gap-3 sm:grid-cols-2 lg:grid-cols-3">
          {features.map((feature) => {
            const Tag = feature.href ? "a" : "article";
            const linkProps = feature.href
              ? { href: sanitizeHref(feature.href) }
              : {};
            return (
              <Tag
                key={feature.id}
                {...linkProps}
                onPointerMove={(event: PointerEvent<HTMLElement>) => {
                  if (event.pointerType === "touch" || reducedMotion.current)
                    return;
                  const box = event.currentTarget.getBoundingClientRect();
                  event.currentTarget.style.setProperty(
                    "--g5-x",
                    `${event.clientX - box.left}px`,
                  );
                  event.currentTarget.style.setProperty(
                    "--g5-y",
                    `${event.clientY - box.top}px`,
                  );
                }}
                className={`group relative isolate flex min-w-0 flex-col justify-between overflow-hidden rounded-2xl border border-white/10 bg-[#181a1c] p-6 transition-[border-color] hover:border-white/30 focus-visible:border-[var(--g5-accent)] motion-reduce:transition-none ${
                  feature.href ? "focus-visible:outline-offset-4" : ""
                } ${
                  feature.size === "wide"
                    ? "sm:col-span-2"
                    : feature.size === "tall"
                      ? "sm:row-span-2"
                      : ""
                }`}
              >
                <span
                  aria-hidden="true"
                  className="pointer-events-none absolute inset-0 -z-10 opacity-0 transition-opacity duration-300 group-hover:opacity-100 group-focus-within:opacity-100 motion-reduce:transition-none"
                  style={{
                    backgroundImage:
                      "radial-gradient(340px circle at var(--g5-x, 50%) var(--g5-y, 50%), color-mix(in srgb, var(--g5-accent) 12%, transparent), transparent 80%)",
                  }}
                />
                <div>
                  <p className="font-mono text-[10px] text-[var(--g5-accent)] uppercase">
                    {feature.eyebrow}
                  </p>
                  <h3 className="mt-5 text-2xl font-medium tracking-[-.04em]">
                    {feature.title}
                  </h3>
                  <p className="mt-3 max-w-sm text-sm leading-7 text-[#b4b6af]">
                    {feature.description}
                  </p>
                </div>
                <div className="mt-8 flex min-h-12 items-end justify-between gap-4">
                  {feature.visual && <div>{feature.visual}</div>}
                  {feature.href && (
                    <span className="ms-auto inline-flex items-center gap-2 text-xs text-[#c2c4bd]">
                      {feature.linkLabel ?? "Explore"}
                      <span
                        aria-hidden="true"
                        className="text-[var(--g5-accent)]"
                      >
                        ↗
                      </span>
                    </span>
                  )}
                </div>
              </Tag>
            );
          })}
        </div>
      )}
    </section>
  );
}

Pricing Switch → components/gear5/pricing-switch.tsx
"use client";

import { useId, useState, type CSSProperties } from "react";
import { sanitizeHref } from "@/lib/gear5/sanitize";

export interface PricingPlan {
  id: string;
  name: string;
  description: string;
  monthlyPrice: string;
  yearlyPrice: string;
  monthlyNote: string;
  yearlyNote: string;
  features: string[];
  href: string;
  featured?: boolean;
}
const plansDefault: PricingPlan[] = [
  {
    id: "starter",
    name: "Starter",
    description: "A little room to explore.",
    monthlyPrice: "$12",
    yearlyPrice: "$9",
    monthlyNote: "$12 billed each month",
    yearlyNote: "$108 billed once a year",
    features: ["3 active projects", "Unlimited ideas", "Community support"],
    href: "#contact",
  },
  {
    id: "studio",
    name: "Studio",
    description: "For your next big thing.",
    monthlyPrice: "$29",
    yearlyPrice: "$24",
    monthlyNote: "$29 billed each month",
    yearlyNote: "$288 billed once a year",
    features: ["Unlimited projects", "Shared workspaces", "Priority support"],
    href: "#contact",
    featured: true,
  },
];
export interface PricingSwitchProps {
  plans?: PricingPlan[];
  title?: string;
  eyebrow?: string;
  monthlyLabel?: string;
  yearlyLabel?: string;
  billingLabel?: string;
  unitLabel?: string;
  actionLabel?: string;
  featuredLabel?: string;
  accent?: string;
  className?: string;
}

/** Prices and billing notes are explicit caller data; no checkout or automatic billing. */
export function PricingSwitch({
  plans = plansDefault,
  title = "Small start. Big possibilities.",
  eyebrow = "Simple pricing",
  monthlyLabel = "Monthly",
  yearlyLabel = "Yearly",
  billingLabel = "Billing period",
  unitLabel = "/ month",
  actionLabel = "Get started",
  featuredLabel = "Popular",
  accent = "#d9fc87",
  className = "",
}: PricingSwitchProps) {
  const id = useId();
  const [yearly, setYearly] = useState(false);
  return (
    <section
      style={{ "--g5-accent": accent } as CSSProperties}
      className={`rounded-3xl bg-[#111214] p-6 text-[#f4f4ef] sm:p-10 ${className}`}
    >
      <p className="font-mono text-[11px] tracking-[.12em] text-[#b4b6af] uppercase">
        {eyebrow}
      </p>
      <h2 className="mt-3 text-3xl font-medium tracking-[-.05em] sm:text-4xl">
        {title}
      </h2>
      <fieldset className="mt-7 flex w-fit gap-1 rounded-full border border-white/20 p-1">
        <legend className="sr-only">{billingLabel}</legend>
        {[
          [false, monthlyLabel],
          [true, yearlyLabel],
        ].map(([value, label]) => (
          <label
            key={String(value)}
            className={`relative cursor-pointer rounded-full px-5 py-2.5 text-sm transition-colors focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-white motion-reduce:transition-none ${yearly === value ? "bg-[var(--g5-accent)] text-[#111214]" : "text-[#c2c4bd]"}`}
          >
            <input
              type="radio"
              name={`${id}-billing`}
              checked={yearly === value}
              onChange={() => setYearly(Boolean(value))}
              className="sr-only"
            />
            {label}
          </label>
        ))}
      </fieldset>
      <div className="mt-7 grid gap-4 sm:grid-cols-2">
        {plans.map((plan) => (
          <article
            key={plan.id}
            className={`relative flex flex-col rounded-2xl border p-6 ${plan.featured ? "border-[var(--g5-accent)] bg-[#1b2017]" : "border-white/15 bg-[#181a1c]"}`}
          >
            <div className="flex items-center justify-between gap-3">
              <h3 className="text-lg font-medium">{plan.name}</h3>
              {plan.featured && (
                <span className="rounded-full bg-[var(--g5-accent)] px-3 py-1 text-[10px] font-semibold text-[#111214]">
                  {featuredLabel}
                </span>
              )}
            </div>
            <p className="mt-2 text-sm text-[#b4b6af]">{plan.description}</p>
            <div aria-live="polite" aria-atomic="true" className="my-7">
              <p className="text-sm text-[#b4b6af]">
                <span className="me-2 text-5xl font-medium tracking-[-.06em] text-[#f4f4ef]">
                  {yearly ? plan.yearlyPrice : plan.monthlyPrice}
                </span>
                {unitLabel}
              </p>
              <p className="mt-3 text-xs text-[#b4b6af]">
                {yearly ? plan.yearlyNote : plan.monthlyNote}
              </p>
            </div>
            <ul className="mb-7 space-y-3 text-sm text-[#d5d6cf]">
              {plan.features.map((feature) => (
                <li key={feature} className="flex gap-3">
                  <span aria-hidden="true" className="text-[var(--g5-accent)]">
                    ✓
                  </span>
                  {feature}
                </li>
              ))}
            </ul>
            <a
              href={sanitizeHref(plan.href)}
              className={`mt-auto flex min-h-11 items-center justify-between rounded-full px-5 py-3 text-sm font-medium transition-opacity hover:opacity-80 motion-reduce:transition-none ${plan.featured ? "bg-[var(--g5-accent)] text-[#111214]" : "border border-white/20 text-[#f4f4ef]"}`}
            >
              {actionLabel}
              <span aria-hidden="true">↗</span>
            </a>
          </article>
        ))}
      </div>
    </section>
  );
}

Testimonial Deck → components/gear5/testimonial-deck.tsx
"use client";

import { useState, type CSSProperties } from "react";

export interface DeckTestimonial {
  id: string;
  quote: string;
  name: string;
  role: string;
}
export const deckTestimonials: DeckTestimonial[] = [
  {
    id: "a",
    quote:
      "The rare kind of collaborator who makes the idea better, then makes it happen.",
    name: "Jamie Chen",
    role: "Founder, Forma · Example testimonial",
  },
  {
    id: "b",
    quote:
      "Every interaction feels considered. Our product finally looks as good as it works.",
    name: "Morgan Lee",
    role: "Product lead, Mono · Example testimonial",
  },
  {
    id: "c",
    quote:
      "From the first conversation to the last detail, the whole process was a joy.",
    name: "Sam Rivera",
    role: "Editor, Terrain · Example testimonial",
  },
];
export interface TestimonialDeckProps {
  testimonials?: DeckTestimonial[];
  title?: string;
  eyebrow?: string;
  previousLabel?: string;
  nextLabel?: string;
  positionLabel?: (current: number, total: number) => string;
  accent?: string;
  className?: string;
}

/** Manual navigation; nothing auto-plays or moves focus when the quote changes. */
export function TestimonialDeck({
  testimonials = deckTestimonials,
  title = "Good work. Better company.",
  eyebrow = "Kind words",
  previousLabel = "Previous testimonial",
  nextLabel = "Next testimonial",
  positionLabel = (current, total) => `${current} / ${total}`,
  accent = "#d9fc87",
  className = "",
}: TestimonialDeckProps) {
  const [selected, setSelected] = useState(0);
  const active = Math.min(selected, Math.max(0, testimonials.length - 1));
  const item = testimonials[active];
  return (
    <section
      style={{ "--g5-accent": accent } as CSSProperties}
      className={`rounded-3xl bg-[#111214] p-6 text-[#f4f4ef] sm:p-10 ${className}`}
    >
      <p className="font-mono text-[11px] tracking-[.12em] text-[#b4b6af] uppercase">
        {eyebrow}
      </p>
      <h2 className="mt-3 text-3xl font-medium tracking-[-.05em] sm:text-4xl">
        {title}
      </h2>
      {item && (
        <>
          <div
            className="relative mt-8 min-h-64 rounded-2xl border border-white/15 bg-[#181a1c] p-6 transition-transform duration-300 motion-safe:hover:-translate-y-1 motion-reduce:transition-none sm:p-8"
            aria-live="polite"
            aria-atomic="true"
          >
            <span
              aria-hidden="true"
              className="font-serif text-6xl leading-none text-[var(--g5-accent)]"
            >
              “
            </span>
            <blockquote className="mt-2 max-w-2xl text-xl leading-relaxed tracking-[-.02em] sm:text-2xl">
              {item.quote}
            </blockquote>
            <div className="mt-7 flex items-center gap-3">
              <span
                aria-hidden="true"
                className="flex size-10 shrink-0 items-center justify-center rounded-full bg-[var(--g5-accent)] text-sm font-medium text-[#111214]"
              >
                {item.name.slice(0, 1)}
              </span>
              <div>
                <p className="text-sm font-medium">{item.name}</p>
                <p className="mt-1 text-xs text-[#b4b6af]">{item.role}</p>
              </div>
            </div>
          </div>
          <div className="mt-5 flex items-center justify-between">
            <span className="font-mono text-xs text-[#b4b6af]">
              {positionLabel(active + 1, testimonials.length)}
            </span>
            <div className="flex gap-2">
              <button
                type="button"
                aria-label={previousLabel}
                disabled={testimonials.length < 2}
                onClick={() =>
                  setSelected(
                    (active - 1 + testimonials.length) % testimonials.length,
                  )
                }
                className="flex size-11 items-center justify-center rounded-full border border-white/20 hover:bg-white/10 disabled:opacity-40"
              >
                ←
              </button>
              <button
                type="button"
                aria-label={nextLabel}
                disabled={testimonials.length < 2}
                onClick={() => setSelected((active + 1) % testimonials.length)}
                className="flex size-11 items-center justify-center rounded-full border border-white/20 hover:bg-white/10 disabled:opacity-40"
              >
                →
              </button>
            </div>
          </div>
        </>
      )}
    </section>
  );
}

Read the setup guide ↗