{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "template-portfolio",
  "title": "Folio: designer portfolio",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A bold five-page portfolio: a huge intro with inline colour pills, a work grid that reveals each result on hover and focus, an about bento, a case study with a sticky fact sheet and contents list, writing and a uses page.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/blur-fade.json",
    "https://ui.ballmac.com/r/copy-button.json",
    "https://ui.ballmac.com/r/marquee.json",
    "https://ui.ballmac.com/r/table-of-contents.json",
    "https://ui.ballmac.com/r/media.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/templates/portfolio/portfolio-fonts.ts",
      "content": "// Ballmac UI: Portfolio template fonts. https://ui.ballmac.com/templates/template-portfolio\nimport { Bricolage_Grotesque, Geist_Mono } from \"next/font/google\"\n\nexport const portfolioSans = Bricolage_Grotesque({ variable: \"--portfolio-sans\", subsets: [\"latin\"], axes: [\"opsz\", \"wdth\"], display: \"swap\", preload: false })\nexport const portfolioMono = Geist_Mono({ variable: \"--portfolio-mono\", subsets: [\"latin\"], display: \"swap\", preload: false })\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/portfolio/portfolio-fonts.ts"
    },
    {
      "path": "registry/ballmac/components/templates/portfolio/portfolio-data.ts",
      "content": "import type { MediaSource } from \"@/components/ballmac/media\"\n\n// Ballmac UI: Portfolio sample content. https://ui.ballmac.com/templates/template-portfolio\n\nexport type PortfolioProject = {\n  slug: string\n  title: string\n  client: string\n  year: string\n  discipline: \"Product\" | \"Brand\" | \"Systems\"\n  blurb: string\n  result: string\n  tags: string[]\n  /** Which painted cover to draw (0 to 5). */\n  cover: number\n  /** Your own image instead of the painted art: an image URL (give it imageAlt), an object with alt text and a dark-mode file, or your own element. */\n  image?: MediaSource\n  /** Describes `image` when it is a plain URL. */\n  imageAlt?: string\n}\n\nexport const projects: PortfolioProject[] = [\n  { slug: \"fernhill\", title: \"Rebuilding onboarding\", client: \"Fernhill\", year: \"2026\", discipline: \"Product\", blurb: \"A first-run flow that gets teams to their first invoice in one sitting.\", result: \"+34% activation\", tags: [\"Product design\", \"Research\", \"Prototyping\"], cover: 0 },\n  { slug: \"lumen\", title: \"A design system in six weeks\", client: \"Lumen Health\", year: \"2025\", discipline: \"Systems\", blurb: \"Tokens, 60 components and the docs that made engineers actually use them.\", result: \"-40% handoff time\", tags: [\"Design systems\", \"Documentation\"], cover: 1 },\n  { slug: \"paddock\", title: \"Identity for a riding school\", client: \"Paddock\", year: \"2025\", discipline: \"Brand\", blurb: \"A mark, a typeface pairing and a handbook for a school that outgrew its logo.\", result: \"3 offices, 1 look\", tags: [\"Brand\", \"Print\", \"Wayfinding\"], cover: 2 },\n  { slug: \"halcyon\", title: \"A calmer banking app\", client: \"Halcyon\", year: \"2024\", discipline: \"Product\", blurb: \"Spending insights that read like a sentence, not a spreadsheet.\", result: \"4.8 App Store rating\", tags: [\"Mobile\", \"Data visualisation\"], cover: 3 },\n  { slug: \"tessera\", title: \"Pricing page, tested\", client: \"Tessera\", year: \"2024\", discipline: \"Product\", blurb: \"Six experiments, one clear winner and a page the sales team stopped apologising for.\", result: \"+22% trial starts\", tags: [\"Experimentation\", \"Copy\"], cover: 4 },\n  { slug: \"quanta\", title: \"Tokens that travel\", client: \"Quanta\", year: \"2023\", discipline: \"Systems\", blurb: \"A theming layer that let one product ship in nine brands.\", result: \"9 brands, 1 codebase\", tags: [\"Design systems\", \"Tokens\"], cover: 5 },\n]\n\nexport type PortfolioPost = { slug: string; title: string; date: string; read: number; tag: \"Process\" | \"Craft\" | \"Career\"; excerpt: string }\n\nexport const posts: PortfolioPost[] = [\n  { slug: \"p1\", title: \"The first-run checklist nobody wants to ship\", date: \"2026-08-14\", read: 7, tag: \"Process\", excerpt: \"Why the thing that tests best is usually the thing that annoys the team.\" },\n  { slug: \"p2\", title: \"Stop designing empty states last\", date: \"2026-05-02\", read: 5, tag: \"Craft\", excerpt: \"Empty is the first thing every new user sees. Treat it that way.\" },\n  { slug: \"p3\", title: \"What I learned from deleting 40 components\", date: \"2026-02-19\", read: 9, tag: \"Process\", excerpt: \"A design system is a garden. A small, tidy one beats a sprawling one.\" },\n  { slug: \"p4\", title: \"Going independent after nine years\", date: \"2025-11-07\", read: 6, tag: \"Career\", excerpt: \"The money, the loneliness and the three clients that made it work.\" },\n  { slug: \"p5\", title: \"A defence of the boring button\", date: \"2025-06-23\", read: 4, tag: \"Craft\", excerpt: \"Novelty has a cost. Spend it where it pays.\" },\n  { slug: \"p6\", title: \"How I run a design review\", date: \"2025-03-11\", read: 8, tag: \"Process\", excerpt: \"Four questions, one hour and no one defending their pixels.\" },\n]\n\nexport const uses: { group: string; items: { name: string; note: string }[] }[] = [\n  { group: \"Hardware\", items: [{ name: \"MacBook Pro 14″\", note: \"M3 Pro, 36 GB. Mostly plugged into a desk shelf in Lisbon.\" }, { name: \"Studio Display\", note: \"The only monitor I have trusted for colour.\" }, { name: \"Keychron Q1\", note: \"Brown switches. Loud enough that my partner has opinions.\" }, { name: \"iPad Pro + Pencil\", note: \"Sketches, critique markup and reading.\" }] },\n  { group: \"Software\", items: [{ name: \"Figma\", note: \"Design, prototypes and the occasional diagram.\" }, { name: \"Linear\", note: \"Where projects and promises go to be counted.\" }, { name: \"Things\", note: \"Personal tasks. Never work tasks.\" }, { name: \"Raycast\", note: \"Launcher, clipboard history and window layouts.\" }] },\n  { group: \"Type\", items: [{ name: \"Bricolage Grotesque\", note: \"This site, because I wanted something with a personality.\" }, { name: \"Geist Mono\", note: \"Small labels and code.\" }, { name: \"Söhne\", note: \"For client work where budget allows.\" }] },\n  { group: \"Reading\", items: [{ name: \"Refactoring UI\", note: \"Still the best short book on interface design.\" }, { name: \"The Design of Everyday Things\", note: \"Reread every two years.\" }, { name: \"Field notebooks\", note: \"Paper, always. Three at a time.\" }] },\n]\n\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/portfolio/portfolio-data.ts"
    },
    {
      "path": "registry/ballmac/components/templates/portfolio/portfolio-theme.tsx",
      "content": "// Ballmac UI: Portfolio template shell. https://ui.ballmac.com/templates/template-portfolio\n\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowUpRight, Menu, X } from \"lucide-react\"\n\nimport { CopyButton } from \"@/components/ballmac/copy-button\"\nimport { portfolioMono, portfolioSans } from \"@/components/ballmac/templates/portfolio/portfolio-fonts\"\nimport { cn } from \"@/lib/utils\"\nimport { Media, type MediaSource } from \"@/components/ballmac/media\"\n\ntype PortfolioPage = \"home\" | \"work\" | \"case\" | \"writing\" | \"uses\"\ntype PortfolioHrefs = Record<PortfolioPage, string>\n\nconst defaultHrefs: PortfolioHrefs = { home: \"/portfolio\", work: \"/portfolio/work\", case: \"/portfolio/work/fernhill\", writing: \"/portfolio/writing\", uses: \"/portfolio/uses\" }\n\n/** Portfolio's palette: gallery white and ink with one acid lime. In dark mode the lime becomes the light on a black wall. */\nconst portfolioCss = `\n.portfolio-theme,body:not(:has([data-gallery])):has(.portfolio-theme){--background:oklch(0.988 0.003 100);--foreground:oklch(0.16 0.01 100);--card:oklch(1 0 0);--card-foreground:oklch(0.16 0.01 100);--popover:oklch(1 0 0);--popover-foreground:oklch(0.16 0.01 100);--primary:oklch(0.16 0.01 100);--primary-foreground:oklch(0.988 0.003 100);--secondary:oklch(0.958 0.008 100);--secondary-foreground:oklch(0.16 0.01 100);--muted:oklch(0.958 0.008 100);--muted-foreground:oklch(0.46 0.012 100);--accent:oklch(0.94 0.014 110);--accent-foreground:oklch(0.16 0.01 100);--border:oklch(0.16 0.01 100 / 14%);--input:oklch(0.16 0.01 100 / 20%);--ring:oklch(0.16 0.01 100);--surface:oklch(0.968 0.006 100);--destructive:oklch(0.54 0.22 28);--chart-1:oklch(0.9 0.19 125);--chart-2:oklch(0.72 0.17 38);--chart-3:oklch(0.78 0.12 235);--chart-4:oklch(0.76 0.13 300);--chart-5:oklch(0.86 0.09 75);--portfolio-on-accent:oklch(0.16 0.01 100);--radius:1rem}\n.dark .portfolio-theme,.dark body:not(:has([data-gallery])):has(.portfolio-theme){--background:oklch(0.14 0.004 100);--foreground:oklch(0.96 0.006 100);--card:oklch(0.18 0.005 100);--card-foreground:oklch(0.96 0.006 100);--popover:oklch(0.2 0.006 100);--popover-foreground:oklch(0.96 0.006 100);--primary:oklch(0.96 0.006 100);--primary-foreground:oklch(0.16 0.01 100);--secondary:oklch(0.23 0.006 100);--secondary-foreground:oklch(0.96 0.006 100);--muted:oklch(0.22 0.006 100);--muted-foreground:oklch(0.72 0.012 100);--accent:oklch(0.26 0.01 110);--accent-foreground:oklch(0.96 0.006 100);--border:oklch(1 0 0 / 12%);--input:oklch(1 0 0 / 16%);--ring:oklch(0.9 0.19 125);--surface:oklch(0.165 0.005 100);--destructive:oklch(0.7 0.19 28);--chart-1:oklch(0.9 0.19 125);--chart-2:oklch(0.72 0.17 38);--chart-3:oklch(0.76 0.12 235);--chart-4:oklch(0.74 0.13 300);--chart-5:oklch(0.84 0.09 75);--portfolio-on-accent:oklch(0.16 0.01 100)}\nbody:not(:has([data-gallery])):has(.portfolio-theme){font-family:var(--portfolio-sans),ui-sans-serif,system-ui,sans-serif}\n`\n\n/** A painted cover: abstract shapes in the chart colours standing in for a screenshot. */\nfunction Cover({ variant, className, image, imageAlt }: { variant: number; className?: string; image?: MediaSource; imageAlt?: string }) {\n  const v = ((variant % 6) + 6) % 6\n  if (image) return <Media media={image} alt={imageAlt} fill className={cn(\"aspect-[4/3] w-full\", className)} />\n  return (\n    <div aria-hidden=\"true\" className={cn(\"relative isolate aspect-[4/3] w-full overflow-hidden\", className)}>\n      {v === 0 && (<><div className=\"bg-chart-5 absolute inset-0\" /><div className=\"bg-card absolute inset-x-[12%] top-[14%] bottom-0 rounded-t-2xl shadow-xl\"><div className=\"bg-chart-1 m-[7%] h-[14%] w-[40%] rounded-md\" /><div className=\"bg-muted mx-[7%] h-[8%] w-[70%] rounded\" /><div className=\"bg-muted mx-[7%] mt-[3%] h-[8%] w-[54%] rounded\" /><div className=\"mx-[7%] mt-[7%] grid grid-cols-3 gap-[3%]\">{[0, 1, 2].map((i) => <div key={i} className=\"bg-secondary aspect-square rounded-lg\" />)}</div></div></>)}\n      {v === 1 && (<><div className=\"bg-foreground absolute inset-0\" /><div className=\"absolute inset-[10%] grid grid-cols-4 gap-[4%]\">{[chart(1), chart(2), chart(3), chart(4), chart(5), chart(1), chart(3), chart(2)].map((c, i) => <div key={i} className={cn(\"rounded-xl\", c)} />)}</div></>)}\n      {v === 2 && (<><div className=\"bg-chart-2 absolute inset-0\" /><div className=\"absolute top-1/2 left-1/2 aspect-square w-[46%] -translate-x-1/2 -translate-y-1/2\"><div className=\"bg-primary-foreground size-full rounded-full\" /><div className=\"bg-foreground absolute inset-y-0 start-0 w-1/2 rounded-s-full\" /></div></>)}\n      {v === 3 && (<><div className=\"bg-chart-3 absolute inset-0\" /><div className=\"bg-foreground absolute top-[10%] bottom-[-8%] left-1/2 w-[38%] -translate-x-1/2 rounded-[2rem] p-[3%]\"><div className=\"bg-card h-full rounded-[1.4rem] p-[8%]\"><div className=\"bg-chart-1 h-[10%] w-[60%] rounded\" /><div className=\"mt-[10%] flex h-[34%] items-end gap-[6%]\">{[40, 70, 55, 90, 65].map((h, i) => <div key={i} className=\"bg-foreground flex-1 rounded-t\" style={{ height: `${h}%` }} />)}</div></div></div></>)}\n      {v === 4 && (<><div className=\"bg-chart-4 absolute inset-0\" /><div className=\"absolute inset-[12%] grid grid-cols-3 gap-[4%]\">{[0, 1, 2].map((i) => <div key={i} className={cn(\"bg-card rounded-2xl p-[10%]\", i === 1 && \"bg-foreground -translate-y-[6%]\")}><div className={cn(\"h-[10%] w-[60%] rounded\", i === 1 ? \"bg-chart-1\" : \"bg-muted\")} /><div className={cn(\"mt-[18%] h-[22%] w-[50%] rounded\", i === 1 ? \"bg-primary-foreground\" : \"bg-secondary\")} /></div>)}</div></>)}\n      {v === 5 && (<><div className=\"bg-secondary absolute inset-0\" />{[0, 1, 2, 3, 4].map((i) => <div key={i} className={cn(\"absolute w-[34%] aspect-square rounded-full mix-blend-multiply\", chart((i % 5) + 1))} style={{ left: `${8 + i * 14}%`, top: `${i % 2 ? 40 : 16}%` }} />)}</>)}\n    </div>\n  )\n}\nfunction chart(n: number) {\n  return [\"bg-chart-1\", \"bg-chart-2\", \"bg-chart-3\", \"bg-chart-4\", \"bg-chart-5\"][(n - 1) % 5]\n}\n\nconst links: { key: PortfolioPage; label: string }[] = [\n  { key: \"work\", label: \"Work\" },\n  { key: \"writing\", label: \"Writing\" },\n  { key: \"uses\", label: \"Uses\" },\n]\n\ntype PortfolioShellProps = React.ComponentProps<\"div\"> & {\n  /** The page being shown, so its nav link is marked current. */\n  page: PortfolioPage\n  /** Override where pages live (used by previews). */\n  hrefs?: Partial<PortfolioHrefs>\n}\n\n/** Portfolio's frame: a wordmark with an availability chip, a quiet nav and a huge email in the footer. */\nfunction PortfolioShell({ page, hrefs: overrides, className, style, children, ...props }: PortfolioShellProps) {\n  const hrefs = { ...defaultHrefs, ...overrides }\n  const [open, setOpen] = React.useState(false)\n  React.useEffect(() => {\n    const classes = [portfolioSans.variable, portfolioMono.variable].filter(Boolean)\n    document.body.classList.add(...classes)\n    return () => document.body.classList.remove(...classes)\n  }, [])\n  const active = page === \"case\" ? \"work\" : page\n  return (\n    <div\n      data-slot=\"portfolio\"\n      className={cn(\"portfolio-theme bg-background text-foreground relative min-h-dvh overflow-x-clip\", portfolioSans.variable, portfolioMono.variable, className)}\n      style={{ fontFamily: \"var(--portfolio-sans), ui-sans-serif, system-ui, sans-serif\", ...style }}\n      {...props}\n    >\n      <style>{portfolioCss}</style>\n      <header className=\"bg-background/80 sticky top-0 z-40 backdrop-blur-xl\">\n        <div className=\"mx-auto flex h-16 max-w-7xl items-center justify-between px-4 sm:px-8\">\n          <a href={hrefs.home} className=\"focus-visible:ring-ring/50 flex items-center gap-3 rounded-lg outline-none focus-visible:ring-[3px]\">\n            <span className=\"bg-chart-1 text-[var(--portfolio-on-accent)] flex size-9 items-center justify-center rounded-full text-sm font-bold tracking-tight\" aria-hidden=\"true\">IC</span>\n            <span className=\"font-semibold tracking-tight\">Ines Calder</span>\n          </a>\n          <span className=\"text-muted-foreground hidden items-center gap-2 text-xs lg:flex\" style={{ fontFamily: \"var(--portfolio-mono)\" }}>\n            <span className=\"relative flex size-2\"><span className=\"bg-chart-2 absolute inset-0 rounded-full\" /><span className=\"bg-chart-2 absolute inset-0 rounded-full opacity-60 motion-safe:animate-ping\" /></span>\n            Booking Q1 2027 · Lisbon\n          </span>\n          <nav aria-label=\"Main\" className=\"hidden items-center gap-1 md:flex\">\n            {links.map((l) => (\n              <a key={l.key} href={hrefs[l.key]} aria-current={active === l.key ? \"page\" : undefined} className=\"text-muted-foreground hover:text-foreground aria-[current=page]:text-foreground aria-[current=page]:bg-accent focus-visible:ring-ring/50 rounded-full px-4 py-2 text-sm font-medium outline-none transition-colors focus-visible:ring-[3px]\">\n                {l.label}\n              </a>\n            ))}\n            <a href=\"#contact\" className=\"bg-foreground text-background focus-visible:ring-ring/50 ms-2 inline-flex h-10 items-center gap-1.5 rounded-full px-5 text-sm font-semibold outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]\">Say hello <ArrowUpRight className=\"size-4 rtl:-scale-x-100\" aria-hidden=\"true\" /></a>\n          </nav>\n          <button type=\"button\" aria-label={open ? \"Close menu\" : \"Open menu\"} aria-expanded={open} aria-controls=\"portfolio-mobile-menu\" onClick={() => setOpen((v) => !v)} className=\"hover:bg-accent focus-visible:ring-ring/50 inline-flex size-10 items-center justify-center rounded-full outline-none focus-visible:ring-[3px] md:hidden\">\n            {open ? <X className=\"size-5\" aria-hidden=\"true\" /> : <Menu className=\"size-5\" aria-hidden=\"true\" />}\n          </button>\n        </div>\n        {open && (\n          <nav id=\"portfolio-mobile-menu\" aria-label=\"Mobile\" className=\"px-4 pb-4 md:hidden\">\n            {[{ key: \"home\" as const, label: \"Home\" }, ...links].map((l) => <a key={l.key} href={hrefs[l.key]} className=\"hover:bg-accent block rounded-2xl px-4 py-3 text-lg font-medium\">{l.label}</a>)}\n          </nav>\n        )}\n      </header>\n      {children}\n      <footer id=\"contact\" className=\"mt-24 border-t\">\n        <div className=\"mx-auto max-w-7xl px-4 pt-16 pb-10 sm:px-8\">\n          <p className=\"text-muted-foreground text-sm\" style={{ fontFamily: \"var(--portfolio-mono)\" }}>Have a product that needs care?</p>\n          <div className=\"mt-4 flex flex-wrap items-center gap-3\">\n            <a href=\"mailto:hello@inescalder.example\" className=\"focus-visible:ring-ring/50 rounded-xl text-[clamp(2rem,7.4vw,6.5rem)] leading-none font-semibold tracking-[-0.045em] break-all outline-none hover:underline hover:decoration-[0.08em] hover:underline-offset-[0.12em] focus-visible:ring-[3px]\">hello@inescalder.example</a>\n            <CopyButton value=\"hello@inescalder.example\" ariaLabel=\"Copy email address\" className=\"size-11\" />\n          </div>\n          <div className=\"text-muted-foreground mt-14 flex flex-wrap items-center justify-between gap-4 border-t pt-6 text-sm\">\n            <p>© 2026 Ines Calder. Designed and built in Lisbon.</p>\n            <ul className=\"flex gap-5\">{[\"LinkedIn\", \"Read.cv\", \"Are.na\", \"RSS\"].map((l) => <li key={l}><a href=\"#\" className=\"hover:text-foreground transition-colors\">{l}</a></li>)}</ul>\n          </div>\n        </div>\n      </footer>\n    </div>\n  )\n}\n\nexport { Cover, PortfolioShell, defaultHrefs as portfolioDefaultHrefs, type PortfolioHrefs, type PortfolioPage, type PortfolioShellProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/portfolio/portfolio-theme.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/portfolio/portfolio-home.tsx",
      "content": "// Ballmac UI: Portfolio home page. https://ui.ballmac.com/templates/template-portfolio\n\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowDownRight, ArrowUpRight, BookOpen, Headphones, Hammer, MapPin } from \"lucide-react\"\n\nimport { BlurFade } from \"@/components/ballmac/blur-fade\"\nimport { Marquee } from \"@/components/ballmac/marquee\"\nimport { posts, projects } from \"@/components/ballmac/templates/portfolio/portfolio-data\"\nimport { Cover, PortfolioShell, type PortfolioHrefs } from \"@/components/ballmac/templates/portfolio/portfolio-theme\"\nimport { cn } from \"@/lib/utils\"\n\nconst mono = { fontFamily: \"var(--portfolio-mono)\" } as const\n\nconst quotes = [\n  [\"Ines made the hard thing feel obvious. Our activation rate said so.\", \"Sasha Petrova, Fernhill\"],\n  [\"The calmest designer I have ever worked with, and the sharpest.\", \"Marcus Lindqvist, Halcyon\"],\n  [\"She cut our component library in half and everyone thanked her.\", \"Priya Raman, Lumen Health\"],\n  [\"We hired Ines for a logo and got a company worth working for.\", \"Tom Beltrán, Paddock\"],\n]\n\nfunction Pill({ className, children }: { className?: string; children?: React.ReactNode }) {\n  return <span aria-hidden=\"true\" className={cn(\"mx-1 inline-block h-[0.72em] w-[1.6em] rounded-full align-[-0.06em] sm:mx-2\", className)}>{children}</span>\n}\n\ntype PortfolioHomeProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<PortfolioHrefs> }\n\n/** The Portfolio home page: a bold intro, a work grid whose cards reveal a result on hover and focus, an about bento, kind words and recent writing. */\nfunction PortfolioHome({ hrefs, ...props }: PortfolioHomeProps) {\n  const h = { work: hrefs?.work ?? \"/portfolio/work\", case: hrefs?.case ?? \"/portfolio/work/fernhill\", writing: hrefs?.writing ?? \"/portfolio/writing\" }\n  return (\n    <PortfolioShell page=\"home\" hrefs={hrefs} {...props}>\n      <main>\n        <section className=\"mx-auto max-w-7xl px-4 pt-14 pb-10 sm:px-8 sm:pt-24\">\n          <BlurFade>\n            <p className=\"text-muted-foreground text-sm\" style={mono}>Independent product designer · 9 years · Lisbon</p>\n            <h1 className=\"mt-6 max-w-6xl text-[clamp(2.6rem,8.6vw,8rem)] leading-[0.94] font-semibold tracking-[-0.05em] text-balance\">\n              I design products people <Pill className=\"bg-chart-1\" />finish <Pill className=\"bg-chart-2\" />using, and teams <Pill className=\"bg-chart-3\" />enjoy shipping.\n            </h1>\n            <div className=\"mt-10 flex flex-wrap items-center gap-3\">\n              <a href=\"#work\" className=\"bg-foreground text-background focus-visible:ring-ring/50 inline-flex h-12 items-center gap-2 rounded-full px-6 font-semibold outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]\">Selected work <ArrowDownRight className=\"size-4 rtl:-scale-x-100\" aria-hidden=\"true\" /></a>\n              <a href={h.writing} className=\"hover:bg-accent focus-visible:ring-ring/50 inline-flex h-12 items-center rounded-full border px-6 font-semibold outline-none transition-colors focus-visible:ring-[3px]\">Read my writing</a>\n            </div>\n          </BlurFade>\n        </section>\n\n        <section aria-label=\"Skills\" className=\"border-y py-5\">\n          <Marquee speed={40} gap={40} pauseOnHover>\n            {[\"Product design\", \"Design systems\", \"Prototyping\", \"Research\", \"Brand\", \"Workshops\", \"Art direction\", \"Front-end\"].map((s) => <span key={s} className=\"text-muted-foreground flex items-center gap-10 text-2xl font-medium tracking-tight sm:text-3xl\">{s}<span className=\"bg-chart-1 size-2.5 rounded-full\" aria-hidden=\"true\" /></span>)}\n          </Marquee>\n        </section>\n\n        <section id=\"work\" aria-labelledby=\"pf-work\" className=\"mx-auto max-w-7xl scroll-mt-20 px-4 py-20 sm:px-8\">\n          <div className=\"flex items-end justify-between gap-4\">\n            <h2 id=\"pf-work\" className=\"text-4xl font-semibold tracking-[-0.04em] sm:text-6xl\">Selected work</h2>\n            <a href={h.work} className=\"focus-visible:ring-ring/50 inline-flex items-center gap-1.5 rounded-full text-sm font-semibold underline-offset-4 outline-none hover:underline focus-visible:ring-[3px]\">All projects <ArrowUpRight className=\"size-4 rtl:-scale-x-100\" aria-hidden=\"true\" /></a>\n          </div>\n          <ul className=\"mt-10 grid gap-x-6 gap-y-12 sm:grid-cols-2\">\n            {projects.slice(0, 4).map((p, i) => (\n              <li key={p.slug} className={cn(i % 2 === 1 && \"sm:mt-16\")}>\n                <a href={h.case} className=\"group focus-visible:ring-ring/50 block rounded-3xl outline-none focus-visible:ring-[3px] focus-visible:ring-offset-4 focus-visible:ring-offset-background\">\n                  <div className=\"relative overflow-hidden rounded-3xl border\">\n                    <Cover variant={p.cover} image={p.image} imageAlt={p.imageAlt} className=\"transition-transform duration-700 ease-out group-hover:scale-[1.04] group-focus-visible:scale-[1.04] motion-reduce:transition-none\" />\n                    <div className=\"bg-chart-1 text-[var(--portfolio-on-accent)] absolute end-4 bottom-4 flex translate-y-3 items-center gap-2 rounded-full px-4 py-2 text-sm font-bold opacity-0 shadow-lg transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100 group-focus-visible:translate-y-0 group-focus-visible:opacity-100 motion-reduce:transition-none\">\n                      {p.result} <ArrowUpRight className=\"size-4 rtl:-scale-x-100\" aria-hidden=\"true\" />\n                    </div>\n                  </div>\n                  <div className=\"mt-5 flex items-start justify-between gap-4\">\n                    <div>\n                      <h3 className=\"text-2xl font-semibold tracking-[-0.03em] text-balance\">{p.title}</h3>\n                      <p className=\"text-muted-foreground mt-1 text-pretty\">{p.blurb}</p>\n                    </div>\n                    <p className=\"text-muted-foreground shrink-0 text-sm\" style={mono}>{p.client}<br />{p.year}</p>\n                  </div>\n                  <p className=\"sr-only\">Result: {p.result}</p>\n                </a>\n              </li>\n            ))}\n          </ul>\n        </section>\n\n        <section aria-labelledby=\"pf-about\" className=\"mx-auto max-w-7xl px-4 pb-20 sm:px-8\">\n          <h2 id=\"pf-about\" className=\"sr-only\">About</h2>\n          <div className=\"grid gap-4 md:grid-cols-4 md:grid-rows-2\">\n            <div className=\"bg-chart-1 text-[var(--portfolio-on-accent)] relative flex min-h-72 flex-col justify-end overflow-hidden rounded-3xl p-7 md:col-span-2 md:row-span-2\">\n              <div aria-hidden=\"true\" className=\"absolute -top-8 -end-8 size-56 rounded-full border-[28px] border-current opacity-15\" />\n              <div aria-hidden=\"true\" className=\"absolute top-6 start-6 flex size-24 items-center justify-center rounded-full bg-primary text-4xl font-semibold text-background\">IC</div>\n              <p className=\"text-3xl leading-tight font-semibold tracking-[-0.03em] text-balance sm:text-4xl\">Hi, I’m Ines. I turn tangled products into ones that feel obvious.</p>\n              <p className=\"mt-3 max-w-md text-pretty opacity-80\">Nine years across fintech, health and developer tools. Before going independent I led design at Fernhill and Lumen Health.</p>\n            </div>\n            <div className=\"bg-card rounded-3xl border p-6\">\n              <p className=\"text-muted-foreground flex items-center gap-2 text-xs\" style={mono}><MapPin className=\"size-3.5\" aria-hidden=\"true\" />Based in</p>\n              <p className=\"mt-3 text-3xl font-semibold tracking-[-0.03em]\">Lisbon</p>\n              <p className=\"text-muted-foreground mt-1 text-sm\">Working across Europe and the US East Coast.</p>\n            </div>\n            <div className=\"bg-foreground text-background rounded-3xl p-6\">\n              <dl className=\"space-y-5\">\n                <div><dd className=\"text-5xl font-semibold tracking-[-0.04em] tabular-nums\">9</dd><dt className=\"text-sm opacity-75\">years designing</dt></div>\n                <div><dd className=\"text-5xl font-semibold tracking-[-0.04em] tabular-nums\">40+</dd><dt className=\"text-sm opacity-75\">products launched</dt></div>\n              </dl>\n            </div>\n            <div className=\"bg-card rounded-3xl border p-6 md:col-span-2\">\n              <p className=\"text-muted-foreground text-xs\" style={mono}>Right now</p>\n              <ul className=\"mt-4 grid gap-4 sm:grid-cols-3\">\n                {[[Hammer, \"Building\", \"A pricing experiment kit for Tessera\"], [BookOpen, \"Reading\", \"The Timeless Way of Building\"], [Headphones, \"Listening\", \"Fela Kuti, on repeat\"]].map(([Icon, k, v]) => {\n                  const I = Icon as typeof Hammer\n                  return <li key={k as string}><I className=\"size-5\" aria-hidden=\"true\" /><p className=\"mt-3 text-sm font-semibold\">{k as string}</p><p className=\"text-muted-foreground text-sm text-pretty\">{v as string}</p></li>\n                })}\n              </ul>\n            </div>\n          </div>\n        </section>\n\n        <section aria-label=\"Kind words\" className=\"border-y py-10\">\n          <Marquee speed={32} gap={48} reverse pauseOnHover>\n            {quotes.map(([q, who]) => (\n              <figure key={who} className=\"w-[26rem] max-w-[80vw]\">\n                <blockquote className=\"text-xl leading-snug font-medium tracking-[-0.02em] text-balance\">“{q}”</blockquote>\n                <figcaption className=\"text-muted-foreground mt-3 text-sm\" style={mono}>{who}</figcaption>\n              </figure>\n            ))}\n          </Marquee>\n        </section>\n\n        <section aria-labelledby=\"pf-writing\" className=\"mx-auto max-w-7xl px-4 pt-20 sm:px-8\">\n          <div className=\"flex items-end justify-between gap-4\">\n            <h2 id=\"pf-writing\" className=\"text-4xl font-semibold tracking-[-0.04em] sm:text-5xl\">Recent writing</h2>\n            <a href={h.writing} className=\"focus-visible:ring-ring/50 inline-flex items-center gap-1.5 rounded-full text-sm font-semibold underline-offset-4 outline-none hover:underline focus-visible:ring-[3px]\">All posts <ArrowUpRight className=\"size-4 rtl:-scale-x-100\" aria-hidden=\"true\" /></a>\n          </div>\n          <ul className=\"mt-8 divide-y border-y\">\n            {posts.slice(0, 3).map((p) => (\n              <li key={p.slug}>\n                <a href={h.writing} className=\"hover:bg-accent/60 focus-visible:ring-ring/50 group -mx-3 grid grid-cols-[1fr_auto] items-baseline gap-4 rounded-2xl px-3 py-5 outline-none transition-colors focus-visible:ring-[3px] sm:grid-cols-[1fr_8rem_6rem]\">\n                  <span className=\"text-xl font-semibold tracking-[-0.02em] text-balance sm:text-2xl\">{p.title}</span>\n                  <span className=\"text-muted-foreground hidden text-sm sm:block\" style={mono}>{p.tag}</span>\n                  <span className=\"text-muted-foreground text-end text-sm tabular-nums\" style={mono}>{p.read} min</span>\n                </a>\n              </li>\n            ))}\n          </ul>\n        </section>\n      </main>\n    </PortfolioShell>\n  )\n}\n\nexport { PortfolioHome, type PortfolioHomeProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/portfolio/portfolio-home.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/portfolio/portfolio-work.tsx",
      "content": "// Ballmac UI: Portfolio work page. https://ui.ballmac.com/templates/template-portfolio\n\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowUpRight } from \"lucide-react\"\n\nimport { projects } from \"@/components/ballmac/templates/portfolio/portfolio-data\"\nimport { Cover, PortfolioShell, type PortfolioHrefs } from \"@/components/ballmac/templates/portfolio/portfolio-theme\"\nimport { cn } from \"@/lib/utils\"\n\ntype Filter = \"All\" | \"Product\" | \"Brand\" | \"Systems\"\nconst filters: Filter[] = [\"All\", \"Product\", \"Systems\", \"Brand\"]\n\ntype PortfolioWorkProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<PortfolioHrefs> }\n\n/** The Portfolio work page: every project, filtered by discipline, in a staggered grid with a result on hover. */\nfunction PortfolioWork({ hrefs, ...props }: PortfolioWorkProps) {\n  const [filter, setFilter] = React.useState<Filter>(\"All\")\n  const shown = projects.filter((p) => filter === \"All\" || p.discipline === filter)\n  const caseHref = hrefs?.case ?? \"/portfolio/work/fernhill\"\n  return (\n    <PortfolioShell page=\"work\" hrefs={hrefs} {...props}>\n      <main className=\"mx-auto max-w-7xl px-4 pt-14 sm:px-8 sm:pt-20\">\n        <p className=\"text-muted-foreground text-sm\" style={{ fontFamily: \"var(--portfolio-mono)\" }}>Work · 2023 to 2026</p>\n        <h1 className=\"mt-4 max-w-4xl text-[clamp(2.6rem,7vw,6rem)] leading-[0.95] font-semibold tracking-[-0.05em] text-balance\">Six projects I’m proud of, and what they changed.</h1>\n        <div role=\"group\" aria-label=\"Filter by discipline\" className=\"mt-10 flex flex-wrap gap-2\">\n          {filters.map((f) => {\n            const n = f === \"All\" ? projects.length : projects.filter((p) => p.discipline === f).length\n            return (\n              <button key={f} type=\"button\" aria-pressed={filter === f} onClick={() => setFilter(f)} className={cn(\"focus-visible:ring-ring/50 h-11 rounded-full border px-5 text-sm font-semibold outline-none transition-colors focus-visible:ring-[3px]\", filter === f ? \"bg-foreground text-background border-transparent\" : \"hover:bg-accent\")}>\n                {f} <span className={cn(\"ms-1 tabular-nums\", filter === f ? \"opacity-70\" : \"text-muted-foreground\")}>{n}</span>\n              </button>\n            )\n          })}\n        </div>\n        <p className=\"sr-only\" role=\"status\">{shown.length} projects shown</p>\n        <ul className=\"mt-12 grid gap-x-6 gap-y-14 sm:grid-cols-2 lg:grid-cols-3\">\n          {shown.map((p) => (\n            <li key={p.slug}>\n              <a href={caseHref} className=\"group focus-visible:ring-ring/50 block rounded-3xl outline-none focus-visible:ring-[3px] focus-visible:ring-offset-4 focus-visible:ring-offset-background\">\n                <div className=\"relative overflow-hidden rounded-3xl border\">\n                  <Cover variant={p.cover} image={p.image} imageAlt={p.imageAlt} className=\"transition-transform duration-700 group-hover:scale-[1.04] group-focus-visible:scale-[1.04] motion-reduce:transition-none\" />\n                  <span className=\"bg-chart-1 text-[var(--portfolio-on-accent)] absolute end-3 bottom-3 inline-flex translate-y-2 items-center gap-1.5 rounded-full px-3.5 py-1.5 text-xs font-bold opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100 group-focus-visible:translate-y-0 group-focus-visible:opacity-100 motion-reduce:transition-none\">{p.result}<ArrowUpRight className=\"size-3.5 rtl:-scale-x-100\" aria-hidden=\"true\" /></span>\n                </div>\n                <div className=\"mt-4\">\n                  <p className=\"text-muted-foreground text-xs\" style={{ fontFamily: \"var(--portfolio-mono)\" }}>{p.client} · {p.year} · {p.discipline}</p>\n                  <h2 className=\"mt-1.5 text-xl font-semibold tracking-[-0.025em] text-balance\">{p.title}</h2>\n                  <p className=\"text-muted-foreground mt-1 text-sm text-pretty\">{p.blurb}</p>\n                  <p className=\"sr-only\">Result: {p.result}</p>\n                </div>\n              </a>\n            </li>\n          ))}\n        </ul>\n      </main>\n    </PortfolioShell>\n  )\n}\n\nexport { PortfolioWork, type PortfolioWorkProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/portfolio/portfolio-work.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/portfolio/portfolio-case.tsx",
      "content": "// Ballmac UI: Portfolio case study page. https://ui.ballmac.com/templates/template-portfolio\n\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowRight } from \"lucide-react\"\n\nimport { TableOfContents } from \"@/components/ballmac/table-of-contents\"\nimport { projects } from \"@/components/ballmac/templates/portfolio/portfolio-data\"\nimport { Cover, PortfolioShell, type PortfolioHrefs } from \"@/components/ballmac/templates/portfolio/portfolio-theme\"\nimport { cn } from \"@/lib/utils\"\n\nconst mono = { fontFamily: \"var(--portfolio-mono)\" } as const\nconst h2 = \"scroll-mt-24 text-3xl font-semibold tracking-[-0.035em] text-balance sm:text-4xl\"\nconst body = \"text-muted-foreground mt-5 max-w-2xl text-lg leading-relaxed text-pretty\"\n\nconst meta = [\n  [\"Role\", \"Lead product designer\"],\n  [\"Team\", \"2 designers, 5 engineers, 1 PM\"],\n  [\"Timeline\", \"14 weeks, Mar to Jun 2026\"],\n  [\"Tools\", \"Figma, Linear, Maze, Framer\"],\n]\n\nconst process = [\n  [\"Week 1–2\", \"Listen\", \"Watched 14 new teams try to get started. Nine gave up before inviting a colleague.\"],\n  [\"Week 3–5\", \"Frame\", \"Reduced the goal to one sentence: send your first invoice in one sitting.\"],\n  [\"Week 6–10\", \"Build and test\", \"Four prototypes, 22 tests. We cut the setup from nine steps to three.\"],\n  [\"Week 11–14\", \"Ship and measure\", \"Rolled out behind a flag to 10, then 50, then 100 percent of new signups.\"],\n]\n\nconst results = [[\"+34%\", \"of new teams activate in their first week\"], [\"−58%\", \"time to first invoice\"], [\"9 → 3\", \"setup steps\"], [\"4.7\", \"onboarding satisfaction, up from 3.9\"]]\n\ntype PortfolioCaseProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<PortfolioHrefs> }\n\n/** The Portfolio case study: a sticky fact sheet, a contents list that follows your reading, the process as a timeline and results in big numbers. */\nfunction PortfolioCase({ hrefs, ...props }: PortfolioCaseProps) {\n  const next = projects[1]\n  return (\n    <PortfolioShell page=\"case\" hrefs={hrefs} {...props}>\n      <main>\n        <header className=\"mx-auto max-w-7xl px-4 pt-14 sm:px-8 sm:pt-20\">\n          <p className=\"text-muted-foreground text-sm\" style={mono}><a href={hrefs?.work ?? \"/portfolio/work\"} className=\"underline-offset-4 hover:underline\">Work</a> / Fernhill</p>\n          <h1 className=\"mt-4 max-w-5xl text-[clamp(2.6rem,7.4vw,6.5rem)] leading-[0.95] font-semibold tracking-[-0.05em] text-balance\">Rebuilding onboarding so teams reach their first invoice in one sitting.</h1>\n          <Cover variant={0} className=\"mt-12 aspect-[16/8] rounded-3xl border\" />\n        </header>\n\n        <div className=\"mx-auto mt-16 grid max-w-7xl gap-12 px-4 sm:px-8 lg:grid-cols-[16rem_minmax(0,1fr)] xl:grid-cols-[16rem_minmax(0,1fr)_14rem]\">\n          <aside className=\"lg:sticky lg:top-24 lg:self-start\">\n            <dl className=\"space-y-5\">\n              {meta.map(([k, v]) => <div key={k}><dt className=\"text-muted-foreground text-xs\" style={mono}>{k}</dt><dd className=\"mt-1 text-sm font-medium text-pretty\">{v}</dd></div>)}\n            </dl>\n          </aside>\n\n          <article className=\"min-w-0 space-y-20\">\n            <section aria-labelledby=\"overview\">\n              <h2 id=\"overview\" className={h2}>Overview</h2>\n              <p className={body}>Fernhill is invoicing software for small studios. It was good at the second month and bad at the first day: almost half of new teams never sent an invoice. I led a 14-week rebuild of the first-run experience.</p>\n            </section>\n            <section aria-labelledby=\"problem\">\n              <h2 id=\"problem\" className={h2}>The problem</h2>\n              <p className={body}>The old flow asked for nine things before it showed any value: company details, tax settings, a logo, bank info, a template, a first customer. Every step was reasonable. Together they were a wall.</p>\n              <blockquote className=\"bg-chart-1 text-[var(--portfolio-on-accent)] mt-8 max-w-2xl rounded-3xl p-8 text-2xl leading-snug font-semibold tracking-[-0.025em] text-balance\">“I just wanted to send one invoice. I closed the tab at step six.”<footer className=\"mt-4 text-sm font-medium opacity-75\" style={mono}>Participant 7, studio owner</footer></blockquote>\n            </section>\n            <section aria-labelledby=\"process\">\n              <h2 id=\"process\" className={h2}>Process</h2>\n              <ol className=\"mt-8 divide-y border-y\">\n                {process.map(([when, title, text]) => (\n                  <li key={title} className=\"grid gap-2 py-6 sm:grid-cols-[8rem_10rem_1fr] sm:gap-6\">\n                    <span className=\"text-muted-foreground text-sm\" style={mono}>{when}</span>\n                    <span className=\"text-xl font-semibold tracking-[-0.02em]\">{title}</span>\n                    <span className=\"text-muted-foreground text-pretty\">{text}</span>\n                  </li>\n                ))}\n              </ol>\n            </section>\n            <section aria-labelledby=\"solution\">\n              <h2 id=\"solution\" className={h2}>The solution</h2>\n              <p className={body}>One screen asks the only question that matters: who are you invoicing? Everything else is a default you can change later, and the first invoice is a draft by the time you finish typing the customer’s name.</p>\n              <div className=\"mt-8 grid gap-4 sm:grid-cols-2\"><Cover variant={4} className=\"rounded-3xl border\" /><Cover variant={3} className=\"rounded-3xl border\" /></div>\n            </section>\n            <section aria-labelledby=\"results\">\n              <h2 id=\"results\" className={h2}>Results</h2>\n              <dl className=\"mt-8 grid gap-4 sm:grid-cols-2\">\n                {results.map(([v, l], i) => (\n                  <div key={l} className={cn(\"rounded-3xl p-7\", i === 0 ? \"bg-chart-1 text-[var(--portfolio-on-accent)]\" : \"bg-secondary\")}>\n                    <dd className=\"text-6xl font-semibold tracking-[-0.05em] tabular-nums\">{v}</dd>\n                    <dt className={cn(\"mt-2 text-pretty\", i === 0 ? \"opacity-80\" : \"text-muted-foreground\")}>{l}</dt>\n                  </div>\n                ))}\n              </dl>\n            </section>\n            <section aria-labelledby=\"reflection\">\n              <h2 id=\"reflection\" className={h2}>What I’d do differently</h2>\n              <p className={body}>I would have involved sales earlier. They knew which questions prospects asked on day one, and two of them became default answers we had to bolt on after launch.</p>\n            </section>\n          </article>\n\n          <aside className=\"hidden xl:block\">\n            <div className=\"sticky top-24\">\n              <TableOfContents title=\"On this page\" offset={96} items={[{ id: \"overview\", title: \"Overview\" }, { id: \"problem\", title: \"The problem\" }, { id: \"process\", title: \"Process\" }, { id: \"solution\", title: \"The solution\" }, { id: \"results\", title: \"Results\" }, { id: \"reflection\", title: \"Reflection\" }]} />\n            </div>\n          </aside>\n        </div>\n\n        <section aria-label=\"Next project\" className=\"mx-auto mt-24 max-w-7xl px-4 sm:px-8\">\n          <a href={hrefs?.case ?? \"/portfolio/work/lumen\"} className=\"group bg-foreground text-background focus-visible:ring-ring/50 grid items-center gap-8 overflow-hidden rounded-3xl p-6 outline-none focus-visible:ring-[3px] focus-visible:ring-offset-4 focus-visible:ring-offset-background sm:grid-cols-[1fr_20rem] sm:p-10\">\n            <div><p className=\"text-sm opacity-60\" style={mono}>Next project</p><p className=\"mt-3 text-4xl font-semibold tracking-[-0.04em] text-balance sm:text-6xl\">{next.title}</p><p className=\"mt-5 inline-flex items-center gap-2 font-semibold\">{next.client}<ArrowRight className=\"size-5 transition-transform group-hover:translate-x-1 motion-reduce:transition-none rtl:rotate-180 rtl:group-hover:-translate-x-1\" aria-hidden=\"true\" /></p></div>\n            <Cover variant={next.cover} image={next.image} imageAlt={next.imageAlt} className=\"rounded-2xl\" />\n          </a>\n        </section>\n      </main>\n    </PortfolioShell>\n  )\n}\n\nexport { PortfolioCase, type PortfolioCaseProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/portfolio/portfolio-case.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/portfolio/portfolio-writing.tsx",
      "content": "// Ballmac UI: Portfolio writing page. https://ui.ballmac.com/templates/template-portfolio\n\"use client\"\n\nimport * as React from \"react\"\nimport { Search } from \"lucide-react\"\n\nimport { posts } from \"@/components/ballmac/templates/portfolio/portfolio-data\"\nimport { PortfolioShell, type PortfolioHrefs } from \"@/components/ballmac/templates/portfolio/portfolio-theme\"\nimport { cn } from \"@/lib/utils\"\n\nconst mono = { fontFamily: \"var(--portfolio-mono)\" } as const\ntype Tag = \"All\" | \"Process\" | \"Craft\" | \"Career\"\nconst tags: Tag[] = [\"All\", \"Process\", \"Craft\", \"Career\"]\nconst dateFmt = new Intl.DateTimeFormat(\"en-US\", { month: \"short\", day: \"numeric\", year: \"numeric\", timeZone: \"UTC\" })\n\ntype PortfolioWritingProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<PortfolioHrefs> }\n\n/** The Portfolio writing page: posts grouped by year, with a topic filter, a search and the reading time of each. */\nfunction PortfolioWriting({ hrefs, ...props }: PortfolioWritingProps) {\n  const [tag, setTag] = React.useState<Tag>(\"All\")\n  const [query, setQuery] = React.useState(\"\")\n  const q = query.trim().toLowerCase()\n  const shown = posts.filter((p) => (tag === \"All\" || p.tag === tag) && (!q || (p.title + p.excerpt).toLowerCase().includes(q)))\n  const years = [...new Set(shown.map((p) => p.date.slice(0, 4)))]\n  return (\n    <PortfolioShell page=\"writing\" hrefs={hrefs} {...props}>\n      <main className=\"mx-auto max-w-5xl px-4 pt-14 sm:px-8 sm:pt-20\">\n        <p className=\"text-muted-foreground text-sm\" style={mono}>Writing</p>\n        <h1 className=\"mt-4 text-[clamp(2.6rem,7vw,5.5rem)] leading-[0.95] font-semibold tracking-[-0.05em] text-balance\">Notes on making things people use.</h1>\n        <div className=\"mt-10 flex flex-wrap items-center gap-3\">\n          <div role=\"group\" aria-label=\"Filter by topic\" className=\"flex flex-wrap gap-2\">\n            {tags.map((t) => <button key={t} type=\"button\" aria-pressed={tag === t} onClick={() => setTag(t)} className={cn(\"focus-visible:ring-ring/50 h-10 rounded-full border px-4 text-sm font-semibold outline-none transition-colors focus-visible:ring-[3px]\", tag === t ? \"bg-foreground text-background border-transparent\" : \"hover:bg-accent\")}>{t}</button>)}\n          </div>\n          <div className=\"relative w-full sm:ms-auto sm:w-64\">\n            <Search className=\"text-muted-foreground pointer-events-none absolute top-1/2 start-3.5 size-4 -translate-y-1/2\" aria-hidden=\"true\" />\n            <input type=\"search\" aria-label=\"Search posts\" value={query} onChange={(e) => setQuery(e.target.value)} placeholder=\"Search posts\" className=\"bg-card focus-visible:ring-ring/50 placeholder:text-muted-foreground h-10 w-full rounded-full border pe-4 ps-10 text-sm outline-none focus-visible:ring-[3px]\" />\n          </div>\n        </div>\n        <p className=\"sr-only\" role=\"status\">{shown.length} posts</p>\n        {years.length === 0 && <p className=\"text-muted-foreground mt-16 rounded-3xl border border-dashed p-12 text-center\">No posts match. Try another topic.</p>}\n        {years.map((y) => (\n          <section key={y} aria-labelledby={`pw-${y}`} className=\"mt-14 grid gap-4 sm:grid-cols-[8rem_1fr]\">\n            <h2 id={`pw-${y}`} className=\"text-muted-foreground text-sm\" style={mono}>{y}</h2>\n            <ul className=\"divide-y border-y\">\n              {shown.filter((p) => p.date.startsWith(y)).map((p) => (\n                <li key={p.slug}>\n                  <a href=\"#\" className=\"hover:bg-accent/60 focus-visible:ring-ring/50 -mx-3 block rounded-2xl px-3 py-6 outline-none transition-colors focus-visible:ring-[3px]\">\n                    <p className=\"text-muted-foreground text-xs\" style={mono}>{dateFmt.format(new Date(p.date))} · {p.tag} · {p.read} min</p>\n                    <h3 className=\"mt-2 text-2xl font-semibold tracking-[-0.03em] text-balance\">{p.title}</h3>\n                    <p className=\"text-muted-foreground mt-1.5 text-pretty\">{p.excerpt}</p>\n                  </a>\n                </li>\n              ))}\n            </ul>\n          </section>\n        ))}\n      </main>\n    </PortfolioShell>\n  )\n}\n\nexport { PortfolioWriting, type PortfolioWritingProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/portfolio/portfolio-writing.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/portfolio/portfolio-uses.tsx",
      "content": "// Ballmac UI: Portfolio uses page. https://ui.ballmac.com/templates/template-portfolio\nimport * as React from \"react\"\n\nimport { uses } from \"@/components/ballmac/templates/portfolio/portfolio-data\"\nimport { PortfolioShell, type PortfolioHrefs } from \"@/components/ballmac/templates/portfolio/portfolio-theme\"\n\nconst mono = { fontFamily: \"var(--portfolio-mono)\" } as const\n\ntype PortfolioUsesProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<PortfolioHrefs> }\n\n/** The Portfolio uses page: the tools, hardware and books behind the work, in plain lists. */\nfunction PortfolioUses({ hrefs, ...props }: PortfolioUsesProps) {\n  return (\n    <PortfolioShell page=\"uses\" hrefs={hrefs} {...props}>\n      <main className=\"mx-auto max-w-5xl px-4 pt-14 sm:px-8 sm:pt-20\">\n        <p className=\"text-muted-foreground text-sm\" style={mono}>Uses · updated September 2026</p>\n        <h1 className=\"mt-4 text-[clamp(2.6rem,7vw,5.5rem)] leading-[0.95] font-semibold tracking-[-0.05em] text-balance\">What’s on my desk, and why.</h1>\n        <p className=\"text-muted-foreground mt-6 max-w-xl text-lg text-pretty\">People ask. Nothing here is sponsored, and most of it is older than it should be.</p>\n        <div className=\"mt-16 space-y-16\">\n          {uses.map((g) => (\n            <section key={g.group} aria-labelledby={`pu-${g.group}`} className=\"grid gap-6 sm:grid-cols-[10rem_1fr]\">\n              <h2 id={`pu-${g.group}`} className=\"text-2xl font-semibold tracking-[-0.03em]\">{g.group}</h2>\n              <ul className=\"divide-y border-y\">\n                {g.items.map((i) => (\n                  <li key={i.name} className=\"grid gap-1 py-5 sm:grid-cols-[14rem_1fr] sm:gap-6\">\n                    <span className=\"font-semibold\">{i.name}</span>\n                    <span className=\"text-muted-foreground text-pretty\">{i.note}</span>\n                  </li>\n                ))}\n              </ul>\n            </section>\n          ))}\n        </div>\n      </main>\n    </PortfolioShell>\n  )\n}\n\nexport { PortfolioUses, type PortfolioUsesProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/portfolio/portfolio-uses.tsx"
    },
    {
      "path": "registry/ballmac/app/portfolio/page.tsx",
      "content": "// Ballmac UI: Portfolio template route. https://ui.ballmac.com/templates/template-portfolio\nimport type { Metadata } from \"next\"\n\nimport { PortfolioHome } from \"@/components/ballmac/templates/portfolio/portfolio-home\"\n\nexport const metadata: Metadata = {\n  title: \"Ines Calder: product designer\",\n  description: \"Independent product designer in Lisbon. Selected work, writing and how to get in touch.\",\n}\n\nexport default function Page() {\n  return <PortfolioHome />\n}\n",
      "type": "registry:page",
      "target": "app/portfolio/page.tsx"
    },
    {
      "path": "registry/ballmac/app/portfolio/work/page.tsx",
      "content": "// Ballmac UI: Portfolio template route. https://ui.ballmac.com/templates/template-portfolio\nimport type { Metadata } from \"next\"\n\nimport { PortfolioWork } from \"@/components/ballmac/templates/portfolio/portfolio-work\"\n\nexport const metadata: Metadata = {\n  title: \"Work · Ines Calder\",\n  description: \"Six projects and what they changed.\",\n}\n\nexport default function Page() {\n  return <PortfolioWork />\n}\n",
      "type": "registry:page",
      "target": "app/portfolio/work/page.tsx"
    },
    {
      "path": "registry/ballmac/app/portfolio/work/[slug]/page.tsx",
      "content": "// Ballmac UI: Portfolio template route. https://ui.ballmac.com/templates/template-portfolio\nimport type { Metadata } from \"next\"\n\nimport { PortfolioCase } from \"@/components/ballmac/templates/portfolio/portfolio-case\"\n\nexport const metadata: Metadata = {\n  title: \"Rebuilding onboarding · Ines Calder\",\n  description: \"A case study: how a first-run flow got teams to their first invoice in one sitting.\",\n}\n\n// One layout for every case study. Load the project by params.slug from your data when you add more.\nexport default function Page() {\n  return <PortfolioCase />\n}\n",
      "type": "registry:page",
      "target": "app/portfolio/work/[slug]/page.tsx"
    },
    {
      "path": "registry/ballmac/app/portfolio/writing/page.tsx",
      "content": "// Ballmac UI: Portfolio template route. https://ui.ballmac.com/templates/template-portfolio\nimport type { Metadata } from \"next\"\n\nimport { PortfolioWriting } from \"@/components/ballmac/templates/portfolio/portfolio-writing\"\n\nexport const metadata: Metadata = {\n  title: \"Writing · Ines Calder\",\n  description: \"Notes on making things people use.\",\n}\n\nexport default function Page() {\n  return <PortfolioWriting />\n}\n",
      "type": "registry:page",
      "target": "app/portfolio/writing/page.tsx"
    },
    {
      "path": "registry/ballmac/app/portfolio/uses/page.tsx",
      "content": "// Ballmac UI: Portfolio template route. https://ui.ballmac.com/templates/template-portfolio\nimport type { Metadata } from \"next\"\n\nimport { PortfolioUses } from \"@/components/ballmac/templates/portfolio/portfolio-uses\"\n\nexport const metadata: Metadata = {\n  title: \"Uses · Ines Calder\",\n  description: \"The tools, hardware and books behind the work.\",\n}\n\nexport default function Page() {\n  return <PortfolioUses />\n}\n",
      "type": "registry:page",
      "target": "app/portfolio/uses/page.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "template",
      "portfolio",
      "designer",
      "case study",
      "personal site",
      "writing",
      "bento"
    ],
    "version": "1.0.0",
    "updated": "2026-10-01",
    "ai": {
      "summary": "Installs a five-page personal portfolio. Change the content in portfolio-data.ts, the palette in portfolioCss, and the painted covers in the Cover component (or replace them with images).",
      "whenToUse": [
        "Personal sites for designers, developers and independent consultants",
        "A bold, typographic portfolio with a real case-study layout"
      ],
      "whenNotToUse": [
        "Agencies with a team and services pages (use template-studio)"
      ],
      "composesWith": [
        "marquee",
        "table-of-contents",
        "blur-fade"
      ],
      "a11y": [
        {
          "keys": "Tab to a work card",
          "action": "Reveals the result on the card, the same as hovering it"
        },
        {
          "keys": "Enter / Space on a filter",
          "action": "Filters the list and announces the number shown"
        }
      ],
      "customization": [
        "Replace Cover with <img> or next/image when you have real screenshots",
        "Edit portfolioCss for the light and dark palettes",
        "Swap the font in portfolio-fonts.ts"
      ]
    },
    "examples": [
      "template-portfolio-demo",
      "template-portfolio-work",
      "template-portfolio-case",
      "template-portfolio-writing",
      "template-portfolio-uses"
    ],
    "url": "https://ui.ballmac.com/components/template-portfolio"
  },
  "docs": "Pages are at /portfolio, /portfolio/work, /portfolio/work/[slug], /portfolio/writing and /portfolio/uses. Edit your projects, posts and kit in portfolio-data.ts; the palette is the portfolioCss string in portfolio-theme.tsx.",
  "categories": [
    "templates"
  ],
  "type": "registry:block"
}