Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/ui/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Particle Component Development Guide for @coss/ui

For blueprint illustration work, read [Plotter](skills/plotter/SKILL.md). It records the visual direction, reference images, and review feedback for the production heroes and the unlinked `/ui/plotter` showcase.

This guide provides comprehensive instructions for creating new particle components that match the existing library's patterns and best practices. Use this when creating equivalents of origin components or building new particles from scratch.

---
Expand Down Expand Up @@ -1377,3 +1379,13 @@ const items = [
---

This guide should enable autonomous creation of particles that match the existing library's patterns, style, and best practices. Always refer to existing particles in the same category for consistency.

<!-- BEGIN:nextjs-agent-rules -->

## This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.

This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.

<!-- END:nextjs-agent-rules -->
19 changes: 19 additions & 0 deletions apps/ui/app/plotter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Plotter illustrations

Direct route: `/ui/plotter`. This showcase is intentionally absent from site navigation and the sitemap, and its metadata disables indexing. The former `/ui/illustration-lab` and `/ui/illustration-lab-prototype` URLs redirect here with their query parameters.

The gallery presents 39 flat monochrome illustrations, organized by subject with category filters and enlarged previews. Stable illustration IDs preserve links and feedback history. Discarded drawings have been removed from the collection; their critique remains in the Plotter skill’s feedback ledger. Inclusion in this curated collection does not make every drawing an approved visual anchor.

## Production selections

| Illustration | Selected page | Showcase source |
| --- | --- | --- |
| P00 | Main | `./primitives-blueprint.tsx` |
| C03 | Particles | `./particles-blueprint.tsx` |
| E02 | 404 | `./not-found-blueprint.tsx` |

The showcase owns these drawing snapshots and its `BlueprintDrawing` wrapper. It can ship independently of the production illustration integration in PR #912. Original C00 and E00 remain separate examples. When a selected production drawing changes, update its showcase snapshot explicitly if that revision should also appear in the gallery.

## Plotter skill

The reusable style instructions, visual anchors, and feedback ledger live in [`../../skills/plotter`](../../skills/plotter). Keep specific user feedback separate from interpretations; a new revision is unreviewed until the user responds. Add illustrations to their subject collection and preserve existing IDs when revising them.
44 changes: 44 additions & 0 deletions apps/ui/app/plotter/blueprint-drawing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { ReactNode } from "react";

export function BlueprintDrawing({ children }: { children: ReactNode }) {
return (
<svg
aria-hidden="true"
className="h-auto w-full overflow-visible font-sans text-foreground [font-optical-sizing:auto] [font-synthesis:none] [font-variation-settings:'GEOM'_25]"
fill="none"
focusable="false"
stroke="currentColor"
strokeWidth="0.8"
viewBox="0 0 560 440"
>
{children}
</svg>
);
}

// Draws hero geometry at empty-state size. Strokes stay one pixel at any scale.
export function CompactBlueprintDrawing({
center: [x, y],
scale,
children,
}: {
center: [number, number];
scale: number;
children: ReactNode;
}) {
return (
<svg
aria-hidden="true"
className="size-full overflow-visible text-foreground [&_*]:[vector-effect:non-scaling-stroke]"
fill="none"
focusable="false"
stroke="currentColor"
strokeWidth="1"
viewBox="0 0 96 96"
>
<g transform={`translate(48 48) scale(${scale}) translate(${-x} ${-y})`}>
{children}
</g>
</svg>
);
}
187 changes: 187 additions & 0 deletions apps/ui/app/plotter/cal-studies.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import { BlueprintDrawing } from "./blueprint-drawing";
import type { IllustrationStudy } from "./study-types";

function RegistrationMarks() {
return (
<g opacity="0.28" strokeWidth="0.6">
<path d="M100 86h12M106 80v12M448 354h12M454 348v12" />
<path d="M262 392h36M280 386v12" />
</g>
);
}

function SharedAvailability() {
return (
<BlueprintDrawing>
<g opacity="0.16" strokeWidth="0.6">
<path d="M140 112V324M196 112V324M252 112V324M308 112V324M364 112V324M420 112V324" />
<path d="M100 160H460M100 200H460M100 240H460M100 280H460" />
<path d="M100 220H460" strokeDasharray="5 5" />
</g>

<path
d="M280 124H336V318H280Z"
fill="currentColor"
fillOpacity="0.025"
opacity="0.45"
strokeDasharray="3 5"
strokeWidth="0.65"
/>
<g opacity="0.25" strokeWidth="0.7">
<path d="M140 180H420M140 260H420" />
<path d="M140 174V186M420 174V186M140 254V266M420 254V266" />
</g>

<rect
fill="var(--color-sidebar)"
height="40"
rx="6"
strokeWidth="0.95"
width="140"
x="196"
y="160"
/>
<rect
fill="var(--color-sidebar)"
height="40"
rx="6"
strokeWidth="0.95"
width="112"
x="280"
y="240"
/>
<path
d="M280 160H330Q336 160 336 166V194Q336 200 330 200H280Z"
fill="currentColor"
fillOpacity="0.055"
stroke="none"
/>
<path
d="M286 240H336V280H286Q280 280 280 274V246Q280 240 286 240Z"
fill="currentColor"
fillOpacity="0.055"
stroke="none"
/>
<g opacity="0.43" strokeWidth="0.65">
<path d="M280 136V304M336 136V304" strokeDasharray="3 4" />
<path d="M308 200V240" />
<path d="M303 215H313M303 225H313" />
</g>
<g fill="var(--color-sidebar)" strokeWidth="0.9">
<circle cx="112" cy="180" r="10" />
<circle cx="112" cy="260" r="10" />
</g>
<g fill="currentColor" fontSize="10" opacity="0.7" stroke="none">
<text textAnchor="middle" x="112" y="183.5">
A
</text>
<text textAnchor="middle" x="112" y="263.5">
B
</text>
</g>
<g opacity="0.5" strokeWidth="0.65">
<path d="M280 104H336M280 96V112M336 96V112" />
<path d="m276 108 8-8m48 8 8-8" />
<path d="M280 326V346M336 326V346M280 338H296M320 338H336" />
<path d="m276 342 8-8m48 8 8-8" />
</g>
<g fill="currentColor" fontSize="10" opacity="0.52" stroke="none">
<text textAnchor="end" x="278" y="91">
13:00
</text>
<text x="338" y="91">
15:00
</text>
<text textAnchor="middle" x="308" y="341">
2h
</text>
<text textAnchor="middle" x="280" y="367">
Shared availability
</text>
</g>
<RegistrationMarks />
</BlueprintDrawing>
);
}

function TwoTimeZones() {
return (
<BlueprintDrawing>
<g opacity="0.1" strokeWidth="0.6">
<path d="M104 124V316 M176 124V316 M392 124V316 M464 124V316" />
<path d="M88 136H232 M336 136H480 M88 248H232 M336 248H480" />
</g>
<g opacity="0.28" strokeDasharray="4 6" strokeWidth="0.6">
<path d="M248 212V240 M320 212V240 M248 324V332 M320 324V332" />
</g>
<g opacity="0.5" strokeWidth="0.65">
<path d="M104 184H464 M104 296H464" />
{[104, 176, 248, 320, 392, 464].map((x) => (
<path d={`M${x} 180v8 M${x} 292v8`} key={x} />
))}
<path d="M248 104H270 M298 104H320 M248 96V120 M320 96V120" />
<path d="m244 108 8-8 m64 8 8-8" />
<path d="M274 220h20 M274 228h20" />
<path d="M248 328H320 M248 324v8 M320 324v8" />
</g>
<g strokeWidth="0.95">
<rect height="40" rx="6" width="72" x="248" y="136" />
<rect height="40" rx="6" width="72" x="248" y="248" />
</g>
<g fill="currentColor" fontSize="11" opacity="0.75" stroke="none">
<text x="104" y="160">
UTC
</text>
<text x="104" y="272">
UTC+03
</text>
<text textAnchor="middle" x="284" y="160">
09:00
</text>
<text textAnchor="middle" x="284" y="272">
12:00
</text>
</g>
<g fill="currentColor" fontSize="10" opacity="0.52" stroke="none">
{["07", "08", "09", "10", "11", "12"].map((hour, index) => (
<text key={hour} textAnchor="middle" x={104 + index * 72} y="204">
{hour}
</text>
))}
{["10", "11", "12", "13", "14", "15"].map((hour, index) => (
<text key={hour} textAnchor="middle" x={104 + index * 72} y="316">
{hour}
</text>
))}
<text textAnchor="middle" x="284" y="107">
1h
</text>
<text textAnchor="middle" x="284" y="354">
Same booking
</text>
</g>
<RegistrationMarks />
</BlueprintDrawing>
);
}

export const calStudies: IllustrationStudy[] = [
{
id: "cal-availability",
code: "K02",
title: "Common ground",
description:
"Two independent schedules share one precisely aligned window. The overlap becomes the composition’s central column.",
category: "Scheduling",
illustration: <SharedAvailability />,
},
{
id: "cal-time-zones",
code: "K03",
title: "One moment, two places",
description:
"Two hour scales describe the same booking. An aligned one-hour interval stays fixed while the local time changes by three hours.",
category: "Scheduling",
illustration: <TwoTimeZones />,
},
];
Loading
Loading